Better Helpdesk
Operations

Run jobs and retention

Call runJobs on a schedule so emails go out, reminders fire, snoozes wake and old conversations are deleted.

Better Helpdesk runs no background process. Work that should not hold up a request is queued in the helpdesk.job table, and nothing happens to it until your app calls helpdesk.runJobs(). Every email goes through that queue, so does AI triage, and the same call wakes snoozed conversations, queues reminders and SLA notices, and applies retention. Run it every minute or every few minutes. How it works explains why this work waits for your scheduler.

Pick a scheduler

When next start runs as a server that stays up, start a timer from Next.js's instrumentation.ts:

instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME !== 'nodejs') return;
  const { helpdesk } = await import('./lib/helpdesk');
  setInterval(() => {
    helpdesk.runJobs({ budgetMs: 20_000 }).catch(error => {
      console.error('[helpdesk] jobs run failed', error);
    });
  }, 60_000).unref();
}

The budget stays under the interval, so runs do not pile up. Several containers can run this at once: jobs and reminders are claimed in the database, so each is done once.

Read what a run did

runJobs returns a report, which the HTTP route and the cron route above send back as JSON:

{ "woken": 0, "reminders": 1, "sla": 0, "retention": 0, "jobs": 4, "failed": 0, "errors": [] }

errors names a step that threw (wake, reminders, sla or retention); the error itself is logged, and the run goes on with the next step. Jobs in the routes reference describes every field.

Delete resolved conversations after a while

Without retentionDays, conversations are kept for good. With it, each run deletes resolved conversations that many days after they were resolved:

lib/helpdesk.ts
export const helpdesk = buildHelpdesk({
  // ...
  retentionDays: 365,
});

Their messages and attachments go with them. Files are deleted from your storage first, so a storage failure leaves the rows in place for the next run to try again. A conversation a customer reopens is no longer resolved, and its clock starts again when it is resolved next.

To delete one person's or one organization's data on request, use deleteContact and deleteCompany, as in Call the helpdesk from your own code.

Find jobs that failed

A job whose handler throws is tried again after 2, 4, 8 and 16 minutes. After the fifth failed attempt it is parked: it stays in the table with its last error and a run_at of 31 December 9999, so it is never claimed again. To see the parked ones on Postgres:

SELECT kind, payload, attempts, last_error, created_at
FROM helpdesk.job
WHERE run_at >= '9999-01-01'
ORDER BY created_at DESC;

Once the cause is fixed, a parked job can be put back in the queue:

UPDATE helpdesk.job SET run_at = now(), attempts = 0 WHERE run_at >= '9999-01-01';

On SQL Server the table is helpdesk.job too, and now() is SYSUTCDATETIME(). On MySQL and SQLite it is helpdesk_job; use that database's own current time in the same form as the other rows' run_at.

The helpdesk.job table and its columns are described in Database.

On this page