Skip to content

Notifications catalog ​

Which way each email goes ​

Agreed with the PM on 26 Oct 2026. Every email the platform sends goes one of two ways, chosen by whether the person is expected to reply:

WayForExamples
The mail library (Nodemailer, createEmailSender() in @breezycorp/notifications, from the system address SMTP_FROM)Emails to staff, and setup and security emails to clients: nobody replies to themStaff invitations, password resets, MFA and new-device notices, payroll staff alerts; contact added or changed, a chat linked to a contact; the payroll link's sign-in and approval codes and its new-sign-in notice
Chatwoot's payroll mailbox (deliverToPayrollRecipient in @breezycorp/agent-runtime, the mailbox chosen in Settings › Integrations)A module's working emails to the client: the client answers them, and Chatwoot ties the answer to the right company and monthThe month's request and reminders, questions, the approval request and its reminders, "done" and "cancelled"; the payroll link email (onboarding, resend, and the client asking from an old link)

Why the split: a reply to a Chatwoot email lands on that company's thread (and the month's, for a month email), where staff and the message pipeline see it. A reply to a mail-library email goes to the system address, where nobody reads it.

When adding an email:

  • If the client may reply, send it through Chatwoot. When the payroll mailbox cannot send, the email is not sent another way: staff are told to fix the mailbox (for the link email, before anything is queued; afterwards, by a payroll staff alert).
  • If nobody replies (a code, a notice, a staff email), use the mail library.

The Infotech file is ready (1 Nov 2026): right after the export is made (the Lead's final approval, or a regenerate), the month's stage-owner bell goes at once instead of at the 02:00 sweep, worded for the hand-off: "The Infotech file is ready: download it and key it into Infotech, then upload Infotech's report." Same recipients as the sweep (the month's preparer if an active Executive, else every Executive) and the same once-per-wait rule.

Who gets a payroll staff alert (queuePayrollStaffAlert, pay-staff-alert.ts): a bell and one email each. Alerts for "the Leads" (a month that could not open, a month opened while the one before is not finished, Lead-only warnings) go to every active Payroll Lead; team alerts (a month opened late, a deadline at risk) also go to the company's assigned executive. When the firm has no active Payroll Lead, the Admins and Platform Admins get the Leads' alerts instead, and their email says why: "This firm has no active Payroll Lead, so alerts meant for the Payroll Leads go to its Admins and Platform Admins. Make someone a Payroll Lead to have them go there instead." (stage review S1-09, 28 Oct 2026). Before, nobody got them and the audit row said FAILURE.

Bookkeeping's client emails (the intake invitation and the reconciliation link) still go by the mail library; by this rule they belong in Chatwoot, which is for the bookkeeping owners to move.

Templates ​

Curated index of every email template the platform sends. The list is loaded from packages/notifications/src/templates/ at build time.

NOTE

Payroll messages to the client are not templates here. The month's request, reminders, "we have everything", questions, the approval request and its reminders, "done" and "cancelled" go through Chatwoot: by email to each person on their thread for the month, sent from the payroll mailbox chosen in Settings › Integrations, or their own chat when they have no email address. Each person gets their own copy (emails greet them by name and end with the firm's sign-off from Settings → General, "Payroll team" until set). What went for a month is listed by GET /ops/cycles/:cycleId/client-notices; one that could not go leaves a staff task on the month. See Timing reference and The client says they didn't receive the email.

TemplateProductRecipientPurposeTrigger
reminderSharedEitherGeneric reminder for an outstanding actionsend-reminder worker job
bookkeeping-batch-readyBookkeepingStaff userA journal batch is ready for senior accountant reviewclassify-and-draft worker job at batch boundary
bookkeeping-upload-fileBookkeepingStaff userUpload file is ready to download and post into the accounting platformgenerate-upload-file worker job
reconciliation-magic-linkBookkeepingClient contactMagic link for client to respond to unreconciled itemsreconciliation dispatch action
reconciliation-client-responseBookkeepingStaff userNotify staff that a client has responded in the portalprocess-client-response worker job
reconciliation-completeBookkeepingStaff userFinal reconciliation report is readygenerate-reconciliation-report worker job

How to debug a missing email ​

  1. Open the Outbox page (/dashboard/outbox) and find the related event.
  2. If the event isn't there, the trigger never fired — check the Audit tab for the cycle / batch / run.
  3. If the event is there but the email didn't land, check the SMTP adapter (in dev: Mailpit at localhost:8025).
  4. For magic-link emails, the recipient sees a 48h-expiring URL. Resending re-mints a new token.

See also Outbox stuck runbook.

Internal use only — BreezyCorp