Start here
DNS setup
Four records per sending domain. Two are required, two are recommended. Everything on this page is generated per domain — nothing is shared between customers, and no key material is hard-coded.
The records
Given example.com, the dashboard shows exactly this (with your real key):
@ 3600 IN TXT "v=spf1 include:mail.yourservice.com ~all"
selector1._domainkey 3600 IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQ…"
_dmarc 3600 IN TXT "v=DMARC1; p=none;"
bounce 3600 IN CNAME bounce.yourservice.com
| Record | Required | What it does |
|---|---|---|
| SPF | yes | Tells receivers our servers may send for your domain. |
| DKIM | yes | Lets receivers verify our cryptographic signature on your mail. |
| DMARC | recommended | Tells receivers what to do when SPF and DKIM fail, and sends you reports. |
| Return path | optional | Branded bounce hostname. Delivery works without it. |
A domain becomes verified once SPF and DKIM both pass. Those two are what actually authenticate the mail.
SPF
Type: TXT
Host: @ (the apex — some registrars want the domain name itself)
Value: v=spf1 include:mail.yourservice.com ~all
One record, always
A domain may publish exactly one SPF record. Two is not "extra authorisation" — it is a permanent error, and receivers treat the whole thing as broken. This is the single most common way domains break their own mail.
If you already send through another provider, merge:
# Before
v=spf1 include:_spf.google.com ~all
# After — one record, both providers
v=spf1 include:_spf.google.com include:mail.yourservice.com ~all
The dashboard detects a second record and says so explicitly rather than just failing verification.
~all vs -all
~all(softfail) — mail from unlisted servers is accepted but marked. Start here.-all(hardfail) — mail from unlisted servers is rejected. Move here once you are certain every legitimate sender is listed.+all— authorises the entire internet to send as you. Never use it. The dashboard warns if it sees one.
The ten-lookup limit
SPF permits at most 10 DNS lookups while evaluating a record (RFC 7208). Each
include:, a, mx, exists and redirect counts. Exceed it and evaluation
returns permerror, which most receivers treat as a failure. The dashboard
counts them for you.
DKIM
Type: TXT
Host: selector1._domainkey
Value: v=DKIM1; k=rsa; p=<your public key>
Every domain gets its own 2048-bit RSA key pair, generated the moment you add the domain. The public half goes in DNS; the private half is encrypted with AES-256-GCM and only ever decrypted inside the signing service. It is never returned by the API and never written to disk.
Some DNS providers reject the long p= value because a single TXT string is
capped at 255 bytes. Providers handle this differently:
- Most (Cloudflare, Route 53, Google Cloud DNS) split it automatically.
- Some require you to enter it as multiple quoted strings:
"v=DKIM1; k=rsa; p=MIIBIjANBg…" "…rest of the key". The two strings are concatenated with no separator — do not add a space.
Verification compares the published key against the one we issued, ignoring whitespace and PEM armour, so either form works.
DMARC
Type: TXT
Host: _dmarc
Value: v=DMARC1; p=none;
DMARC ties SPF and DKIM to the visible From: address and tells receivers what
to do when neither aligns. Gmail and Yahoo require a DMARC record from bulk
senders, and it is the single best signal that you are a deliberate sender.
Roll it out in stages
v=DMARC1; p=none; rua=mailto:dmarc@example.com; ← observe
v=DMARC1; p=quarantine; pct=25; rua=mailto:… ← tighten gradually
v=DMARC1; p=reject; rua=mailto:… ← enforce
Stay at p=none until the reports show every legitimate stream passing. Jumping
straight to p=reject will silently destroy mail from systems you forgot about
— the marketing tool, the CRM, the invoicing service.
Add rua=mailto:… or you will get no reports at all, which makes the whole
exercise blind.
We never modify records you already publish. If you already have a DMARC policy, keep it — verification reports what it found and moves on.
Return path (optional)
Type: CNAME
Host: bounce
Value: bounce.yourservice.com
Bounce reports are addressed to bounce+<emailId>@bounce.yourservice.com, so
each report identifies exactly which message it belongs to. This CNAME gives
that a hostname under your own domain.
It is genuinely optional. DMARC alignment is satisfied by the DKIM signature,
which carries d=example.com, so delivery and alignment work with or without
this record.
Verifying
Dashboard → your domain → Verify DNS, or:
curl -X POST https://api.yourservice.com/v1/domains/dom_abc123/verify \
-H "Authorization: Bearer $MAIL_API_KEY"
DNS changes are not instant. Propagation usually takes minutes and can take up to the TTL of the record you replaced. If verification fails immediately after you publish, wait and retry before assuming something is wrong.
Checking it yourself
dig +short TXT example.com
dig +short TXT selector1._domainkey.example.com
dig +short TXT _dmarc.example.com
dig +short CNAME bounce.example.com
On Windows:
Resolve-DnsName -Type TXT example.com
Resolve-DnsName -Type TXT selector1._domainkey.example.com
After it verifies
Send yourself a test message and inspect the headers. In Gmail: Show original. You want all three:
SPF: PASS with IP …
DKIM: PASS with domain example.com
DMARC: PASS
DKIM: PASS with domain example.com — not yourservice.com — is the one that
matters. It means the signature is aligned with the visible From: address,
which is what DMARC checks.
Common failures
| Symptom | Cause | Fix |
|---|---|---|
SPF MULTIPLE_SPF_RECORDS | Two TXT records starting v=spf1. | Merge into one. |
SPF INCLUDE_MISSING | Record exists but does not list us. | Add include:mail.yourservice.com to the existing record. |
| SPF passes locally, fails here | Added at www or a subdomain instead of the apex. | Host must be @. |
DKIM MISSING_RECORD | Provider appended the domain twice — selector1._domainkey.example.com.example.com. | Enter the host as selector1._domainkey only. |
DKIM KEY_MISMATCH | An old key from a previous provider, or the value was truncated on paste. | Replace with the exact value from the dashboard. |
| DKIM fails on long values | Provider will not accept a >255-byte string. | Split into two quoted strings with no separator. |
LOOKUP_FAILED | Transient resolver problem, or the domain does not resolve. | Retry in a minute. |
| Verified, then failed later | A required record was removed or edited. | Re-publish it; verification runs on a schedule. |
Platform-side DNS
These are records you publish for the platform itself, not per customer. See deployment.md.
mail.yourservice.com. A 203.0.113.10
mail.yourservice.com. TXT "v=spf1 ip4:203.0.113.10 -all"
bounce.yourservice.com. A 203.0.113.10
bounce.yourservice.com. MX 10 mail.yourservice.com.
api.yourservice.com. A 203.0.113.10
dashboard.yourservice.com. A 203.0.113.10
; And at your hosting provider, the reverse record:
10.113.0.203.in-addr.arpa. PTR mail.yourservice.com.
The SPF record on mail.yourservice.com is what customers' include: resolves
to. When you add a sending IP, add it there — every customer picks up the change
without touching their own DNS. That is the entire point of the include:
indirection.