Sender setup
These three DNS records prove your emails really come from you. Without them, Gmail, Outlook and Yahoo can’t tell your password reset from a phishing email pretending to be you, so they send it to spam, or drop it. Here is what each one does, how to check yours in one prompt, and exactly what to add for your email provider.
The records, in plain words
When your app sends an email, the receiving server asks your domain’s DNS three questions. The answers are records you add once, at whoever hosts your DNS (Cloudflare, Vercel, Namecheap, GoDaddy, Route 53…).
| Record | The question it answers | What it looks like |
|---|---|---|
| SPF | Is this server allowed to send email for this domain? | v=spf1 include:amazonses.com ~all |
| DKIM | Was this email really signed by the domain, and has nobody changed it on the way? Your provider signs every email with a private key; this record is the public half. | resend._domainkey → p=MIGfMA0… |
| DMARC | If an email claiming to be from this domain fails those checks, what should I do with it, and where do I send reports? | v=DMARC1; p=none; rua=mailto:… |
| Return path | Where do bounces go? A bounce address on your own subdomain (send.yourdomain.com) lets SPF count for your domain, not your provider’s. | send MX feedback-smtp… |
DMARC passes when SPF or DKIM passes for the domain in your From address. That is why the provider’s own records aren’t enough: they prove the email came from Resend or SendGrid, not from you. Since 2024 Gmail and Yahoo require SPF, DKIM and a DMARC record from anyone sending in bulk.
Check your domain
Ask your agent. It reads your code to find which provider sends your email, then calls check_sender_domain, which looks your domain up in public DNS. Nothing is sent and no test run is needed.
Check our sending domain with Flowproof's check_sender_domain. Work out which email provider this app sends with from the code, pass it, and tell me exactly which DNS records to add.You get one line you can act on, then each record with exactly what to add:
check_sender_domain domain "acme.com", provider "resend"
2 of 4 ready: add DKIM and DMARC.
SPF ready SPF at send.acme.com authorises your provider (include:amazonses.com).
DKIM missing add TXT resend._domainkey.acme.com p=<the public key Resend shows>
DMARC missing add TXT _dmarc.acme.com v=DMARC1; p=none; rua=mailto:dmarc-reports@acme.com
Return path ready send.acme.com sends bounces back to Resend.| Status | Means |
|---|---|
| ready | The record is there and says the right thing. |
| missing | There is no record. The result gives the type, the host and the value to add. |
| wrong | There is a record, but it is broken: two SPF records, an SPF record that allows every server, a revoked DKIM key, a DMARC record with no policy. |
| unchecked | DNS alone can’t tell. Some providers pick a random DKIM name or bounce subdomain per account (Postmark, SES, SendGrid); your agent asks you for it from the provider’s DNS page and checks again. |
Values in <angle brackets> differ for every account: copy them from your provider’s dashboard. DNS changes can take up to an hour to show up, so give it a moment before you check again. The check is limited to 20 domains per 10 minutes per account.
Records for each provider
Hosts are relative to your sending domain: send means send.yourdomain.com. Each provider shows your exact values on the page named under its heading; add those, not the examples.
Resend
Where: Resend dashboard -> Domains -> your domain -> Records
| For | Type | Host | Value | Source |
|---|---|---|---|---|
| DKIM | TXT | resend._domainkey.yourdomain.com | p=<the public key Resend shows> | resend.com |
| SPF | TXT | send.yourdomain.com | v=spf1 include:amazonses.com ~all "send" is the default return-path subdomain; you can choose another when you add the domain. | resend.com |
| Return path | MX | send.yourdomain.com | 10 feedback-smtp.us-east-1.amazonses.com The documented value is for the us-east-1 region. For another region Resend shows its own value; copy it from the dashboard. | resend.com |
| DMARC | TXT | _dmarc.yourdomain.com | v=DMARC1; p=none; rua=mailto:<a mailbox you read> Recommended once the domain is verified. | resend.com |
Domains added from August 2026 may be given CNAME records for the return path instead of the TXT and MX pair. Always add exactly what the Records tab shows.
Postmark
Where: Postmark -> Sender Signatures -> your domain -> DNS Settings
| For | Type | Host | Value | Source |
|---|---|---|---|---|
| DKIM | TXT | <date>pm._domainkey.yourdomain.com | v=DKIM1; k=rsa; p=<the public key Postmark shows> Postmark picks the selector: a date-like string ending in "pm", e.g. 20230601123456pm._domainkey. | postmarkapp.com |
| Return path | CNAME | pm-bounces.yourdomain.com | pm.mtasv.net pm-bounces is the default; you can pick another subdomain. This is also what makes SPF align. | postmarkapp.com |
| SPF | TXT | yourdomain.com | Nothing to add Postmark no longer needs an include in your own SPF record: the Return-Path CNAME covers SPF. | postmarkapp.com |
| DMARC | TXT | _dmarc.yourdomain.com | v=DMARC1; p=none; rua=mailto:<a mailbox you read> Postmark recommends DMARC but does not generate the record; this is the standard starting point. Unverified. | rfc-editor.org |
SendGrid
Where: SendGrid -> Settings -> Sender Authentication -> Authenticate Your Domain
| For | Type | Host | Value | Source |
|---|---|---|---|---|
| Return path | CNAME | em<1234>.yourdomain.com | u<1234567>.wl<123>.sendgrid.net With automated security on (the default). The em#### subdomain is random unless you set a custom return path. | twilio.com |
| DKIM | CNAME | s1._domainkey.yourdomain.com | s1.domainkey.u<1234567>.wl<123>.sendgrid.net | twilio.com |
| DKIM | CNAME | s2._domainkey.yourdomain.com | s2.domainkey.u<1234567>.wl<123>.sendgrid.net | twilio.com |
| SPF | TXT | em<1234>.yourdomain.com | v=spf1 include:sendgrid.net ~all Only with automated security off (then also MX em#### -> mx.sendgrid.net and a TXT DKIM key at m1._domainkey). With it on, the em#### CNAME covers SPF. | twilio.com |
| DMARC | TXT | _dmarc.yourdomain.com | v=DMARC1; p=none; SendGrid includes this in the records it generates. | twilio.com |
s1 and s2 are the default DKIM selectors; SendGrid lets you choose a custom three-character selector instead.
Amazon SES
Where: Amazon SES console -> Configuration -> Identities -> your domain
| For | Type | Host | Value | Source |
|---|---|---|---|---|
| DKIM | CNAME | <token>._domainkey.yourdomain.com (three of them) | <token>.dkim.amazonses.com Easy DKIM. The tokens are random per identity, and some regions use a regional host (dkim.<region>.amazonses.com): copy all three exactly. | docs.aws.amazon.com |
| Return path | MX | <your MAIL FROM subdomain, e.g. mail> | 10 feedback-smtp.<region>.amazonses.com Custom MAIL FROM domain. Without it SES bounces through amazonses.com: SPF passes, but not for your domain. | docs.aws.amazon.com |
| SPF | TXT | <your MAIL FROM subdomain> | v=spf1 include:amazonses.com ~all | docs.aws.amazon.com |
| DMARC | TXT | _dmarc.yourdomain.com | v=DMARC1; p=none; rua=mailto:<a mailbox you read> AWS suggests moving from none to quarantine to reject once reports are clean. | docs.aws.amazon.com |
SES only delivers to verified addresses until you request production access.
Mailgun
Where: Mailgun -> Sending -> Domain settings -> DNS records
| For | Type | Host | Value | Source |
|---|---|---|---|---|
| SPF | TXT | yourdomain.com (the domain you added to Mailgun, often mg.<your domain>) | v=spf1 include:mailgun.org ~all | documentation.mailgun.com |
| DKIM | TXT | <selector>._domainkey.yourdomain.com | k=rsa; p=<the public key Mailgun shows> The selector is shown in Mailgun (and can be set through the API). With Automatic Sender Security it is two CNAMEs instead: pdk1._domainkey and pdk2._domainkey -> ...dkim1.mailgun.com. | documentation.mailgun.com |
| Return path | MX | yourdomain.com (the Mailgun domain) | 10 mxa.mailgun.org and 10 mxb.mailgun.org EU accounts: mxa.eu.mailgun.org and mxb.eu.mailgun.org. Never put these on a domain whose inbox you use: send from a subdomain. | documentation.mailgun.com |
| DMARC | TXT | _dmarc.yourdomain.com | v=DMARC1; p=none; rua=mailto:<a mailbox you read> Mailgun offers a generated DMARC record; its exact value is not in its public docs. Unverified. | rfc-editor.org |
Brevo
Where: Brevo -> Senders, Domains & Dedicated IPs -> Domains
| For | Type | Host | Value | Source |
|---|---|---|---|---|
| Ownership | TXT | yourdomain.com | brevo-code:<the code Brevo shows> | developers.brevo.com |
| DKIM | TXT | mail._domainkey.yourdomain.com | k=rsa;p=<the public key Brevo shows> Newer setups use two CNAMEs instead (DKIM 1 and DKIM 2); their exact names are shown in Brevo, not in its public docs. | developers.brevo.com |
| SPF | TXT | yourdomain.com | Nothing to add on a shared IP Brevo says SPF and MX are not required to authenticate a domain; it provides them for a dedicated IP. | help.brevo.com |
| Return path | CNAME | <branded subdomain, e.g. send> | <the target Brevo shows> The optional "branded" CNAME aligns SPF and the return path. Brevo shows the target; it is not in its public docs. Unverified. | help.brevo.com |
| DMARC | TXT | _dmarc.yourdomain.com | v=DMARC1; p=none; rua=mailto:rua@dmarc.brevo.com | help.brevo.com |
If a domain is not authenticated, Brevo sends from a brevosend.com address instead of yours.
Other SMTP provider
Where: Your provider's domain or sender authentication page
| For | Type | Host | Value | Source |
|---|---|---|---|---|
| SPF | TXT | yourdomain.com or the provider's bounce subdomain | v=spf1 include:<your provider> ~all Your provider gives you the include. Unverified. | rfc-editor.org |
| DKIM | TXT | <selector>._domainkey.yourdomain.com | v=DKIM1; k=rsa; p=<public key> Your provider gives you the selector and key. Unverified. | rfc-editor.org |
| DMARC | TXT | _dmarc.yourdomain.com | v=DMARC1; p=none; rua=mailto:<a mailbox you read> | rfc-editor.org |
Don’t launch on your auth provider’s built-in mailer
Supabase, Auth0, Cognito and Firebase all send sign-up and password-reset emails out of the box, from their own domain. That is fine while you build. In production it breaks in ways you won’t see:
- Supabase’s built-in email sends only a few emails an hour; past that, sign-ups and password resets silently get no email.
- Auth0’s built-in provider is for testing only, with a low limit and no deliverability promises.
- Cognito’s default email sends a limited number a day for the whole user pool, then stops.
- Firebase sends from a shared firebaseapp.com address your users don’t recognise.
And none of them can pass DMARC for your domain, because the email isn’t from your domain. Connect your own provider (custom SMTP in Supabase and Firebase, Amazon SES in Cognito, an email provider in Auth0), send from an address on your domain, add the records above, and a Flowproof run will stop flagging shared_auth_mailer.
DMARC: start at none, then tighten
Start with p=none and a rua= address: receivers deliver as normal and send you daily reports of who is sending as your domain. Once the reports show only your own services passing, move to p=quarantine (failures go to spam), and later p=reject. Moving too early sends your own misconfigured email to spam, so fix alignment first.
Have exactly one DMARC record and one SPF record per name. Two of either is an error, and receivers then ignore both.
Then test the real thing
DNS says what you published; it can’t say what a receiver made of a real email. Once the check is all ready, have your agent send each email through your provider to a Flowproof test address: the report grades SPF, DKIM and DMARC as the receiving server saw them. See how a test run works.