Skip to main content
Every lifecycle moment that should reach a customer — a receipt, a renewal reminder, a dunning nudge, a recovery win — flows through one template system and one queue.

Defaults plus per-tenant overrides

Template content lives in @duro/email as a set of defaults (keyed receipt, renewal_reminder, dunning_1…4, payment_recovered, card_expiring, welcome, …), each a { subject, body, channel }. A merchant can override any of them, stored per-tenant in the core schema’s email_templates. The resolver is simple and predictable: Merge tags ({{store_name}}, {{amount}}, {{plan_name}}, {{invoice_id}}, {{update_link}}, {{next_attempt}}) are interpolated by an iterative renderer — no recursion, no template-injection surface. {{store_name}} is filled from the tenant automatically.

Wired into the lifecycle

The worker’s LifecycleMailer is the bridge between an event happening and an email being queued:
MomentTemplate
Successful renewalreceipt
First renewal failuredunning_1
Subsequent dunning retriesdunning_2…4 (by attempt)
Recovery succeededpayment_recovered
Renewing within the merchant’s windowrenewal_reminder
Register / KYC submittedwelcome / KYC templates (core-api)
Each is rendered with the right context and enqueued, not sent — the email worker drains the queue against SMTP. Verified end-to-end through real Redis: a successful renewal queues a templated receipt; a failed one queues a dunning reminder.

The Email tab in the builder

The same template content is what the appearance builder’s Email tab edits — subject, body, merge tags, live preview — and what the dunning settings’ email-sequence editor sequences. One template store, edited from the UI, rendered by the worker. No divergence between “what the merchant configured” and “what the customer receives.” Next: the appearance builder.