Migration to high deliverability emails

Bring campaigns, OTP, and transactional mail up to current inbox hygiene — visible branding, a why-you-receive line, native double opt-in, and workflows that wait for confirmation.

Inbox placement is no longer "SPF + DKIM and hope". Gmail and Yahoo expect a recognizable sender and a sentence that explains why this specific person got this specific email — including one-time passwords and receipts. Lumail's editor scores those two things. This page is the migration path.

If you only need the confirmation switch, use Enable Double Opt-In. Come back here for branding, the why-line, OTP/transactional copy, and workflow timing.

What "migrated" looks like

Every email that leaves the organization — campaign, workflow, confirmation, OTP, receipt — has:

  1. Visible branding in the first screen of the body (logo, organization name, or your domain as its own first line).
  2. A why-you-receive sentence in the same language as the email. Not the unsubscribe line.
  3. A real postal address ({{org-adresse}} / @org-adresse) and an unsubscribe link on marketing mail.
  4. Native double opt-in on public signups, with a branded confirmation email.
  5. Workflows that enroll SUBSCRIBED contacts, not pending ones.

The content checker awards 5 points for branding and 10 points for the why-line. The default "Don't want to receive these emails?" footer does not count as a why-line. See Content Deliverability Checker.

1. Add clear branding

Put the brand where a human sees it without scrolling. The checker accepts any of:

  • A logo block with a real image URL (filename-only or a hidden link does not count)
  • The organization name, including the {{org-name}} / @org-name variable
  • Your domain alone as the first text block (melvynx.com)

Snippets are checked too. Put the header (logo + name) in a snippet and reuse it in campaigns, workflow emails, and the confirmation template so the brand cannot drift.

Do this on OTP and transactional HTML as well. A bare "Your code is 482191" from noreply@ with no logo and no product name looks like phishing. Same sender display name, same logo, same domain in the first lines.

2. Say why they received this email

Gmail/Yahoo want a causal sentence. Language does not matter. Shape does.

ChannelLine that passes
Newsletter / campaignYou're receiving this email because you signed up at example.com
French listTu reçois ce mail car tu t'es inscrit sur example.com
ConfirmationYou're receiving this because you just signed up and need to confirm your email
OTP / magic linkYou're receiving this because you requested a sign-in code for example.com
Receipt / invoiceYou're receiving this because you placed an order on example.com
Password resetYou're receiving this because a password reset was requested for your example.com account

Write it in the email language. Put it near the footer, not only in preview text.

Does not count:

  • Don't want to receive these emails?
  • Unsubscribe alone
  • A legal address with no causal phrase

Agents writing campaign/workflow bodies must add this sentence and visible branding. Call get_skill({ type: "docs", page: "tutorials/migration-to-high-deliverability-emails" }) before rewriting a migrated list.

Transactional and SMTP sends (html, markdown, React Email) are not scored by the editor. Add the two lines yourself. Same rule as campaigns.

3. Turn on native double opt-in and brand the confirmation

Public capture pages, forms, and API signups should confirm the inbox before marketing starts.

UI: Settings → Configuration → Double opt-inEdit confirmation email.

Agent (admin/owner):

get_organization_settings
update_organization_settings({ doubleOptInEnabled: true })
get_confirmation_email
update_confirmation_email({ subject, preview, template })

The confirmation body must include {{confirmUrl}} (button url or link href). Missing it is rejected. Use the same logo/name snippet and a why-line. Subject/preview should name the brand (Confirm your Acme subscription), not a generic "Confirm your email address" if you can do better.

What the switch does, and what it does not:

  • New public signups become PENDING_CONFIRMATION and get one confirmation email (24h token).
  • Existing SUBSCRIBED contacts stay subscribed. Enabling DOI is not a retroactive purge.
  • Confirmation emails send from the organization's default sender.
  • skipDoubleOptIn: true is for trusted backends (checkout, server sync) only. Never on a public form.
  • triggerWorkflows: false skips the confirmation email. It is not a substitute for skipDoubleOptIn.
  • Do not build a custom confirmation state machine with the transactional API.

Full source table: Enable Double Opt-In.

4. Set workflows up for confirmation

With DOI on, tag-triggered and "subscriber created" workflows wait until the click. Tags attached at signup are replayed after confirm so the welcome sequence enrolls a real SUBSCRIBED contact.

Do this:

  1. get_organization_settings — if DOI is off and the list is public, turn it on first.
  2. get_skill({ type: "workflow" }) then get_workflow (keep updatedAt).
  3. configure_workflow_draft with the full graph. EMAIL steps need complete inline content: branding, why-line, {{unsubscribeUrl}}, {{org-adresse}}.
  4. Do not publish until the user confirms. publish_workflow is dangerous.

Do not:

  • Enroll PENDING_CONFIRMATION contacts as if they were live.
  • Put skipDoubleOptIn on a capture-page or client-side signup.
  • Publish subscriber.tag.added with empty tagIds.
  • Call create_campaign for a workflow email.

Manual enrollment (add_subscribers_to_workflow) still requires a subscribed contact. dryRun: true first.

5. Other current recommendations

These are the other recent product rules that belong in the same migration, not a second project:

ItemDo
Physical address@org-adresse in the footer. CAN-SPAM / inbox filters.
Unsubscribe{{unsubscribeUrl}}. Type / → Unsubscribe Link. Translate the label if the email is not English.
No shortenersbit.ly, t.co, and friends fail the checker. Use your own domain. Lumail already tracks clicks.
Click domainSettings → Click domain. Dedicated host for /r/<id> tracked links, with DNS verification. Shared tracking is the fallback.
Sending subdomainPrefer mail.example.com over the root. Isolate reputation.
Content checkerFix the eleven checks before send. Unspam rewrites trigger words.
Transactional listKeep Add transactional recipients to the marketing list off unless you have consent. OTP/receipt recipients must not become SUBSCRIBED by accident.
Trusted importsCSV import writes SUBSCRIBED and does not send confirmation. Clean the file first.
Footer snippetOne branded footer (why-line + address + unsubscribe) reused everywhere. Settings → Language does not rewrite snippet copy.

DNS, bounces, and DMARC stay on Deliverability and Email Domains. This page is content and consent, not SPF.

Agent sequence

For a migration request ("make our emails high deliverability"):

  1. get_skill({ type: "docs", page: "tutorials/migration-to-high-deliverability-emails" })
  2. get_writing_style then get_email_snippets — reuse the header/footer if they exist, otherwise create them.
  3. get_organization_settings → enable DOI if public signups are on.
  4. Brand update_confirmation_email (must keep {{confirmUrl}}).
  5. Rewrite campaign/workflow bodies with branding + why-line. get_skill({ type: "campaign" }) / get_skill({ type: "workflow" }) first.
  6. For OTP/transactional templates in the customer's app, give them the two sentences to paste. send_email does not invent them.

Members can read settings and the confirmation template. Only owners/admins can flip DOI or save the confirmation email.