Skip to main content
友田 陽大
Resend & transactional email
Resend
メール配信
SPF/DKIM/DMARC
到達率
インフラ
信頼性

Resend Domain Authentication and Deliverability: Putting SPF, DKIM and DMARC on the Right Hostnames

Most Resend domain verification failures are a DNS hostname problem: SPF on send, DKIM on resend._domainkey, DMARC on _dmarc. Plus dig checks and safe DMARC rollout.

Published
Reading time
30 min read
Author
友田 陽大
Share

For a stretch of time, the contact form on this site returned a 502 on every single submission. The Route Handler was correct, Zod validation passed, and it worked locally. The cause was that Resend had never verified the domain — and the reason verification never completed was that I had put all four records (DKIM, the SPF TXT, the SPF MX and DMARC) on the apex, the root domain. No amount of re-reading the code was going to surface that. What was broken was a DNS hostname.

It is not an exotic mistake. The second item in Resend's own troubleshooting checklist is "verify that the records are added at the correct location (the send subdomain, not the root domain)". When something makes it into a numbered checklist, it is because a lot of people get stuck there.

This article is a working guide to getting Resend domain authentication right once and keeping it right. It traces the division of labour between the three standards back to the RFCs, lays out the exact record table, kills the hostname trap with dig, grows a DMARC policy safely, lines everything up against the Gmail, Yahoo and Microsoft requirements, and finishes with a triage path for "it says delivered but nobody can find it". The map of the whole cluster lives in the Resend production guide.


Why you need all three: SPF, DKIM and DMARC

They are not competing standards. They are three layers with different jobs, and once that clicks, every record placement below follows from logic rather than memory.

StandardIn one lineThe question it answersPrimary spec
SPFDeclares the allowed sending pathIs this IP allowed to send on behalf of this domain?RFC 7208
DKIMProves integrity via a signatureAre the body and headers still as the domain owner signed them?RFC 6376 (key length updated by RFC 8301)
DMARCInstructions and reporting on failureIf SPF/DKIM fail, what should happen and who gets told?RFC 9989 (obsoletes RFC 7489)

SPF (RFC 7208) declares in DNS which sending IPs are authorised for a domain. §3.1 states that "SPF records MUST be published as a DNS TXT (type 16) Resource Record only" and that "multiple SPF records are not permitted for the same owner name". Publishing two SPF TXT records on send.example.com violates the spec — merge them into one instead.

DKIM (RFC 6376) signs headers and body with a private key and publishes the public key in DNS. §3.6.2.1 states that "all DKIM keys are stored in a subdomain named _domainkey", which is why Resend uses resend._domainkey: resend is the selector. The p= tag in the key record holds the public key data, and RFC 6376 defines it as "REQUIRED. An empty value means that this public key has been revoked." Truncating the value when you paste it can therefore read as a revocation, so always copy the whole thing.

DMARC lets the sending domain tell receivers what to do when SPF and DKIM fail, and lets you receive reports on the outcome. This is the piece most likely to be stale in your head in 2026: RFC 9989 (Standards Track, May 2026) obsoletes RFC 7489 and RFC 9091. At minimum, "DMARC is only an Informational RFC, not a real standard" is no longer a true statement.

Resend's docs state the pass condition cleanly.

An email must pass either SPF or DKIM checks (but not necessarily both) to achieve DMARC compliance and be considered authenticated. A message fails DMARC if both SPF and DKIM fail on the message. — Resend Docs, DMARC

DMARC only needs one of them to pass. The Gmail, Yahoo and Microsoft bulk sender requirements below still ask you to configure both. Do not collapse those two statements into one.


The exact records Resend generates

If you add example.com in the us-east-1 region, these are the records Resend asks for. The host column uses the relative name — the form without the domain part.

PurposeResend recordTypeHost (relative)ValuePriority
Bounce/complaint Return-PathSPFMXsendfeedback-smtp.us-east-1.amazonses.com10
SPF policySPFTXTsend"v=spf1 include:amazonses.com ~all"
DKIM public keyDKIMTXTresend._domainkeyp=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB... (the full generated value)
Click/open tracking (optional)TrackingCNAMElinkslinks1.resend-dns.com
Receiving (optional)Receiving MXMXinboundinbound-smtp.us-east-1.amazonaws.com10
DMARC (manual — Resend does not create it)TXT_dmarcv=DMARC1; p=none; rua=mailto:dmarcreports@example.com;

The real point of that table is that the placement splits four ways, and DMARC is the odd one out.

RecordBelongs onDoes NOT belong on
SPF MXsend.example.comapex
SPF TXTsend.example.comapex
DKIM TXTresend._domainkey.example.comapex, send.
DMARC TXT_dmarc.example.com (organizational domain)send.
Tracking CNAMEthe tracking subdomainapex

SPF and DKIM live under send. and resend._domainkey., but DMARC lives at _dmarc on the organizational domain. That asymmetry is exactly what caught me: I assumed "they are all Resend records, so they all go in the same place" and piled four records onto the apex. Putting DMARC on send.example.com does nothing at all — receivers only ever look at _dmarc.example.com.

If the domain you add to Resend is itself a subdomain (say notifications.example.com), everything shifts down with it: SPF to send.notifications.example.com, DKIM to resend._domainkey.notifications.example.com. And note that Resend does not create the DMARC record for you. It shows a suggested value on the domain detail page; publishing it is your job.

There is one internal inconsistency in the official docs worth knowing about. The create-domain API reference example shows DKIM as three CNAME records, while the get-domain example, the domain.updated webhook payload and all five per-provider DNS guides show a single TXT at resend._domainkey. The weight of evidence favours the single TXT as current behaviour, but the only thing you can be certain of is the value on your own screen. Paste whatever was generated for you.

What the SDK types actually accept

If you run a SaaS where each tenant brings a domain, you will do this over the API — and there you need to know that the type definitions in the installed resend@6.4.1 are narrower than the official code samples. The tracking docs show resend.domains.create({ name, openTracking, clickTracking, trackingSubdomain }), but in v6.4.1 CreateDomainOptions is only { name, region?, customReturnPath? }, and trackingSubdomain is absent from UpdateDomainsOptions too. When you need a parameter the types do not expose, the practical answer is the SDK's public generic HTTP methods (resend.post() / resend.patch()) against the REST snake_case names.

import { Resend } from "resend";

const resend = new Resend(process.env.RESEND_API_KEY);

// The SDK does not throw. Always branch on { data, error }.
const { data, error } = await resend.domains.create({
  name: "notifications.example.com",
  // The region is baked into the MX value and cannot be changed later. Decide it up front.
  region: "ap-northeast-1",
  // Return-Path subdomain. Defaults to send. Max 63 chars, letters, numbers and hyphens only.
  customReturnPath: "outbound",
});
// Classify on error.name, not on the status code:
// the docs and the SDK disagree on the status for some error codes.
if (error) throw new Error(`Resend domains.create failed: ${error.name}`);
// data.records holds every DNS record you need to publish (type, host, value, priority).

// Once DNS is in place, kick off verification. verify does not return pass/fail —
// it only starts an asynchronous cycle, and the domain goes to pending regardless of its state.
await resend.domains.verify(data.id);

// Poll with get, or subscribe to the domain.updated webhook.
// The per-record status tells you exactly which single record is failing.
const { data: domain } = await resend.domains.get(data.id);
console.log(domain?.records.filter((r) => r.status !== "verified"));

Reading the types surfaces one more gap: v6.4.1's DomainStatus is a five-value union of pending, verified, failed, temporary_failure and not_started, and does not include the partially_verified and partially_failed values that appear in the docs. Those were added on the docs side once domains gained separate sending and receiving capabilities, so anywhere you switch exhaustively on status needs a fallback for unknown values. Webhook handling is covered separately in Resend webhooks, signature verification and bounce handling.


The biggest trap: the doubled hostname

Here is the heart of it. Most DNS providers automatically append your own domain to whatever you type in the host field. Type send.example.com and the record that actually gets created is send.example.com.example.com. Resend's UI shows you the FQDN, so copy-pasting it straight across breaks things.

Resend repeats the same sentence, verbatim across its Cloudflare, Vercel, Route 53, GoDaddy and Namecheap guides.

Omit your domain from the record values in Resend when you paste. Instead of send.example.com, paste only send (or send.subdomain if you're using a subdomain).

The same applies to DKIM: paste resend._domainkey, not resend._domainkey.example.com. A closely related accident happens on the value side.

Problem: Your MX record appears as feedback-smtp.eu-west-1.amazonses.com.example.com instead of feedback-smtp.eu-west-1.amazonses.com Solution: In your DNS provider, add a trailing period (dot) at the end of the record value: feedback-smtp.eu-west-1.amazonses.com.Resend Docs, What if my domain is not verifying?

The trailing dot is the DNS convention for "this is a fully qualified name, do not modify it". If your domain has been glued onto the value, that fixes it.

Measure DNS, not the dashboard

Control panels show you the value you think you entered. The thing to trust is the answer the rest of the world actually gets.

# SPF MX (has your domain been appended to the value? what is the priority?)
dig +short MX send.example.com
# SPF TXT (does exactly one record starting with v=spf1 come back?)
dig +short TXT send.example.com
# DKIM TXT (is the full p= value there, or was it truncated?)
dig +short TXT resend._domainkey.example.com
# DMARC (is it on _dmarc rather than the apex?)
dig +short TXT _dmarc.example.com
# The doubled-hostname check. Anything returned here is the broken record.
dig +short TXT send.example.com.example.com

Resend's own docs suggest equivalent commands such as nslookup -type=TXT resend._domainkey.example.com. Resend also publishes its own DNS lookup tool, dns.email, and points at it from several troubleshooting pages: "you can use Resend's dns.email tool to check that your records are visible publicly." Handy whenever you suspect a local DNS cache.

When it still will not verify

Work down the failure modes Resend names, in this order.

  1. Incorrect record values — extra quotes or spaces, truncated long values, SPF information pasted into the DKIM record, or an incomplete copy.
  2. DNS providers auto-appending domain names — the doubling described above.
  3. Nameserver conflicts — DNS managed in more than one place (Vercel, Cloudflare, the registrar) and the records added somewhere that is not authoritative.
  4. Region mismatch (region-mismatch) — the domain's configured region differs from the region in the MX value.
  5. Multiple regions (multiple-regions) — MX records pointing at different regions. Every MX for a domain must point at the same one.
  6. Propagation — domains often verify within about 15 minutes, and DNS changes can take up to 72 hours. If nothing is detected within 72 hours the status becomes failed; use the "Restart verification" button to trigger a fresh check.

The domain detail page runs real-time DNS validation and marks problem records with red wavy underlines. Look there first.


Regions and the custom Return-Path

There are four sending regions: North Virginia (us-east-1), Ireland (eu-west-1), São Paulo (sa-east-1) and Tokyo (ap-northeast-1). The API default is us-east-1. The region is not a standalone setting stored somewhere abstract — it is baked into the MX value itself (feedback-smtp.<region>.amazonses.com). That is precisely why "I meant to change the region but forgot to repoint the MX" shows up as region-mismatch. Two constraints are worth knowing before you choose.

  • You cannot change a region in place. The official procedure is: delete the current domain, add the same domain again with the new region, then update your DNS records. Sending stops while you do it, so choose carefully the first time.
  • Region is not data residency. Resend states that region selection "controls where your emails are routed and sent from. It does not control where customer data is stored", and that all account data — email metadata, logs and API records — "is stored in the United States regardless of the sending region you select". Picking Tokyo does not keep your data in Japan. On engagements with data residency requirements, surface that sentence during requirements gathering rather than after signing.

Why the Return-Path is a separate subdomain

By default the Return-Path — the envelope sender, where bounces come back to — uses the send subdomain, and customReturnPath changes it. Resend describes its role as: "the custom return path is used for SPF authentication, DMARC alignment, and handling bounced emails."

This is the key to how SPF and DMARC relate. SPF authenticates the envelope MailFrom domain — the Return-Path — not the domain in the From: header. Send with From: noreply@example.com and the record SPF actually evaluates is the one on send.example.com. The reason DMARC still passes despite the mismatch is that alignment is relaxed by default. Using the RFC's own alignment example: if SPF passes with a MailFrom domain of cbg.bounces.example.com and the From: is payments@example.com, the organizational domains match, so the identifiers are aligned in relaxed mode but not in strict mode.

In other words, setting aspf=s in your DMARC record makes SPF alignment fail against Resend's default setup (a Return-Path on send.). DMARC as a whole still passes as long as DKIM is aligned, but you are running on one engine. Unless you have a specific reason, leave aspf at its relaxed default. For reference, customReturnPath must be 63 characters or less, start with a letter, end with a letter or number, and contain only letters, numbers and hyphens — and Resend warns to "avoid setting values that could undermine credibility (e.g. testing), as they may be exposed to recipients."


The SPF 10 DNS lookup limit

The most common SPF operational failure is adding one more include: to an existing record until it blows the limit. RFC 7208 §4.6.4 is unambiguous.

The following terms cause DNS queries: the "include", "a", "mx", "ptr", and "exists" mechanisms, and the "redirect" modifier. SPF implementations MUST limit the total number of those terms to 10 during SPF evaluation, to avoid unreasonable load on the DNS. If this limit is exceeded, the implementation MUST return "permerror".

permerror means "cannot evaluate", not "pass". SPF effectively dies — and what breaks is not the entry you just added but the domain's entire SPF evaluation.

Counts toward the limitDoes not count
The include: a mx ptr exists mechanisms and the redirect= modifierThe all ip4: ip6: mechanisms and the exp= modifier

RFC 7208 also says implementations SHOULD limit "void lookups" (queries returning an empty answer or NXDOMAIN) to two, and that exceeding the limit produces a permerror — which is what catches you when you forget to remove the include: for a service you stopped using. The ptr mechanism has the section heading "ptr (do not use)" in §5.5 and is explicitly documented as SHOULD NOT be published. Microsoft's high-volume sender FAQ makes the same point: "If you exceed 10 DNS lookups, your SPF check might fail. Tools exist to 'flatten' your record or reduce the number of includes."

There is good news specific to Resend here. Resend's SPF record sits on its own send. subdomain, so it does not consume the 10-lookup budget of your apex SPF record. The classic failure of stacking Google Workspace and three other SaaS includes onto the apex and then breaking it by adding one more is structurally unlikely with Resend.

Let me be precise about where that comes from. This is not a guarantee Resend's documentation makes — it is my own conclusion, drawn from the fact that SPF is evaluated per domain (RFC 7208) plus the fact that Resend places its records on send. The official docs never mention the 10-lookup limit at all. Confirm it in your own environment with dig txt send.example.com and dig txt example.com before you rely on it. Two caveats:

  • If send.example.com already has an SPF TXT or an MX record, that is a different problem. RFC 7208 §3.2 forbids multiple records under the same owner name, so do not add Resend's as a second one — reconcile them into a single record. Likewise, remove any pre-existing MX before adding Resend's.
  • Resend generates ~all (softfail). The official docs say nothing at all about hardening that to -all. It is neither recommended nor discouraged territory, so if you change it, verify the blast radius yourself.
  • Size limits bite too. RFC 7208 notes that a character-string in a TXT record maxes out at 255 octets, that an SPF record SHOULD stay small enough for the query result to fit within 512 octets, and that answers fit in UDP packets when the combined length stays under 450 octets.

Growing a DMARC policy

Three steps, in order

Resend's official procedure has three steps, and not going straight to reject is the most important part of it.

Step 1  v=DMARC1; p=none; rua=mailto:dmarcreports@example.com;
        ↓ read the reports; confirm every sending source aligns via SPF or DKIM
Step 2  v=DMARC1; p=quarantine; rua=mailto:dmarcreports@example.com;
        ↓ confirm nothing landing in spam is legitimate mail of yours
Step 3  v=DMARC1; p=reject; rua=mailto:dmarcreports@example.com;
PolicyWhat the receiver does
p=none;Allow all email. Monitoring for DMARC failures only
p=quarantine;Send messages that fail DMARC to the spam folder
p=reject;Bounce delivery of emails that fail DMARC

In Resend's words: "It's a best practice to use quarantine or reject, but only do this once you know your messages are delivering and fully passing DMARC." The reason to go slowly is entirely practical. Resend is rarely the only thing sending from your domain. Accounting SaaS, CRM, applicant tracking, monitoring tools, an old internal cron job — publish p=none with a rua address and sending sources nobody remembered start showing up in the reports. Flip to reject before you have reconciled them and they vanish silently.

Build the rua endpoint first

rua is where aggregate reports (emails with an XML attachment) are sent. RFC 9989 states that if the tag is not provided, "Mail Receivers MUST NOT generate aggregate feedback reports for the domain". A DMARC record without rua is therefore a declaration with no way to observe its effect. Resend itself publishes an open-source DMARC Analyzer that turns DMARC XML reports into human-readable dashboards. The hosted browser version is at checkdmarc.email and the repository is resend/resend-dmarc-analyzer. It shows SPF and DKIM alignment results per source, which is exactly what you need during the p=none phase. It is built with Next.js, React Email and Resend, and can be self-hosted with automated report ingestion via Resend Receiving. Use Google Postmaster Tools alongside it.

The tag list, and where it currently disagrees

This is the single most "stale knowledge will burn you" item in the article. Resend's DMARC parameter table still lists pct, but RFC 9989 (May 2026) removed the pct tag.

TagMeaningStatus
vProtocol version. Always v=DMARC1, case sensitive, must come firstCurrent
pPolicy for the organizational domain: none / quarantine / rejectDowngraded to RECOMMENDED in RFC 9989; a record without it is treated as p=none
spPolicy for subdomainsCurrent
npPolicy for non-existent subdomainsNew in RFC 9989
tTest mode: y / n (default n)New in RFC 9989; the effective successor to pct
psdFlag indicating whether the domain is a public suffix domainNew in RFC 9989
adkim / aspfDKIM / SPF alignment mode: r (relaxed, the default) or s (strict)Current
rua / rufDestinations for aggregate / failure reportsCurrent (see the caveat on ruf below)
pctPercentage of messages subjected to filteringRemoved in RFC 9989, replaced by t=y/n

RFC 9989 spells out why pct went: "Operational experience showed that the 'pct' tag was usually not accurately applied, unless the value specified was either 0 or 100 (the default), and the inaccuracies with other values varied widely from one implementation to another." Because pct=0 was genuinely useful, the spec introduced t as a two-valued "testing" shorthand. t=y means a quarantine policy is applied as none, and a reject policy is applied as quarantine. Resend is refreshingly honest about the related risk — "While the DMARC protocol includes both pct and ruf parameters, they are not widely followed by mailbox providers. These settings may not be respected or followed" — and Microsoft states outright that it sends RUA to the addresses in your DMARC record but has no plans to send RUF. Do not build anything that depends on ruf.

The other significant RFC 9989 change is that the Organizational Domain is no longer derived from the Public Suffix List but by a "DNS Tree Walk": strip labels from the left while querying _dmarc., with a shortcut that keeps a domain with more than eight labels from producing more than eight DNS queries. If you use deeply nested subdomains, be aware that which _dmarc record applies can be interpreted differently than it was under RFC 7489.

p governs the organizational domain, sp its subdomains, and np non-existent subdomains. If you have consolidated sending onto notifications.example.com, you can lock the apex down with p=reject and raise only the sending subdomain in stages. BIMI (the inbox logo) adds a constraint here: Resend states that "for BIMI on a subdomain, the root or APEX domain must also have a DMARC policy of p=quarantine or p=reject in addition to the subdomain. If not, the subdomain will not be compliant to display a BIMI logo."


The Gmail, Yahoo and Microsoft bulk sender requirements

Since February 2024 the major receivers have published explicit sender requirements. The three differ in the details, and some widely repeated numbers are simply wrong. Here it is from primary sources only.

Gmail (personal accounts)Yahoo / AOLOutlook.com (consumer)
Bulk thresholdClose to 5,000+ messages to personal Gmail in 24h, counted per primary domain. Once you qualify, the status is permanentExplicitly refuses to publish a number5,000+ messages to Microsoft consumer services from the same 5322.From domain
All sendersSPF or DKIMSPF or DKIMNot specified separately
Bulk sendersSPF and DKIM and DMARC (p=none is fine)SPF and DKIM and DMARC (at least p=none, and DMARC must pass)SPF and DKIM and DMARC (at least p=none)
AlignmentFrom: aligned with the SPF or DKIM organizational domain (one is enough)Same, and relaxed alignment is stated as acceptableAt least one of SPF/DKIM aligns with the 5322.From domain
Spam rateTarget 0.10%, never reach 0.30%Below 0.3%, calculated on inbox-delivered mailNot published as a number
One-click unsubscribeRequired for marketing and subscribed mail. Only RFC 8058 headers count — mailto and landing pages do notRequired for marketing and subscribed mail. RFC 8058 highly recommended, mailto acceptable"Functional unsubscribe links" is a recommendation
Failure mode4.7.2x rate limiting, then 5.7.2x blockingSpam folder or rejectionJunk folder, then rejection with 550 5.7.515

A few widely circulated errors worth correcting:

  • "Yahoo's bulk threshold is 5,000/day" is not Yahoo's position. Yahoo's FAQ states that a bulk sender is "an email sender sending a significant volume of mail. We will not specify a volume threshold." Google and Microsoft are the ones publishing 5,000/day.
  • Remembering only "0.3% is the Gmail requirement" is dangerous. The monitoring section of the same page says to "keep spam rates reported in Postmaster Tools below 0.10% and avoid ever reaching a spam rate of 0.30% or higher". 0.30% is less a requirement than a cliff: cross it and you become ineligible for mitigation.
  • Gmail's bulk sender status is permanent. "Senders who meet the above criteria at least once are permanently considered bulk senders," and changes in sending practice do not undo it.
  • Gmail's sender guidelines do not apply to Google Workspace accounts. They apply to personal @gmail.com and @googlemail.com addresses.
  • Important if you send to Japan: Yahoo states plainly that "Yahoo Japan is a separate entity, and we cannot speak to their plans as we do not coordinate with them." Do not apply the Yahoo column above to Yahoo! JAPAN.

DKIM key length is genuinely inconsistent across sources. Google states that "sending to personal Gmail accounts requires a DKIM key of 1024 bits or longer" and recommends 2048 where the DNS provider supports it. Resend, on the other hand, signs with 1024-bit keys and states that it does not support 2048-bit DKIM, citing RFC 8301 §3.2's 1024-bit minimum for verifiers and the fact that Gmail, Outlook, Yahoo and Apple all accept 1024-bit signatures. That same §3.2, though, says signers SHOULD use keys of at least 2048 bits, so Resend's position diverges from the recommendation. If your organisation has a hard 2048-bit requirement, confirm with Resend before you commit.

Resend enforces its own account-level guardrails on top of all this: you must keep your bounce rate under 4% and your spam rate under 0.08%, or sending may be temporarily paused. That is a different scope from the receiver-side thresholds in the table above (Gmail's 0.30% cliff, Yahoo's 0.3%) — what receivers measure themselves versus a Resend account policy — so do not merge the numbers. Receiving bounces and complaints is covered in Resend webhooks, signature verification and bounce handling, and implementing one-click unsubscribe in batch sending, scheduling and subscription management.


Subdomain strategy: split transactional from marketing

Resend's recommendation is consistent — send from a subdomain, not the root domain — for two reasons.

Reputation Isolation — Things happen. Maybe someone decides to DDOS your signup page and you get stuck sending tens of thousands of bounced verification emails to burner addresses. … If your root domain ends up with a jeopardized reputation, it can be a long road to recovery. Sending Purpose Transparency — A password reset email carries higher priority than a monthly product update. … By segmenting your sending purposes by subdomain, you give Inbox Providers clear indication of where to place your emails.

A practical split looks like this.

PurposeExample subdomainTrackingEnforced TLS
Auth, password resets, receiptsaccount.example.comOffWorth considering
Notifications and alertsnotifications.example.comOffOptional
Newsletters and announcementsupdates.example.comOnOptional

Resend gives the same example: "you can configure your newsletter for open and click tracking while keeping tracking disabled for your important transactional emails such as password resets." You can add and verify multiple subdomains of the same root domain, and each one must be configured and verified individually. Three things to watch:

  • Do not use a brand-adjacent lookalike domain. Resend calls out getacme-mail.com and acme-alerts.com by shape: they look suspicious to spam filters and can be treated as phishing or spoofing attempts.
  • A new subdomain is a new reputation. Resend publishes warm-up schedules; for a new domain the guideline is up to 150 messages on day 1 and up to 2,000 on day 7 (there is a faster table for moving an established domain). It also says to keep bounce rate below 4% and spam rate below 0.08% during warm-up, and to slow down and investigate the root cause if either climbs.
  • The per-plan domain limit decides whether this split is even available to you. As of August 2026 the official pricing page lists 1 custom domain on Free, 10 on Pro, 1,000 on Scale, and a negotiated number on Enterprise. Since every subdomain is added and verified as a domain in its own right, the three-way split above does not fit on the single-domain Free plan. Pricing and limits change, so confirm the current numbers on the official pricing page before designing around many subdomains. Free-plan sending volume is 100 transactional emails per day and 3,000 per month (inbound mail draws on the same quota), plus unlimited marketing email to up to 1,000 contacts per month.

What open and click tracking costs you

This is where 2024-era knowledge most needs updating. Open and click tracking is disabled by default for all domains, and it does nothing unless both of the following are true.

  1. open_tracking or click_tracking is enabled for the domain.
  2. A tracking subdomain (e.g. links.example.com) is configured and successfully verified.

The tracking subdomain is a CNAME pointing at links1.resend-dns.com. Resend's own reasoning is that "shared tracking domains can hurt deliverability because spam filters may flag links rewritten through shared domains as suspicious", and the dashboard's Deliverability Insights flags use of a shared domain as an improvement item. The mechanism itself drags deliverability down. Open tracking inserts a 1x1 transparent GIF and detects the open when that image is downloaded. Click tracking rewrites every link in the HTML body to route through the tracking subdomain and records the click mid-redirect. Both read to receivers as marketing signals.

Link and open tracking can be great for marketing emails but not for transactional emails. This kind of tracking can actually hurt your deliverability. … Also, Supabase has noted that link tracking is known for corrupting verification links, making them unusable for your users. — Resend Docs, deliverability for Supabase Auth emails

Link rewriting corrupting verification links is fatal for anything sending magic links or one-time tokens. Resend's "delivered but not received" page also lists "turn off open and click tracking" among its fixes.

The open rate is not an accurate number

Open rate is not a trustworthy measurement to begin with. Resend gives four reasons.

  • Gmail clipping — messages over 102KB are clipped and are not counted as an open unless the recipient views the entire message.
  • Inboxes that do not download images by default, or corporate firewalls that block or cache assets.
  • Inboxes that open the email before delivery — malware scanning or privacy protection (Apple Mail Privacy Protection) can fire an open event for a message nobody read.
  • Plain-text-only emails cannot be tracked at all, because open tracking depends on that 1x1 image.

Resend's conclusion: open tracking is "not a statistically accurate way of detecting if your users are engaging with your content", and while it does not affect whether the email is delivered, it "most likely will impact your inbox placement". The alternatives it suggests are tracking clicks instead, and tracking outside the inbox — page visits and conversions.

Once created, it cannot be removed

One more operational trap: after a tracking subdomain has been created it can only be changed, never removed. That is deliberate, to avoid breaking links in mail that has already gone out. So when you change it, the records table will show both an active and an inactive CNAME, and you must not delete the old one. The new record needs to be verified after the change, and until it is, the previous value is used. And if you remove a domain that has a tracking subdomain configured, the Resend-provisioned proxy goes with it and every link in previously sent mail using that subdomain breaks. To keep those links alive you would need to stand up your own proxy pointing at Resend's tracking DNS records before deleting the domain.

Open tracking records when a recipient opened a message, without them being aware of it. A good decision criterion is whether you can write down, in your own privacy policy, what you record and what you use it for. How this sits under Japan's personal information protection law depends on which fields you collect and how linkable they are to an individual, so I will not make a blanket claim here. If you send advertising or promotional email in Japan, the Act on Regulation of Transmission of Specified Electronic Mail adds its own opt-in rules and the five display obligations of Article 4 on top of all this. The scope of the statute, the retention period for consent records, the penalties and a worked footer implementation are covered in batch sending, scheduling and subscription management — go there when you design marketing email. It is a different layer from domain authentication, but it lands on your desk at the same moment.


Triage for "it says delivered but nobody received it"

This is the situation I get asked about most. Start by understanding precisely what Delivered means.

When an email is sent, it is marked as Delivered once the recipient server accepts it with a 250 OK response. However, the server can then direct the email to the inbox, queue it for later, route it to the spam folder, or even discard it. … Inbox Providers do not share any information on how the messages are later filtered. — Resend Docs, delivered but not received

Delivered means the receiving server accepted it — not that it reached an inbox. Everything after that is the receiver's internal processing, and Resend is never told about it. Get that wrong and you can search the sending stack forever without finding an answer.

[1. Check the log] What status does the Resend dashboard show for that email?
  ├─ Bounced ─────→ Permanent: the address does not exist. Remove it from the list
  │                 Transient: mailbox full, too large, content rejected. Design a retry
  ├─ Complained ──→ A complaint. It enters the suppression list; nothing more is sent there
  ├─ Send failed ─→ Classify on error.name (403 usually means a sender domain mismatch)
  └─ Delivered ───→ go to 2

[2. Check authentication] Do the headers show spf=pass / dkim=pass / dmarc=pass?
  ├─ any fail ────→ go to 3
  └─ all pass ────→ go to 4

[3. Measure DNS] dig send. / resend._domainkey. / _dmarc. and rule out
  doubled hostnames, truncated values and region mismatches

[4. Check content and reputation]
  ├─ Do the links use the same domain you send from?
  ├─ Is a plain text version (text) included?
  ├─ Is the sender a no-reply address?
  ├─ Is the body over 102KB? (Gmail clips it)
  ├─ Does turning open and click tracking off change anything?
  └─ Check domain reputation in Postmaster Tools

[5. Suspect the receiving side]
  ├─ Enterprise mail security (Mimecast / Proofpoint / Barracuda) quarantine
  ├─ Personal filters, the Promotions tab, deleted folders
  └─ If found, ask them to mark it Not Spam and allowlist the sending domain

Know how suppressions behave, too. Addresses added by a bounce or a complaint apply to your entire team and are skipped across all of your domains and subdomains. When you are sure you sent something but nothing appears in the log, look there. Note also that Gmail and Google Workspace are known not to return complained events, so complaints from Gmail recipients are invisible to Resend. There are two classic causes of a 403 on the send itself: the resend.dev test domain can only send to the email address on your own account, and verifying sending.domain.com while sending from an @domain.com address is a domain mismatch. The verified domain and the from domain must match exactly, subdomain included.

Error classification and retry design are covered in Resend idempotency, retries and error handling, and the Route Handler implementation in Resend with the Next.js App Router. When I finally fixed the outage this article opens with, I did not stop at the DNS: I added sender fallback, retries and an idempotency key. DNS will break again some day, and the sending code should have a layer that does not drop leads when it does.


Checklist

After touching DNS, verify in this order.

  • The SPF MX and TXT are on send. (or your custom Return-Path subdomain)
  • The DKIM TXT is on resend._domainkey. and the p= value is not truncated
  • The DMARC TXT is on _dmarc. under the organizational domain, not on send.
  • dig +short TXT send.example.com.example.com returns nothing (no doubled hostname)
  • Your domain is not appended to the MX value, and its region matches the domain's region
  • There is no duplicate SPF TXT or MX under send.
  • Your apex SPF record stays within 10 DNS lookups (audit those include: entries)
  • DMARC starts at p=none with rua, and you are actually reading the reports
  • aspf=s is not set (it breaks SPF alignment against Resend's default setup)
  • Open and click tracking is off on transactional domains; if you use tracking, old CNAMEs are still in place
  • The from domain matches the verified domain exactly, subdomain included
  • A plain text version (text) is included and the sender is not a no-reply address
  • You have a way to monitor bounce rate under 4% and spam rate under 0.08%

Wrapping up

Domain authentication is invisible once it works, which is exactly why people look in the wrong place when it stops working. I re-read the Route Handler over and over; what was actually broken was a DNS hostname. Three things to take away.

  • The placement splits four ways and DMARC is the exception. SPF on send., DKIM on resend._domainkey., DMARC on _dmarc. under the organizational domain. Enter relative names only, and confirm the real FQDNs with dig.
  • DMARC is an operation, not a declaration. Observe with p=none and rua, reconcile the sending sources you did not know about, then raise the policy. And note that RFC 9989 removed pct in favour of t=y/n.
  • Leave tracking off by default. It requires a verified custom tracking subdomain, link rewriting can corrupt verification links, and the open rate is not statistically accurate to begin with.

If you are still choosing a provider, Resend compared with SendGrid, SES and Postmark covers that decision. Otherwise, run dig +short TXT send.<your-domain>.<your-domain> once against your own domain. If anything comes back, that is the record to fix today.

This article is based on the Resend documentation (Domains / DMARC / Tracking / Deliverability, as of August 2026), RFC 7208, RFC 6376, RFC 8301 and RFC 9989, and the sender requirement pages published by Google, Yahoo and Microsoft, reorganised around production operating decisions. Specifications, limits and pricing change, so confirm the current values on the official pages before adopting anything in production.

Frequently asked questions

I added the DNS records but Resend still will not verify my domain. What should I check first?
The hostname. Resend's SPF records (MX and TXT) belong on the send subdomain and DKIM on resend._domainkey. Most DNS providers auto-append your domain to the host field, so typing send.example.com silently creates send.example.com.example.com. Enter only send and resend._domainkey as the official guides instruct, then confirm the real FQDNs with dig. If the records are not detected within 72 hours the domain flips to failed.
Do I need both SPF and DKIM?
For DMARC itself, passing either one is enough. Resend's docs state it plainly: an email passes DMARC if either SPF or DKIM passes, and fails only if both fail. However, the bulk sender requirements from Gmail, Yahoo and Microsoft all require SPF and DKIM to be configured. Verifying a domain in Resend sets up both automatically, so in practice: configure both, and remember that DMARC only needs one of them to be aligned.
Can I go straight to p=reject on DMARC?
No. Resend's official sequence is three steps: publish p=none with a rua address, read the reports until every sending source is authenticated and aligned, then move to quarantine and finally reject. It is extremely common for an accounting SaaS, a CRM or an old internal cron job to be sending from the same domain without anyone remembering. Jumping to reject makes all of those disappear silently. Start by building somewhere to receive the rua reports.
Should I enable open tracking?
Not for transactional email. Resend disables it by default and recommends disabling open rates for transactional mail. There are two reasons: the 1x1 pixel and the rewritten links read as marketing signals to receivers, and the numbers are not statistically accurate anyway because of Apple Mail Privacy Protection and blocked images. Resend also cites Supabase noting that link tracking is known for corrupting verification links.
Should I send from the root domain or a subdomain?
A subdomain. Resend gives two reasons: reputation isolation (you can quarantine a compromised subdomain, whereas a damaged root domain can be a long road to recovery) and sending purpose transparency (splitting password resets from monthly updates helps inbox providers triage). Do not reach for a brand-adjacent lookalike domain instead: those get flagged as phishing.
Resend signs with 1024-bit DKIM. Is that a problem?
Gmail requires at least 1024 bits for personal Gmail accounts and recommends 2048. Resend signs with 1024-bit keys and states explicitly that it does not support 2048-bit DKIM. RFC 8301 requires signers to use at least 1024 bits and says they SHOULD use at least 2048, so the requirement and the recommendation pull in different directions here. Resend invites you to get in touch if your security team has a hard 2048-bit requirement, so confirm before you commit.

References

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

Are your emails landing in spam — or not landing at all?

Deliverability recovery and email-provider selection, as a technical advisor

"Only Gmail rejects us." "Tightening DMARC started blocking our legitimate mail." "Should we be on Amazon SES or Resend?" From an on-the-ground audit of your authentication records (the SPF 10-DNS-lookup limit, DKIM, DMARC alignment) through sending-domain separation, compliance with the Gmail / Yahoo / Microsoft bulk-sender requirements, and vendor selection with a volume-based cost model — we decide it together.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

Also worth reading