Magic-link and receipt emails come from an authenticated domain
The sending domain implied by DKIM selectors or a Supabase default sender has SPF and DMARC.
- Severity
- high
- Tier
- Passive: runs on any URL
- Category
- Fix
- Free, in full, below
- Id
- auth-sender-authenticated
What this means
Passing means the domain your sign-in codes, magic links and receipts are sent from has SPF, DKIM and DMARC published, and it is a domain you control. Failing means that mail goes out from a domain with no authentication, or from your auth provider's shared default sender, which you cannot authenticate at all. This is the "magic-link emails land in spam" verdict: the product works, but the email that lets people in does not arrive.
How we test it
We work out the sending domain first: a DKIM selector on your domain names the provider, and with no selector on a Supabase-backed app we assume the Supabase default sender. Then we look up SPF at that domain's apex, DKIM at the common selectors, and DMARC at _dmarc. Your own domain with all three is a pass. Your own domain with SPF and DMARC but no DKIM selector we recognise is a warn. The provider's shared default sender, or your domain missing SPF or DMARC, is a fail. This is a passive check.
Why it matters
Auth emails from an unauthenticated sender go to spam, and users cannot sign in. A new user who never receives the code gives up, and an existing user locked out of a password reset writes to support, if support mail is deliverable either. The Module 08 notes record how this happens even after setup: an SMTP password rotation sent only the password field, the provider treated it as the whole block, host, port, user and sender came back empty, and the product silently fell back to the provider's shared, rate-limited mailer. SMTP settings are set as a complete set and proven with a real send.
The fix
Do this in order, with your own domain as the sender.
In Resend, Domains, Add Domain, enter
YOUR_SENDING_DOMAINand pick the region nearest your users. Leave the page open; the DKIM value comes only from here.In Cloudflare DNS, add the records Resend shows. Enter only the host part in the Name field (Cloudflare appends the domain), DNS only:
MX send feedback-smtp.REGION.amazonses.com priority 10
TXT send v=spf1 include:amazonses.com ~all
TXT resend._domainkey p=COPY_THE_WHOLE_STRING_FROM_RESEND
TXT _dmarc v=DMARC1; p=quarantine; rua=mailto:dmarc@YOUR_SENDING_DOMAIN; pct=100
There can be only one SPF TXT on the apex. If Cloudflare Email Routing is on, it already added v=spf1 include:_spf.mx.cloudflare.net ~all; if Resend also asks for SPF on @, merge the two into v=spf1 include:_spf.mx.cloudflare.net include:amazonses.com ~all, using the include values each provider actually shows. Make sure dmarc@YOUR_SENDING_DOMAIN forwards to an inbox you read. Back in Resend, click Verify.
Create a key for the auth mailer only: Resend, API Keys, Create, permission "Sending access", restricted to this one domain. Never paste it into a file or a chat.
In Supabase, Authentication, SMTP Settings, set all six fields together: host
smtp.resend.com, port465, usernameresend, password the key from step 3, sender name your product name, sender emaillogin@YOUR_SENDING_DOMAIN. If you change this through the management API instead, send everysmtp_*field from a fresh read; a partial write empties the rest. While you are there, edit Authentication, Email Templates so each template shows{{ .Token }}and none includes{{ .ConfirmationURL }}; corporate mail scanners spend one-time links before the user clicks them.Prove it. From a terminal:
dig +short TXT resend._domainkey.YOUR_SENDING_DOMAIN # a line starting p=
dig +short TXT YOUR_SENDING_DOMAIN | grep -c spf1 # 1
dig +short TXT _dmarc.YOUR_SENDING_DOMAIN # v=DMARC1; p=quarantine; ...
Then request a password recovery for your own address:
curl -s -X POST "SUPABASE_URL/auth/v1/recover" \
-H "apikey: $SUPABASE_ANON_KEY" -H "Content-Type: application/json" \
-d '{"email":"YOUR_EMAIL"}'
Expected {}. The message must arrive from login@YOUR_SENDING_DOMAIN, and its headers must show SPF: PASS, DKIM: PASS and DMARC: PASS. A message from a supabase.io address means the SMTP block is incomplete; a 5xx means the key is wrong or scoped to a different domain.
A deeper runbook for this class is in the Fix Kit, Module 08.
Prompt for your AI
Paste this into the coding agent that built the app.
Read the Supabase SMTP settings and the DNS for YOUR_SENDING_DOMAIN before changing anything, and never print a key.
Run dig +short TXT YOUR_SENDING_DOMAIN | grep spf1, dig +short TXT resend._domainkey.YOUR_SENDING_DOMAIN and dig +short TXT _dmarc.YOUR_SENDING_DOMAIN, and show me which records are missing.
For each missing record give me the exact Cloudflare entry: type, host part only (send, resend._domainkey or _dmarc), value, DNS only. The DKIM value comes only from the Resend domain page, so ask me for it.
If the apex already has an SPF record, give me one merged record; there must be exactly one v=spf1 on the apex.
Once I confirm the records resolve, tell me the six Supabase SMTP values to set together: host smtp.resend.com, port 465, username resend, password the send-only key, sender name, sender email login@YOUR_SENDING_DOMAIN.
Change every auth email template to show {{ .Token }} and remove {{ .ConfirmationURL }} from all of them.
Run curl -s -X POST "SUPABASE_URL/auth/v1/recover" -H "apikey: $SUPABASE_ANON_KEY" -H "Content-Type: application/json" -d '{"email":"MY_EMAIL"}' and show me the output. Expected {}.
Finally run dig +short TXT _dmarc.YOUR_SENDING_DOMAIN | grep -c DMARC1 and dig +short TXT resend._domainkey.YOUR_SENDING_DOMAIN | grep -c "p=" and show both counts. Expected 1 and 1.
Grade your own URL
Every grade lists this check with its evidence for your site. Two free grades a day.