Communication — Common cases
"A new notification creates a job but does not send"
This is usually caused by Notification Settings at the Scheduler step. Check in order:
- Has the notification type been seeded into
NotificationOptionfor the BU? (A new type must be seeded before it can be sent.) - Does the BU have a subscriber/recipient? If not, the handler skips gracefully.
- Does Generator have a handler for the new type? A new feature may not have wired the route yet.
Real-world example: A new notification type is enabled for a BU, jobs are created, but customers receive nothing. → The bottleneck is Scheduler/Generator. → The new type is not seeded into
NotificationOptionfor the BU → seed it before sending works.
Add a placeholder to a template
Templates use {{...}}. For example, booking-notification-checkout-instruction adds a checkout-instruction placeholder. Process:
- Add the placeholder to the template.
- Ensure the Generator handler provides a value for that placeholder when rendering.
- Missing placeholder data → renders empty (check fallback).
Real example:
{{INCIDENT_DETAILS_LINK}}points to/attendance/incident-management(there is no deep-link to a specific incident yet — switch to a deep-link when the route exists).
Turn off/reduce a notification type
Some situations need notification suppress:
| Case | How |
|---|---|
| Do not send notification for pending booking | Mute in the pending condition |
| Turn off booking notification by subscription config | Subscription setting |
Mute/suppress is notification-only: it does not change booking/enrollment/invoice state (see the "alert mute is notification-only" principle in Subscription).
Configure default recipients for a site
- Seed the default recipient email for the site.
- The optional "Send copy to site contact email" shows only for site-level options (
NotificationOption.LevelTypeId = Org);BusinessUnit/Enterpriselevels (e.g. "Business Unit Setting updated") → the option is hidden in the editor,SendToSiteContactEmail = trueis never persisted (the request sendstrue, it is stored asfalse; channel flag, template, subscriber, sender stay unchanged). - When generating a site-level notification: setting enabled +
OrgSettings.EmailReplyTonon-empty → the generator adds the site contact email as a recipient (does not replace subscribers); disabled/empty email → not added. Applies to every site-level handler that builds the recipient/subscriber list and to direct send paths (not through the generator). - Scope beats stored flag at send time: non-site-level never receives a copy even if the flag is still
true; no migration needed. - Account-facing handlers (single recipient — booking/quote/subscription,
CancelBooking,Invoice, NCS attendance) use the site contact only as a template variable, outside this copy rule.
Real-world example: An admin enables copy for
IncidentSubmitted(site-level), leaving the setting enabled on the BU option "Business Unit Setting updated". → The generator creates the notification. → TheIncidentSubmittedone adds the site contact email; the BU one has no copy because scope takes precedence.
"Customer receives duplicate email" (send side)
Different from duplicates in Platform (duplicate job creation), this is duplication during send. Prevent it with a send claim:
Created/RetryReady -> Sending (conditional update)If duplication still happens, check (1) OutgoingMessage is truly "one row/recipient/channel" (RecipientKeyHash stable?), (2) send claim is conditional on Status, and (3) AttemptNo is not accidentally included.
Real-world example: A parent receives two identical booking-confirmation emails. → Send once per person/channel. → Send claim moves
Created → Sendingconditionally on Status → the second worker cannot claim it again.
Campaign to many recipients: large payload
Use MessagingJob + Blob. Notes:
- Large content stays in Blob (
messaging-jobcontainer); SQL only keeps orchestration + blob name. - Retry reuses compatible blob payload; mismatched payload → stop + report conflict (do not overwrite).
Safe report-subscription cutover
Report subscription is a thin path with:
- Shadow mode + allowlist + one-owner guard so old and new schedulers never send the same report period together.
- Step-by-step cutover with rollback. Details: Report Subscription.
"Job was skipped/deactivated — why did it not send?"
When automation is skipped/deactivated, there is no delivery. The reason is in AutomationScheduler.DispatchResultJson (SkippedReasonCode/ConflictReasonCode/Summary) and support screens — not in the send pipeline.
Real-world example: Support is asked why a schedule reminder did not send. → Look in the right place instead of digging through the pipeline. →
DispatchResultJsonshowsSkippedReasonCode→ skipped intentionally, not send-failed.