> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aspfox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Background Job Schedules

> Cron schedules, batch sizes, retry behavior, and what to do when a job fails.

All jobs run via Hangfire with PostgreSQL storage. The Hangfire dashboard is at `/hangfire` (requires `is_admin` JWT claim).

## Job schedule reference

| Job                  | Cron schedule                           | Batch size                            | What it does                                                                                                      |
| -------------------- | --------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `trial-expiry`       | `0 9 * * *` (daily 9:00 AM UTC)         | 100 tenants/run                       | Sends 7-day and 1-day trial warning emails. Expires trials that have ended and downgrades tenants to Free.        |
| `subscription-sync`  | `0 */2 * * *` (every 2 hours)           | All tenants with Stripe subscriptions | Fetches current subscription state from Stripe and reconciles with local database. Recovers from missed webhooks. |
| `token-cleanup`      | `0 3 * * *` (daily 3:00 AM UTC)         | All expired tokens                    | Deletes expired refresh tokens, password reset tokens, email verification tokens, and magic link tokens.          |
| `invitation-cleanup` | `0 4 * * 0` (weekly Sunday 4:00 AM UTC) | All expired invitations               | Removes invitations that expired more than 30 days ago. Keeps recently expired ones visible for resend.           |

## Retry behavior

All jobs use the same retry policy: **3 attempts** with exponential backoff.

| Attempt   | Delay after failure      |
| --------- | ------------------------ |
| 1st retry | 60 seconds               |
| 2nd retry | 300 seconds (5 minutes)  |
| 3rd retry | 900 seconds (15 minutes) |

After 3 failed attempts, the job moves to the **Failed** queue in the Hangfire dashboard and stops retrying automatically.

## What to do when a job fails repeatedly

1. Go to `/hangfire` → **Failed Jobs**
2. Click on the failed job to see the full exception and stack trace
3. Fix the root cause (usually a database connectivity issue, Stripe API error, or configuration problem)
4. Click **Requeue** to retry the job immediately
5. Monitor the job's next scheduled run to confirm it succeeds

Common causes:

* `token-cleanup` fails → usually a PostgreSQL connectivity issue
* `subscription-sync` fails → usually a Stripe API rate limit or network issue; retries automatically
* `trial-expiry` fails → usually an email delivery issue; check Resend dashboard

## Triggering a job manually

From the Hangfire dashboard:

1. **Recurring Jobs** tab
2. Find the job by name
3. Click **Trigger now**

The job starts immediately regardless of its scheduled time.

## Disabling a job

To disable a job without deleting it:

```csharp theme={null}
// Comment out or remove the RecurringJob.AddOrUpdate call in HangfireJobRegistrar.cs
// RecurringJob.AddOrUpdate<TrialExpiryJob>(…)

// Optionally, explicitly remove it so it does not appear in the dashboard:
RecurringJob.RemoveIfExists("trial-expiry");
```

Restart the API after making this change.

## Viewing job history

Hangfire stores the execution history of every job run. From the dashboard:

* **Succeeded** — completed runs with duration and timestamp
* **Failed** — failed runs with exception details
* **Recurring Jobs** — next scheduled run, last execution status

Job history is retained for 7 days by default. Configure the retention period in `HangfireConfiguration.cs`.
