Mailer Module¶
The mailer module sends email from the platform: confirmation codes, statements, alerts and marketing campaigns. It assembles the message, queues it, picks a provider, and then tracks what happened — delivered, bounced, complained, opened, clicked.
Everything in this module exists to protect one thing: the reputation of the sending domain. A domain that starts landing in spam takes the login codes down with the promotions, so bounces, complaints, unsubscribes and sender-domain verification are first-class features rather than afterthoughts.
What it covers¶
- Providers — 9 integrations: SMTP plus API providers, each an adapter with its own credentials schema and capability set.
- Queue — FIFO with attempts, exponential backoff, stuck recovery and expiry.
- Routing — the provider is chosen by recipient domain and message class, with failover to backup profiles and per-profile rate and daily limits.
- Content assembly — safe
{{placeholder}}substitution with HTML escaping, a builder tree serialized through a tag allow list, and a generated plain-text part. - Templates and triggers — a platform event renders a template and queues the email without a caller.
- Campaigns — a managed rollout over a CRM segment, with a cursor, resume and dedupe.
- Suppression list — hard bounces, blocks, complaints and unsubscribes are never emailed again.
- One-click unsubscribe — signed token,
List-UnsubscribeandList-Unsubscribe-Post. - Reputation guard — a profile is paused automatically when its bounce or complaint rate crosses the threshold.
- Domain verification — DKIM and SPF state of the sender domain, asked of the provider.
- Pre-send lint — Gmail clipping, image-only bodies, missing alt text, broken links.
Methods¶
Names in the table are the TCP command names; the REST column is the HTTP entry point of the same method.
Emails¶
| Method | REST | Description |
|---|---|---|
| MailerSendEmail | POST mailer/email |
Queue one email |
| MailerSendBulk | POST mailer/email/bulk |
Queue up to 10 000 recipients |
| MailerResendEmail | POST mailer/email/resend |
Send a copy of an existing email |
| MailerCancelEmail | POST mailer/email/cancel |
Cancel an email still in the queue |
| MailerPreviewEmail | POST mailer/email/preview |
Assemble the message without sending |
| MailerLintEmail | POST mailer/email/lint |
Pre-send warnings |
| MailerSendTestEmail | POST mailer/email/test |
Send a test to your own address |
| MailerFindEmail | GET mailer/email/find |
Find emails by address, provider id or campaign |
| MailerGetEmail | GET mailer/email/message/:id |
One email with attempts and events |
| MailerGetEmails | GET mailer/emails/list |
Email history with filters and aggregates |
| MailerGetStats | GET mailer/email/stats |
Delivery, bounce, complaint, open and click rates |
| MailerGetLastEmailsByParents | — | Last email time for a batch of recipients |
Campaigns¶
| Method | REST | Description |
|---|---|---|
| MailerAddCampaign | POST mailer/campaign |
Create a campaign over a CRM segment |
| MailerUpdateCampaign | PUT mailer/campaign |
Update a campaign |
| MailerCancelCampaign | POST mailer/campaign/cancel |
Stop a campaign and its queued emails |
| MailerResumeCampaign | POST mailer/campaign/resume |
Continue an interrupted campaign |
| MailerEstimateCampaignRecipients | POST mailer/campaign/estimate-recipients |
Count recipients before the rollout |
| MailerDeleteCampaign | DELETE mailer/campaign |
Delete a campaign |
| MailerGetCampaigns | GET mailer/campaigns/list |
Campaigns with rollout progress |
Templates and triggers¶
| Method | REST | Description |
|---|---|---|
| MailerAddTemplate | POST mailer/template |
Create a template |
| MailerUpdateTemplate | PUT mailer/template |
Update a template |
| MailerCloneTemplate | POST mailer/template/clone |
Copy a template, for another language |
| MailerDeleteTemplate | DELETE mailer/template |
Delete a template |
| MailerGetTemplates | GET mailer/templates/list |
Templates of the brand |
| MailerAddTrigger | POST mailer/trigger |
Subscribe an event to templates |
| MailerUpdateTrigger | PUT mailer/trigger |
Update a trigger |
| MailerDeleteTrigger | DELETE mailer/trigger |
Delete a trigger |
| MailerFireTrigger | POST mailer/trigger/fire |
Fire an event by hand |
| MailerGetTriggers | GET mailer/triggers/list |
Triggers of the brand |
| MailerGetTriggerSubscriptions | GET mailer/triggers/subscriptions |
What the module listens to |
Providers, profiles and agents¶
| Method | REST | Description |
|---|---|---|
| MailerGetProviderAdapters | GET mailer/providers/adapters |
Implemented adapters and capabilities |
| MailerAddProvider | POST mailer/provider |
Register a provider |
| MailerUpdateProvider | PUT mailer/provider |
Update a provider |
| MailerDeleteProvider | DELETE mailer/provider |
Soft-delete a provider |
| MailerGetProviders | GET mailer/providers/list |
Provider catalogue |
| MailerAddProfile | POST mailer/providerProfile |
Create a provider profile |
| MailerUpdateProfile | PUT mailer/providerProfile |
Update a provider profile |
| MailerDeleteProfile | DELETE mailer/providerProfile |
Soft-delete a provider profile |
| MailerGetProfiles | GET mailer/providerProfiles/list |
Profiles with domain and pause state |
| MailerGetMyProfiles | GET mailer/providerProfile/available/me |
Profiles and templates for the current manager |
| MailerGetProfileBalance | GET mailer/providerProfiles/balance |
Provider quota with low-balance flags |
| MailerVerifyProfileDomain | POST mailer/providerProfile/verify-domain |
DKIM and SPF state of the sender domain |
| MailerAddAgent | POST mailer/agent |
Link a manager to a sender address |
| MailerUpdateAgent | PUT mailer/agent |
Update the link |
| MailerDeleteAgent | DELETE mailer/agent |
Remove the link |
| MailerGetAgents | GET mailer/agents/list |
Links of the brand |
Suppressions and unsubscribe¶
| Method | REST | Description |
|---|---|---|
| MailerAddSuppression | POST mailer/suppression |
Suppress an address |
| MailerImportSuppressions | POST mailer/suppression/import |
Import up to 50 000 addresses |
| MailerDeleteSuppression | DELETE mailer/suppression |
Remove a suppression |
| MailerCheckSuppression | GET mailer/suppression/check |
Check an address before sending |
| MailerGetSuppressions | GET mailer/suppressions/list |
Suppression entries |
| MailerHandleUnsubscribe | POST mailer/unsubscribe |
Public one-click unsubscribe |
Send windows, webhooks and service¶
| Method | REST | Description |
|---|---|---|
| MailerAddSendWindow | POST mailer/sendWindow |
Allowed sending hours for a country |
| MailerUpdateSendWindow | PUT mailer/sendWindow |
Update a send window |
| MailerDeleteSendWindow | DELETE mailer/sendWindow |
Delete a send window |
| MailerCheckSendWindow | GET mailer/sendWindow/check |
Whether a country can be emailed now |
| MailerGetSendWindows | GET mailer/sendWindows/list |
Send windows |
| MailerAddWebhook | POST mailer/webhook |
Create a provider event URL |
| MailerUpdateWebhook | PUT mailer/webhook |
Update a webhook |
| MailerDeleteWebhook | DELETE mailer/webhook |
Delete a webhook |
| MailerGetWebhooks | GET mailer/webhooks/list |
Webhooks of the brand |
| MailerGetWebhookEvents | GET mailer/webhookEvents/list |
Raw provider events |
| MailerHandleWebhook | POST mailer/handleWebhooks/:uuid |
Public endpoint the provider calls |
| MailerPing | GET mailer/ping |
Cheap liveness probe |
| MailerHealth | GET mailer/health |
Full module state |
Entities¶
| Entity | Scope | Purpose |
|---|---|---|
providers |
global | Email integration catalogue; name is the adapter name |
providerProfiles |
brand | Credentials, sender identity, tracking flags, limits, pause state, domain status |
agents |
brand | Manager-to-sender-address link, used by INDIVIDUAL profiles |
triggers |
brand or global | Platform event to template subscription |
templates |
brand | Subject, HTML, text part, builder tree, language, message class |
campaigns |
brand | Managed rollout: recipient set, schedule, counters, cursor |
emails |
brand | Emails, queue and state: status, kind, attempts, bounceType, openCount, clickCount |
webhooks |
brand | Public provider event URLs and their secrets |
webhookEvents |
via webhooks |
Raw provider payloads and deduplication |
suppressions |
brand | Addresses that must not be emailed: bounces, complaints, unsubscribes, manual blocks |
sendWindows |
brand | Allowed sending hours per country (off by default) |
Everything is scoped by brand. Desks are not part of the model: a recipient address,
a template and a provider profile belong to a brand, not to a department.
Email statuses¶
| Status | Meaning |
|---|---|
PENDING |
In the queue, not sent yet |
IN_PROGRESS |
The queue is handing it to the provider right now |
SENT |
Provider accepted it; the receiving mail server has not answered |
DELIVERED |
Receiving server accepted the message |
BOUNCED |
Delivery failed; bounceType says whether it was hard, soft or a block |
COMPLAINED |
Recipient marked it as spam |
REJECTED |
Provider refused the message |
EXPIRED |
No final event arrived within the allowed window |
FAILED |
Sending failed and no attempts are left |
SENT is not final: the provider accepting a message and the recipient's server accepting it
are different events. The only transition allowed out of a final status is
DELIVERED → COMPLAINED — a spam complaint arrives after delivery, sometimes hours later, and
it is the most damaging signal a sender can get, so it is never dropped.
Opens and clicks are not statuses. They live in their own columns (openedAt,
openCount, clickedAt, clickCount, lastClickedUrl): otherwise one open would overwrite
the fact of delivery and a second one would overwrite a complaint.
Email class¶
Every email has a kind: TRANSACTIONAL or MARKETING (the default, and the restrictive
one).
TRANSACTIONAL |
MARKETING |
|
|---|---|---|
| Send window | ignored | applied |
| Unsubscribe, spam complaint | do not block | block |
| Hard bounce, block, manual entry | block | block |
| Unsubscribe link and headers | not added | always added |
A confirmation code must arrive at 3 a.m., and a customer who unsubscribed from promotions has not refused their login codes. Profiles can be restricted to one class too, so marketing does not burn the reputation of the transactional domain.
Sending pipeline¶
sequenceDiagram
participant CRM
participant Module as Mailer
participant Queue
participant Provider
CRM->>Module: MailerSendEmail
Module->>Module: address, suppression, duplicate
Module->>Module: assemble content, text part, unsubscribe
Module->>Module: size, attachments, send window
Module->>Queue: PENDING
Queue->>Provider: send (attempt N)
Provider-->>Queue: provider message id → SENT
Provider-->>Module: delivery, bounce, complaint, open, click
Module->>Module: status, suppression, counters
Module-->>CRM: mailer.email.final
Checks that can reject an email for free run before the provider is contacted: address validity, suppression, duplicate window, assembled size, attachment limits, recipient domain, send window. Everything after queuing — attempts, backoff, failover, rate limits — happens without the caller waiting.
Protecting the domain¶
Three mechanisms work together, and each one is documented on its own method page:
- Suppression list — a bounce or complaint from a provider webhook lands there automatically, so the same dead address is never emailed twice.
- Reputation guard — a cron computes the bounce and complaint rates per profile over a rolling window and disables a profile that crosses the threshold (defaults: 5% bounces, 0.1% complaints, with a minimum sample). The profile stops taking part in routing until an administrator enables it again.
- Domain verification — MailerVerifyProfileDomain asks the provider about DKIM and SPF, because an unverified sender domain is invisible from the sending side: the provider reports success while mailbox providers file the mail as spam.
Events¶
| Event | When |
|---|---|
mailer.email.queued |
Email accepted into the queue |
mailer.email.sent |
Provider accepted the email |
mailer.email.final |
Delivered, bounced, complained, rejected, expired or failed |
mailer.email.engagement |
Open or click recorded |
mailer.recipient.unsubscribed |
Recipient unsubscribed |
mailer.profile.paused |
Profile paused by the reputation guard |
mailer.statusChanged |
Notification for the manager UI |