Skip to main content

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_oauth and imap_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

SignalMeaning
Subscription expiresPush inboxes — when the Graph subscription lapses if not renewed
Last polledPolled inboxes — the last cycle that reached the mailbox
Connected accountWhich account the delegated sign-in actually authenticated as
Last errorThe most recent failure message
Consecutive failuresResets 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:

  1. Filtered? The sender may look like a no-reply or a bounce. Check Ignore no-reply senders.
  2. Threaded instead of created? If the message referenced an existing thread it became a comment on that ticket, not a new one.
  3. Duplicate? Already ingested under the same Message-ID.
  4. 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.