Skip to content

Background processing

Some things in CRM happen with nobody at a keyboard. An SLA turns red overnight, an escalation nobody acknowledged climbs to the next level, an approval request nobody answered expires and the quote behind it drops back to Draft. This page is the inventory of those timed jobs: what each one changes, how often it runs, and whether it is running at all out of the box.

Administrators and whoever runs your deployment need this page to configure it. Everyone else needs it on the morning a record changed and nobody owns up.

Three workers run inside the API process. They start when the API starts, do one pass immediately, then repeat on their own interval, and stop when the API shuts down. All three are on by default — you do not schedule anything to get them.

Worker Runs every What it changes
SLA 60 seconds Flags cases approaching a target, marks the breach once a target passes, raises the escalation that follows from it
Escalation 30 seconds Chases unacknowledged escalations, advances or expires them
Approval timeout 60 seconds Reminds approvers, escalates a stalled request to the approver’s manager, expires it in the end

Before it does anything, each worker takes a named lock row in the database and gives it back at the end of the cycle. So running several API instances behind a load balancer does not double-process: one instance works the cycle, the others see the lock and skip. The SLA and escalation workers back themselves off after repeated failures rather than hammering the database, and the API carries on serving requests either way.

Each pass takes up to 500 running SLA timers — first-response and resolution, across every open case — and compares them to the clock.

  • Past the warning point: the timer goes to Warning, the case is flagged and its SLA state set to Warning, and the case owner gets a high-priority notification.
  • Past the target: the timer goes to Breached with the overrun recorded in minutes, the case’s breach flags are set (overall, plus first-response or resolution), the owner gets an urgent notification, and the worker raises an escalation on the case — applying whichever escalation rule matches, or a bare escalation if none does. Escalation stops at level 5, and a rule’s own cooldown and maximum are respected, so a long-running breach does not generate an escalation a minute.

Notifications are held to one per case per target per hour, so a breach does not turn into a stream of alerts.

Two kinds of case are invisible to this worker: anything Closed, Cancelled or Resolved, and any case whose clock is paused because it is Waiting for customer. Time spent waiting on the customer genuinely cannot breach.

On the case itself, the countdown on the SLA card is worked out fresh each time you open it. The red SLA Breached badge, the breach flags, the escalation entry on the timeline and the notification in your bell are the worker’s doing — which is why they can appear on a case nobody has touched.

Service & cases covers the policies, business hours and escalation rules this worker reads.

This one looks after escalations after they exist.

  • Ten minutes before an acknowledgement deadline, it reminds whoever the escalation is assigned to — once.
  • At the deadline, it either creates the next-level escalation and closes this one, or simply expires it, depending on the rule; the default is to escalate. Chains stop at level 5. Whoever failed to acknowledge is told.
  • Past its auto-expire time, an open or acknowledged escalation is closed as Expired.

The reminder and the deadline notice both need someone to send to, so an escalation raised with no assignee — which is what a breach with no matching escalation rule produces — still advances and expires on time, quietly.

Each pass handles up to 100 acknowledgement timeouts, 100 expirations and 50 reminders, so a backlog drains over a few cycles rather than in one.

Automation describes the policy — reminder, escalate to the manager, expire after three attempts. What matters here is the mechanics: it runs every 60 seconds, handles up to 100 timed-out requests and 50 reminders a pass, reminds the approver 24 hours before the deadline, and falls back to a seven-day deadline for older requests that carry no step deadline of their own. All three of those figures are configurable.

When a request expires, its decision history gains an entry recorded by System, and a quote sitting in Needs review is put back to Draft so it can be resubmitted. Both are quiet changes: an expired request drops off the Approvals list, which shows only what is still pending, and the quote’s own history says nothing about the reset. The submitter’s notification is the trail to follow in the app — that, and the request’s decision history if you read it back through the API.

The other three jobs are command-line programs. Nothing in the product starts them and no screen triggers them — if nobody has put them in a scheduler, they have never run.

Job How it is run Cadence it expects
Forecast snapshot npm run worker:forecast-snapshot One shot. Schedule once a day.
Quote expiry npm run worker:quote-expiry Loops every 5 minutes; -- --once for a single pass
Email ingestion (IMAP) node dist/workers/email-ingestion-processor.js Loops every 60 seconds

A run walks every unlocked fiscal period that covers today, sums each owner’s deals by forecast category — deals whose close date falls inside the period, with each deal’s exchange rate applied — and writes one row per owner per category, dated today. Locked periods are skipped.

Two consequences worth planning around. Out of the box there is no snapshot history, because nothing has run the job — the trend the forecast dashboard returns comes back empty. And nothing de-duplicates a day: run the job twice on the same date and you get two sets of rows for that date. Schedule it once, at a fixed time, and leave it. The job never revises a row it has already written — a snapshot is what the pipeline looked like that morning, which is the point of keeping it. A stored snapshot can still be corrected by hand through the API, with an adjusted amount and a note; see Reports & forecasting.

Reports & forecasting covers periods, quotas and what snapshots are for.

This worker is meant to poll support mailboxes over IMAP and turn new mail into cases. The fetch step is not implemented — it connects to nothing and returns no messages — so running it collects no mail, while still stamping each active IMAP mailbox as Connected with a fresh sync time. That combination is worse than not running it, so leave it unscheduled.

Inbound mail that does work arrives by webhook: your mail provider posts to CRM and the case is created or matched on the spot, with no worker involved. See Service & cases.

Everything below is an environment variable read at API startup, so a change needs a restart.

Setting Default Effect
WORKERS_ENABLED true Master switch for all three in-process workers
WORKER_SLA_ENABLED true The SLA worker
WORKER_ESCALATION_ENABLED true The escalation worker
WORKER_APPROVAL_TIMEOUT_ENABLED true The approval timeout worker
SLA_PROCESSOR_INTERVAL_MS 60000 SLA cadence
ESCALATION_PROCESSOR_INTERVAL_MS 30000 Escalation cadence
APPROVAL_TIMEOUT_PROCESSOR_INTERVAL_MS 60000 Approval timeout cadence
APPROVAL_DEFAULT_TIMEOUT_DAYS 7 Fallback deadline for requests with no step deadline
APPROVAL_REMINDER_HOURS_BEFORE 24 How far ahead of the deadline approvers are reminded
QUOTE_EXPIRY_INTERVAL_MS 300000 Quote expiry loop cadence
QUOTE_EXPIRY_BATCH_SIZE 100 Quotes considered per quote-expiry pass
EMAIL_INGESTION_INTERVAL_MS 60000 Email ingestion loop cadence

The sample environment file ships some of these commented out with different suggested numbers. The defaults above are what the code uses when the variable is unset, and a commented-out line is unset.

Switching a worker off switches off its behaviour, not just its schedule: no worker means no breach flags, no automatic escalation and no approval expiry — those things are only ever done here. If you would rather run them as separate processes — on their own box, or on a different cadence — set the matching WORKER_*_ENABLED to false so the API does not also run them, and start them with npm run worker:sla, npm run worker:escalation or npm run worker:approval-timeout. Each accepts -- --once to do a single pass and exit, which is the form to use from cron. The same database lock keeps a standalone worker and an API instance from treading on each other.

If your deployment links CRM to CPQ, the API also runs a writeback consumer that reads what CPQ publishes and files it on this side: recording which CPQ record an account, contact or deal is paired with (set once, never repointed), and mirroring CPQ’s quotes, orders, invoices and payments into CRM’s own lists so the account’s history is complete here too. It refuses anything from a tenant whose base currency, locale or time zone does not match on both sides, rather than mirroring figures that would not add up.

It is off unless enabled, needs Redis, and is silent when it cannot start — the API boots either way. If you are not running CPQ, ignore it.

Why something changed with nobody clicking

Section titled “Why something changed with nobody clicking”

Working backwards from a surprise, in the order that usually settles it:

What you are looking at Where the evidence is
Case suddenly shows SLA Breached The SLA card on the case, and the owner’s notification
Case escalated on its own The escalation panel on the case and the entry on its Timeline tab; the escalation dashboard for the wider picture
Escalation moved up a level or went Expired The escalation’s own history — the escalation worker acted on the deadline
Approval request gone from the Approvals list It hit its deadline and expired; the submitter has the notification, and the request’s decision history records the entry against System
Quote back in Draft with no edit behind it Its approval request expired; the submitter has the notification
Forecast trend has no history The snapshot job has never been scheduled
Quote past its validity date still Presented Expected — see the caution above

Every alert these workers raise lands in the in-app notification centre and nowhere else; see Notifications before you rely on someone hearing about a breach out of hours.

  • Service & cases — the SLA policies, business hours and escalation rules the first two workers act on.
  • Automation — the approval policy the timeout worker enforces.
  • Reports & forecasting — what forecast snapshots are for, and what is missing without them.
  • Sales — quote statuses and validity dates.
  • Notifications — where everything these jobs raise ends up.