Skip to content

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-Unsubscribe and List-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 verificationMailerVerifyProfileDomain 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