Custom domains
Every identity starts on the platform domain, for example bookings.brightwell@agents.example. A tenant
can move its identities to its own domain, such as bookings@brightwell.example or
bookings@agents.brightwell.example, so its mail carries its own brand and builds its own sending
reputation. History and threads move with the identity, and a domain change can be rolled back.
Your domain does not have to be on Cloudflare. It can stay with your registrar, Google, Microsoft, Route 53 or a web host, and keep the mailboxes it already has. Only the deployment’s platform domain must be on Cloudflare.
This guide helps you choose how to connect your domain, add it, move identities onto it, keep it healthy and fix it when something breaks.
Choose how to connect your domain
You pick a connection method when you add the domain. It decides what you change at your DNS host and how mail reaches and leaves your agents.
| Your situation | Method | What you change |
|---|---|---|
| The domain is already on Cloudflare, in the same account as the deployment | cloudflare_zone | Nothing: Pylota Mail writes the records |
You have a new domain just for agents, such as brightwell-agents.example | nameservers | Two NS records at your registrar |
You want agents on a subdomain of your main domain, such as agents.brightwell.example. The main domain stays at its DNS host and keeps its mail | dns_records | One MX, three DKIM CNAMEs, two MAIL FROM records and an ownership TXT, at your DNS host |
You want agents to answer as your existing addresses (bookings@brightwell.example), and Google Workspace or Microsoft 365 stays your mail system | send_only (your mailbox forwards to the agent), or smtp_relay (the agent sends through your provider) | send_only: three DKIM CNAMEs, two MAIL FROM records and an ownership TXT, plus a forwarding rule per address. smtp_relay: an ownership TXT, plus SMTP credentials |
| Your deployment runs on a Cloudflare Enterprise account and you want the simplest set-up for a subdomain | delegated_subdomain | NS records for the subdomain, at your DNS host |
| You are trying things out | Stay on the platform domain | Nothing |
How the methods compare:
| Method | Mail to your agents arrives through | Mail from your agents is sent by | Addresses on the domain |
|---|---|---|---|
cloudflare_zone | Cloudflare Email Routing | Cloudflare Email Sending | Any on an apex; at most 200 on a subdomain |
nameservers | Cloudflare Email Routing | Cloudflare Email Sending | Any |
dns_records | Amazon SES | Amazon SES | Any |
send_only | Your mailbox, which forwards each message | Amazon SES | Any; each needs a forwarding rule |
smtp_relay | Your mailbox (forwarding), or Amazon SES | Your own provider, over SMTP | Any |
delegated_subdomain | Cloudflare Email Routing | Cloudflare Email Sending | Any |
Not every deployment offers every method. The methods that use Amazon SES need the operator to have
connected SES (Deploy to Cloudflare › Connect Amazon SES).
delegated_subdomain needs a Cloudflare Enterprise account and the operator’s opt-in, and a tenant key
may use nameservers only when the operator allows it. When a method is not available, adding a domain
with it fails with 422 transport_unavailable, and details.reason says why. In v1.0, dns_records,
smtp_relay and delegated_subdomain depend on build-time spikes (S11, S12 and S10).
A Cloudflare zone can have at most 30 mail domains (routing and sending together, including the apex).
Before you start
- You need a key with
domains:write(tenant, partner or platform) to add a domain, andidentities:writeto add and promote addresses. cloudflare_zone,nameserversanddelegated_subdomainwork through the Cloudflare API, so the deployment needsPM_CF_API_TOKEN, with the permissions in Deploy to Cloudflare › Create a Cloudflare API token. Without it, the request fails with422 cf_token_required. The one exception: an operator can add acloudflare_zoneapex withpmail domains add --local-token, which then uses their localCLOUDFLARE_API_TOKEN(CLI › Commands that use your Cloudflare token). Subdomains,nameserversanddelegated_subdomainalways need the token on the deployment.dns_records,send_onlyandsmtp_relayneed no Cloudflare token.- With a tenant or partner key,
cloudflare_zone(andreplace_mxwith it) works only on a zone that this deployment created for your workspace withnameserversordelegated_subdomain, or one the operator assigned to it (tenant policydomains.cloudflare_zones, which only a platform key sets). An assigned zone allows names under it, such asmail.example.comunderexample.com, but not the zone’s apex itself, so the apex’s own mail (its MX records) stays as it is. Another workspace’s zone and the zone of the deployment’s own hosts are refused with403 scope_deniedanddetails.reason: "zone_not_allowed", fornameserversanddelegated_subdomaintoo (Identities and domains › Zone permission). - The records you publish are always read from the provider when you ask for them. Never copy records from this page or anywhere else (FR-DOM-3).
name and host: which one your DNS host wants
Every record has two forms of its name:
| Field | Example | Use it when |
|---|---|---|
name | pm-bounce.agents.brightwell.example | Your DNS host asks for the full name |
host | pm-bounce.agents | Your DNS host adds brightwell.example itself, as many do |
If you paste the full name into a form that adds your domain, the record ends up at
pm-bounce.agents.brightwell.example.brightwell.example. The health check notices this and reports
record_doubled_name with a fix that tells you to enter the host value only.
Add a domain
Every method follows the same four steps: add, publish, verify, move identities.
-
Add the domain.
pmail domains add agents.brightwell.example --method dns_records --tenant brightwellor with the API:
curl -X POST https://mail.example.com/v1/tenants/ten_01J9…/domains \ -H "Authorization: Bearer $PYLOTA_MAIL_KEY" -H "Content-Type: application/json" \ -d '{"name":"agents.brightwell.example","method":"dns_records"}'The domain is created in state
pending. The sections below list what each method checks and what it asks you to publish. -
Publish the records, if the method needs any:
pmail domains records dom_01JA…{ "data": [ { "type": "TXT", "name": "_pylota-mail.agents.brightwell.example", "host": "_pylota-mail.agents", "value": "pm-verify=8f2k…", "purpose": "ownership", "required": true, "status": "ok", "observed": ["pm-verify=8f2k…"] }, { "type": "MX", "name": "agents.brightwell.example", "host": "agents", "value": "inbound-smtp.eu-west-2.amazonaws.com", "priority": 10, "purpose": "mx", "required": true, "status": "missing", "observed": [] } ], "checked_at": "2026-10-09T10:05:00Z" }Each record’s
statusisok,missing,mismatchorunexpected. Add any that aremissing, correct any that aremismatch, and removeunexpectedones, which conflict (a second SPF record, for example). -
Verify.
pmail domains verify dom_01JA…This runs a check now (at most once a minute per domain). Checks use two independent DNS resolvers, and the state changes after two consecutive agreeing results, so it can take a few minutes. DNS changes can also take time to reach the resolvers. When verification passes and the domain becomes
healthy, adomain.verifiedevent is sent. If it passes with a warning, it becomesdegraded(domain.degraded), anddomain.recoveredfollows once the warning is fixed. -
Move identities onto it. See Move an identity to the new domain.
A domain already on Cloudflare: cloudflare_zone
pmail domains add brightwell.example --method cloudflare_zone --tenant brightwell
Pylota Mail writes the mail records into the zone itself, so there is usually nothing to publish.
- On an apex (
brightwell.example), every address reaches the Worker through one catch-all rule, and there is no limit on addresses. If the apex already has MX records, the request is refused with409 existing_mx, because enabling routing would stop that mail. If you really mean to move the domain’s mail to Pylota Mail, repeat the request with"replace_mx": true(--replace-mx) (H5). - On a subdomain (
mail.brightwell.example), the apex’s own MX records and mailboxes are not touched. Each address needs its own routing rule, so a subdomain holds at most 200 addresses (Limits). A new address stayspendinguntil its rule exists. If creating the rule fails, the address stayspendingwith reasonrouting_rule_failedand is retried with backoff. It is never marked active without its rule (H6).
A new domain just for mail: nameservers
pmail domains add brightwell-agents.example --method nameservers --tenant brightwell
Pylota Mail creates a Cloudflare zone for the domain, and you point the domain’s nameservers at it. This hands the whole domain to the deployment, which manages only mail records. Use it for a domain that has no website and no other mail.
- Before creating the zone, Pylota Mail looks for a website (an A or AAAA record at the name, or a CNAME,
A or AAAA record at
www) and for mail (MX records). If it finds any, the request is refused with409 domain_not_dedicated, anddetails.recordslists what it found. Moving the nameservers would stop that website or mail. If you are sure, repeat the request with"confirm_dedicated": true(--confirm-dedicated). - The records returned are two
NSrecords. Set them at your registrar (where you bought the domain), not at a DNS host. Reminders (domain.reminder) are sent after 24 hours, 72 hours and 7 days. - Cloudflare deletes a zone that is not activated within 28 days. A final reminder is sent at day 21.
If the zone is deleted, the domain becomes
removedwith reasonzone_expired, and you can add it again. - A tenant key may use this method only if the operator allows it (tenant policy
domains.allow_create_zone); otherwise the request fails with422 transport_unavailable. Pylota Mail Cloud allows it. - If Cloudflare limits how many domains the account can add, the request fails with
429 upstream_rate_limited; try again after the time inRetry-After(3 hours).
Once the zone is active, the domain works like a cloudflare_zone apex: every address works.
A subdomain at any DNS host: dns_records
pmail domains add agents.brightwell.example --method dns_records --tenant brightwell
Mail to and from the subdomain goes through Amazon SES. Your main domain, its website and its mailboxes stay where they are. Every address on the subdomain works as soon as the domain is healthy; you do not change DNS when you add an agent.
Publish these records at your DNS host (the values come from pmail domains records):
| Record | Purpose |
|---|---|
TXT _pylota-mail.agents.brightwell.example | Proves you control the domain |
MX agents.brightwell.example, priority 10 | Sends mail for the subdomain to Amazon SES |
Three CNAMEs at …._domainkey.agents.brightwell.example | DKIM signing keys |
MX and TXT at pm-bounce.agents.brightwell.example | The MAIL FROM (bounce) domain, so SPF aligns |
TXT _dmarc.agents.brightwell.example (suggested) | Only suggested when no DMARC record exists for the domain yet |
Use a name that receives no mail today. If it already has MX records, the request is refused with
409 existing_mx. Pylota Mail cannot change your DNS, so "replace_mx": true (--replace-mx) here
means “I will replace these”. Until the old MX records are gone, the domain is degraded with
mx_unexpected, because mail is split between two systems.
The local part prefix pm-bounce is reserved on these domains. See also
How SES domains differ.
Keep your mailbox, forward to the agent: send_only
pmail domains add brightwell.example --method send_only --tenant brightwell
Your agents answer as your existing addresses, such as bookings@brightwell.example, while your current
mail system (Google Workspace, Microsoft 365 or any other) keeps receiving the mail. Pylota Mail sends
through Amazon SES, signed for your domain.
-
Publish the records: the ownership TXT, three DKIM CNAMEs, and the MX and TXT at
pm-bounce.brightwell.example. Your existing MX records stay as they are. -
Add a forwarding rule in your mail system for each agent address, to that identity’s platform address, for example
bookings@brightwell.example→bookings.brightwell@agents.example. The domain response shows the platform address for each address. Use a rule that forwards each message unchanged. -
Test the forwarding once the address exists on the identity:
pmail addresses test-forwarding bookings@brightwell.exampleor
POST /v1/identities/{identity_id}/addresses/{address_id}/test-forwarding. Pylota Mail sends a short message, frommailer-daemon@agents.examplewith the subject “Pylota Mail forwarding check”, tobookings@brightwell.example. If it comes back through your forwarding rule within 10 minutes, the address’sforwardingbecomesok; otherwisefailed. The test is not stored as a message and does not count as a send. Until a test or a real forwarded message arrives,forwardingisunverified.
The main drawback: forwarders that change the message. Forwarding breaks SPF for the original
sender, so Pylota Mail decides trust from the sender’s DKIM signature and from ARC. A forwarder that
rewrites the body (adds a footer, a disclaimer or a banner) breaks that signature. Such messages fail
authentication and are quarantined by default (quarantine_reason: auth_failed), so the agent does not see them until a person
releases them (Receiving › Quarantine). Prefer a mail system that forwards
messages unchanged and adds ARC.
An agent replying to its own external address, which forwards back to the platform address, is caught by loop detection.
Keep your mailbox, send through your provider: smtp_relay
pmail domains add brightwell.example --method smtp_relay --tenant brightwell --inbound forward \
--smtp-host smtp.provider.example --smtp-port 587 --smtp-username agents@brightwell.example \
--smtp-password-stdin --probe-from agents@brightwell.example
or with the API:
{ "name": "brightwell.example", "method": "smtp_relay", "inbound": "forward",
"smtp": { "host": "smtp.provider.example", "port": 587, "username": "agents@brightwell.example",
"password": "…", "probe_from": "agents@brightwell.example" } }
The agents’ mail leaves through your own provider (Microsoft 365, Google Workspace, Postmark, Mailgun, SendGrid or any other with SMTP submission), with your provider’s reputation and authentication. The CLI never takes the password as an argument: it asks for it with hidden input, or reads it from standard input when you pipe it in.
What to set up with your provider:
- An account that can send by SMTP, with its user name and password. Port
465(TLS from the start) or587(STARTTLS) only; port25is refused with400 smtp_port_not_allowed. The relay must offer TLS: Pylota Mail never sends the credentials without it (422 smtp_tls_required). Wrong credentials give422 smtp_auth_failed. Pylota Mail tries the login once before it stores anything, and keeps the credentials encrypted. They are never shown again. - Permission to send as the agent addresses. If your provider limits which
Fromaddresses an account may use, allow the agent addresses andprobe_from. - DKIM for your domain. Your provider must sign with your own domain (or send with a MAIL FROM on your domain), so that DMARC passes.
The alignment probe. Pylota Mail cannot see from DNS how your provider signs, so before the first
send, and then every day, it sends a probe message through your relay to the platform domain. The probe
passes when the From address arrives unchanged and DMARC passes for your domain. If your provider
re-signs with its own domain the issue is smtp_unaligned; if it changes the From address,
smtp_from_rewritten. After a failed probe, another runs 20 minutes later. Two failed probes in a row
make the domain failing (about 40 minutes from the first failure at most), and sends fall back to the
platform address. Run a probe now with pmail domains probe brightwell.example
(POST /v1/domains/{domain_id}/probe, at most once a minute); the result appears in
pmail domains health within 15 minutes.
Inbound. With --inbound forward, your mailbox forwards to the agents exactly as for
send_only, with the same forwarding test and the same
drawback. With --inbound ses, you also publish the Amazon SES MX and DKIM records, as for
dns_records; the MX then sends all of the domain’s mail to
Pylota Mail, so use it only on a name that has no other mailboxes.
Delivery statuses. Your provider does not report deliveries back. A message is submitted once
your relay accepts it, and stays submitted unless a bounce arrives
(Sending › Delivery status).
Changing the password or the relay: pmail domains update brightwell.example --smtp-password-stdin
(or PATCH /v1/domains/{domain_id} with smtp). The new values are used only after a probe with them
passes; until then sends keep using the old ones.
A delegated subdomain: delegated_subdomain
pmail domains add agents.brightwell.example --method delegated_subdomain --tenant brightwell
The deployment gets its own Cloudflare zone for the subdomain, and you delegate the subdomain to it with
NS records at your DNS host. The parent domain stays where it is. After that, the subdomain works like
a cloudflare_zone apex: every address works and Pylota Mail writes the mail records.
- Available only when the deployment’s Cloudflare account is on Enterprise and the operator has set
PM_CF_SUBDOMAIN_SETUP = "on". Otherwise the request fails with422 transport_unavailable. Without it, usedns_records. - A zone hold on your own Cloudflare account can block the zone; the request then fails with
409 zone_hold. Release the hold for subdomains and try again. - The delegation is checked every week. If the
NSrecords at the parent change, the domain issuspended(nameservers_changed) until you restore them and re-prove ownership.
How SES domains differ
Domains whose mail arrives through Amazon SES (dns_records, and smtp_relay with inbound: ses) behave
differently from Cloudflare domains in three ways:
| Situation | Cloudflare domain | SES domain |
|---|---|---|
| Mail to an address that does not exist | Refused with 550 5.1.1 | Accepted by SES, then dropped without a bounce. A bounce sent after acceptance would go to whatever sender the message claims, which spam forges |
| Mail to a retired address | Refused with 550 5.1.6 | SES sends a bounce, 550 5.1.6, from mailer-daemon@ the platform domain |
| Largest message accepted | 25 MiB | 40 MB |
So on an SES domain, someone who mistypes an address gets no bounce. Tell your contacts the exact addresses your agents use.
What happens when a domain fails
Whatever the method, Pylota Mail never sends as a domain whose authentication is broken. When a domain
is failing (or suspended), sends go out from the identity’s platform address instead, for example
bookings.brightwell@agents.example, with the same display name, and replies still come back into the
same thread (Fix a failing domain). The platform address always sends through
the platform domain on Cloudflare, so fallback works for every method, including smtp_relay and SES
domains.
Move an identity to the new domain
bookings.brightwell@agents.example primary, active ──── promote ───▶ alias, active (kept for fallback)
bookings@brightwell.example alias, pending ─ healthy ─▶ active ─ promote ─▶ primary, active
The platform address (bookings.brightwell@agents.example) is never retired: it is where sends go when
the domain fails (When the domain fails). A later move between two of your own domains
retires the old one as usual.
-
Add the address.
curl -X POST https://mail.example.com/v1/identities/idn_01J9Z3K8V4/addresses \ -H "Authorization: Bearer $PYLOTA_MAIL_KEY" -H "Content-Type: application/json" \ -d '{"local_part":"bookings","domain_id":"dom_01JA…"}'CLI:
pmail addresses add. The new address is analiaswith statuspending. It becomesactivewhen its domain is healthy, andidentity.address_activatedis sent. It already receives mail once it is active. On asend_onlydomain (orsmtp_relaywithinbound: forward), add the forwarding rule and run the forwarding test now. -
Promote it when you are ready for new mail to come from it:
curl -X POST https://mail.example.com/v1/identities/idn_01J9Z3K8V4/addresses/adr_01JA…/promote \ -H "Authorization: Bearer $PYLOTA_MAIL_KEY" -H "Content-Type: application/json" \ -d '{"retire_previous_after_days":90}'CLI:
pmail addresses promote. The address becomes the primary. The previous primary becomes an alias with statusretiring, and itsretire_atis set (default 90 days, range 0–365); if the previous primary is the platform address, it becomes anactivealias instead and is never retired. Promotion needs the domain to behealthyordegraded, otherwise409 domain_not_ready.identity.address_promotedis sent.
What changes after a promotion (FR-ADR-2):
- New threads send from the new primary.
- Existing threads keep replying from the address the other party wrote to, until it retires. A
customer who answers an old message to
bookings.brightwell@agents.exampleis answered from that address (C3). - A retiring address keeps receiving into the same identity until
retire_at. Then it becomesretired,identity.address_retiredis sent, and mail to it is refused with550 5.1.6(on an SES domain, SES sends that bounce). The platform address keeps receiving for good.
To roll back, promote the previous address again (the retiring one, or the platform address). Any retirement is cancelled and it is the primary once more (FR-ADR-4).
To retire an alias early, use POST …/addresses/{address_id}/retire with {"after_days": 0}
(CLI pmail addresses retire). The primary cannot be retired (409 address_is_primary), and the
platform address can never be retired or deleted (409 address_in_use). Only a pending address that
never received mail can be deleted; any other gets 409 address_in_use.
Domain health
Every domain is checked every 15 minutes and after every change, with two independent DNS-over-HTTPS resolvers. One resolver’s error or disagreement never changes the state (H7); a change needs two consecutive agreeing results.
| State | What it means | Sending | Events |
|---|---|---|---|
pending | Newly added; records not checked yet | No. Addresses stay pending | domain.created |
verifying | Checks are running | No | – |
healthy | Every required record is correct | Yes | domain.verified (verification passed), domain.recovered (on return from degraded or failing) |
degraded | An issue was found that does not break authentication | Yes | domain.degraded with issues[] |
failing | A required authentication record is missing or wrong, or (for smtp_relay) the alignment probe failed twice | No. Sends fall back to the identity’s platform address | domain.failing with issues[] and fallback_active |
suspended | Failing for 14 days, or the domain’s ownership signals changed | No | domain.suspended with reason |
While a domain is pending, verifying, degraded, failing or suspended, domain.reminder events
are sent after 24 hours, 72 hours and 7 days in that state. Each issue carries a code, the record
and an exact fix. Relay these to the person who manages the domain’s DNS: the integrator owns how the
operator is told (H1).
See the current state and the history of checks:
pmail domains health dom_01JA…
{
"state": "failing", "reason": "dkim_missing", "since": "…",
"issues": [ { "code": "dkim_missing", "record": "cf-bounce._domainkey…", "fix": "Add TXT … with value …" } ],
"checks": [ { "at": "…", "resolver": "cloudflare-doh", "outcome": "fail" } ],
"fallback_active": true
}
Issues you may meet with the methods that keep DNS at your host:
| Issue | Methods | What it means | What to do |
|---|---|---|---|
record_doubled_name (degraded) | dns_records, send_only, smtp_relay | A record was entered with the full name in a form that adds your domain | Enter the host value instead (name and host) |
mx_unexpected (degraded) | dns_records, smtp_relay with inbound: ses | Another MX record still points elsewhere, so mail is split | Remove the old MX records |
mx_missing (fail) | dns_records, smtp_relay with inbound: ses | The MX record to Amazon SES is missing | Publish it as the fix says |
dkim_missing, ses_dkim_failed (fail) | dns_records, send_only, smtp_relay with inbound: ses | A DKIM CNAME is missing or wrong, or SES could not verify it | Publish the three CNAMEs exactly as the fix says |
mail_from_failed (degraded) | dns_records, send_only | The pm-bounce MX or TXT is missing. DKIM still aligns, so sending continues | Publish both pm-bounce records |
smtp_unaligned, smtp_from_rewritten (degraded the first time, then fail) | smtp_relay | Your provider signs with its own domain, or changes the From address | Turn on DKIM for your domain at your provider; allow the agent addresses as senders |
smtp_probe_timeout (fail before the first pass; after it, degraded, then fail after three in a row) | smtp_relay | No probe arrived within 15 minutes | Check that the relay accepts and sends mail from probe_from |
smtp_auth_failed, smtp_tls_required (fail) | smtp_relay | The relay refused the login, or offered no TLS | Update the credentials with pmail domains update |
The full list, per method, is in Domains on any DNS host › Health checks per method.
Fix a failing domain
- Read the issues:
pmail domains health dom_01JA…(or theissuesin thedomain.failingevent). - Apply each
fixat your DNS host (or, forsmtp_relay, at your mail provider), exactly as given. Runpmail domains records dom_01JA…to confirm each record isok. - Run
pmail domains verify dom_01JA…(forsmtp_relay,pmail domains probetoo). After two consecutive passing checks the domain returns tohealthyanddomain.recoveredis sent.
While the domain is failing, nothing is sent as it. Sends go out from the identity’s platform address
with the same display name, flagged sent_via_fallback, and replies still thread correctly. Threads
that fell back stay on the platform address until the domain is healthy and the thread has had no
messages for 72 hours, so a conversation does not switch addresses back and forth (Sending › When a domain fails). If the
tenant set domain_fallback: false, those sends failed instead (domain_failing_no_fallback) and must
be sent again with new idempotency keys.
Re-prove ownership
A domain is suspended after 14 days of failing, or when its ownership signals change: its
nameservers moved, the ownership TXT record disappeared, or its registration changed (checked weekly
through RDAP) (H4). domain.suspended gives the reason:
failing_14_days, nameservers_changed, ownership_record_missing or registration_changed.
Pylota Mail never sends as a domain whose ownership may have changed hands. To restore it:
pmail domains reprove dom_01JA…
This issues a new ownership TXT value for _pylota-mail.<domain>. Publish it, fix any other
issues, then run pmail domains verify.
Change domains again
You can repeat the move from one custom domain to another at any time: add an address on the new domain, wait for it to become active, promote it. The previous primary retires as before.
Only one pending address per identity and domain is allowed. If you start a second change while the first address is still pending, the newer request replaces the older pending address (A11).
Remove a domain
pmail domains remove dom_01JA…
Removal fails with 409 domain_in_use while any address on the domain is active or retiring.
Retire them first. Once removal starts (202), what Pylota Mail set up for the domain is deleted (the
routing rules, the Email Sending onboarding and the event subscription, or the SES identity), and
domain.removed is sent. Records you published at your own DNS host, and forwarding rules in your
mailbox, stay until you remove them.
DMARC alignment
DMARC passes when either SPF or DKIM passes and is aligned with the domain in From.
| Transport | DKIM | SPF |
|---|---|---|
Cloudflare Email Sending (cloudflare_zone, nameservers, delegated_subdomain) | Signed for the domain itself (selector cf-bounce), so aligned under relaxed and strict (adkim=s) alignment | The Return-Path is on cf-bounce.<domain>, which aligns under relaxed SPF alignment (the default), but not under aspf=s |
Amazon SES (dns_records, send_only) | Easy DKIM signs for the domain, so aligned under relaxed and strict alignment | The MAIL FROM is pm-bounce.<domain>, which aligns under relaxed SPF alignment |
Your SMTP relay (smtp_relay) | Depends on your provider. The alignment probe proves that DKIM or SPF aligns before the first send and every day | Depends on your provider |
So DKIM alignment carries DMARC on Cloudflare and SES, and the probe checks it on a relay. Before onboarding, a preflight checks the domain’s DMARC alignment tags against the transport’s DKIM domain and reports combinations that would fail (H3). Ramp the domain’s own DMARC policy as described in Deploy to Cloudflare › DNS authentication and the DMARC ramp.
FAQ
Can a tenant use its main company domain?
Yes. If it already has mailboxes, use send_only or smtp_relay: the agents answer as addresses on
that domain and the mailboxes keep working. Or put the agents on a subdomain with dns_records. Only a
cloudflare_zone apex and nameservers take over the domain’s mail, and both refuse a domain with
existing mail unless you confirm.
Does my domain have to be on Cloudflare?
No. Only the deployment’s platform domain does. dns_records, send_only and smtp_relay work with
any DNS host.
What happens to old threads after a domain change? They continue. Replies to the old address arrive in the same identity and are answered from the address the other party used, until it retires.
Can I undo a domain change?
Yes, while the old address is still retiring: promote it again. Once it is retired, it can no
longer receive mail and cannot be given to anyone else. The platform address never retires, so a move
away from it can always be undone.
Why the limit of 200 addresses on a subdomain?
It applies only to a cloudflare_zone subdomain. Cloudflare allows catch-all routing only on a zone
apex, so each address on such a subdomain needs its own routing rule, and Cloudflare allows 200 rules per
domain. A zone apex, a dns_records subdomain and a delegated subdomain have no such limit.
Does Pylota Mail ever send as a broken domain?
No. A failing domain falls back to the platform address. A suspended domain sends nothing as
itself until ownership is proved again.
Can operators delegate a subdomain to the deployment instead?
On a Cloudflare Enterprise account, yes, with delegated_subdomain once the operator turns it on.
Otherwise use dns_records.
Do I need PM_CF_API_TOKEN?
For cloudflare_zone, nameservers and delegated_subdomain added through the API, yes; its
permissions are in Deploy to Cloudflare. Without it,
an operator can still add a zone apex with pmail domains add --local-token and their local token; subdomains,
nameservers and delegated_subdomain need the token on the deployment. dns_records, send_only and
smtp_relay do not use it.
Do I need an AWS account?
The operator does, for dns_records and send_only (and smtp_relay with inbound: ses). Tenants do
not.