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.
What the API starts for you
Section titled “What the API starts for you”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.
The SLA worker
Section titled “The SLA worker”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.
The escalation worker
Section titled “The escalation worker”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.
The approval timeout worker
Section titled “The approval timeout worker”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.
What an operator has to schedule
Section titled “What an operator has to schedule”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 |
Forecast snapshots
Section titled “Forecast snapshots”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.
Email ingestion
Section titled “Email ingestion”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.
Turning them off, and tuning them
Section titled “Turning them off, and tuning them”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.
One more thing the API can start
Section titled “One more thing the API can start”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.
Connects to
Section titled “Connects to”- 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.