Health & Troubleshooting
The email cycle
A background worker job drives email-to-ticket for both transports by calling /api/cron/email every 2 minutes. That endpoint does two things:
- Renews Graph mail subscriptions before they expire (push inboxes)
- Polls every IMAP-mode inbox (
imap_oauthandimap_basic)
So the practical latency is: seconds for graph_app, up to ~2 minutes for the polled modes.
:::caution The cycle needs a secret
The endpoint refuses unauthenticated triggers. The worker sends CRON_SECRET, falling back to GRAPH_WEBHOOK_SECRET. If neither is set the job skips entirely and logs a warning — mail will not flow, and polled inboxes will report "Not yet polled" forever. See Environment Variables.
:::
This job replaced a daily webhook-renewal job. Renewal only acts inside a 36-hour window, so running it on the short cycle costs almost nothing and removes the up-to-24-hour wait a push inbox used to have before its subscription was even checked.
Polling is safe to run concurrently with itself: worst case two cycles fetch the same unread message, and the second ingestion de-duplicates on Message-ID.
Health signals on an inbox
| Signal | Meaning |
|---|---|
| Subscription expires | Push inboxes — when the Graph subscription lapses if not renewed |
| Last polled | Polled inboxes — the last cycle that reached the mailbox |
| Connected account | Which account the delegated sign-in actually authenticated as |
| Last error | The most recent failure message |
| Consecutive failures | Resets to zero on any cycle that gets real work done |
A cycle that ingests successfully and then fails later still records its progress. Without that, a late error hid earlier success — the inbox reported "Not yet polled" and climbed towards a tripped circuit while it was, in fact, creating tickets.
The circuit breaker
After 5 consecutive failures an inbox trips: circuitOpen is set, polling stops, and that tenant's admins are notified in-app naming the failing mailbox. Notification is always scoped to the inbox's own tenant — never a platform-wide broadcast, and never a vendor who happens to be an admin elsewhere.
To recover: fix the underlying problem, then re-enable the inbox from Admin → Email. Saving IMAP credentials also clears the breaker and resets the failure count.
Common failures
Every Graph call returns 403
The platform app registration is missing Mail.ReadWrite / Mail.Send application permissions, or admin consent was never granted. See Azure App Registration.
IMAP against Microsoft 365 won't authenticate
Expected — basic auth for IMAP and SMTP AUTH are retired in Exchange Online. Switch the inbox to imap_oauth.
"Not yet polled" forever
Either the worker isn't running, or CRON_SECRET/GRAPH_WEBHOOK_SECRET isn't set so the cycle is skipping. Check the worker logs for [email-cycle]. A 401 in those logs means the worker's secret disagrees with the web app's.
Mail arrives but no ticket appears
Work down the pipeline:
- Filtered? The sender may look like a no-reply or a bounce. Check Ignore no-reply senders.
- Threaded instead of created? If the message referenced an existing thread it became a comment on that ticket, not a new one.
- Duplicate? Already ingested under the same
Message-ID. - Routed somewhere unexpected? Use Simulate on the inbox's routing rules with the real sender and recipient.
Recent activity on the inbox shows what Ticku did with each recent message, which usually answers this in one look.
The connected account is wrong
The admin authenticated as themselves rather than as the mailbox. Disconnect and reconnect, checking the account chooser. Ticku records the signed-in account separately from the mailbox address precisely so this is visible.
Tickets land in the default project instead of the routed one
Either no rule matched, or a higher-precedence rule won. Remember recipient rules read the To header only — if your support address was CC'd rather than in To, no recipient rule fires. Simulate with the exact addresses to confirm.