READ-ONLY PACKAGE PREVIEW

resend/references/domains.md

Version edbfece3f402.bb1 · MIT. This preview displays packaged text and does not execute code. Treat the contents as untrusted instructions.

← Return to resource and package checksum

Domains

Overview

Domains must be verified before sending. The workflow is: create domain, add DNS records to your provider, call verify, then poll until verified.

Create → Add DNS records → Verify → Poll status → Send

SDK Methods

Node.js

Operation Method Notes
Create resend.domains.create(params) Returns DNS records to configure
Get resend.domains.get(id) Returns domain with DNS records and status
List resend.domains.list({ limit?, offset? }) Paginated list
Update resend.domains.update(params) Update tracking, TLS, capabilities
Delete resend.domains.remove(id) Permanent — not .delete()
Verify resend.domains.verify(id) Triggers async DNS verification

Python

resend.Domains.create/get/list/update/remove/verify — same operations with snake_case params (e.g., custom_return_path, open_tracking, click_tracking).

Claiming a domain another Resend account already verified? See Claim a Domain — available in the Node.js, Python, Ruby, Go, Rust, and Java SDKs, and the CLI (resend domains claim).

Use a Subdomain

Prefer a subdomain (e.g., send.example.com) over the root domain:

  • No MX conflicts with existing email (Google Workspace, Microsoft 365)
  • Isolated reputation — if transactional reputation gets damaged, your root domain is unaffected
  • DNS records (DKIM CNAMEs, MX, TXT) go on the subdomain, not the root

Create Domain

const { data, error } = await resend.domains.create({
  name: 'send.acme.com',           // subdomain recommended
  region: 'us-east-1',              // immutable after creation
  customReturnPath: 'bounce',       // optional: [email protected] — helps DMARC alignment
  openTracking: false,
  clickTracking: false,
});
if (error) {
  console.error(error);
  return;
}

// data.records contains DNS records to add:
// [{ type: 'MX', name: '...', value: '...' }, { type: 'TXT', ... }, ...]
console.log(data.id);      // domain ID for later calls
console.log(data.records);  // add these to your DNS provider
domain = resend.Domains.create({
    "name": "send.acme.com",
    "region": "us-east-1",
    "custom_return_path": "bounce",
    "open_tracking": False,
    "click_tracking": False,
})
# domain["records"] has the DNS entries to configure

Verify Flow

After adding DNS records to your provider, trigger verification and poll:

// Trigger verification (returns immediately)
await resend.domains.verify(data.id);

// Poll until verified (DNS propagation can take minutes to hours)
const { data: domain } = await resend.domains.get(data.id);
console.log(domain.status); // 'pending', 'verified', 'failed'

Verify DNS Propagation

dig TXT send.example.com +short
dig MX send.example.com +short
dig CNAME resend._domainkey.send.example.com +short

Update Domain

const { data, error } = await resend.domains.update({
  id: 'domain_abc123',
  clickTracking: true,
  openTracking: true,
  tls: 'enforced',
  capabilities: { sending: 'enabled', receiving: 'enabled' },
});

Claim a Domain

Claiming takes over a domain another Resend account has already verified. The domain transfers to your account as a brand-new domain with fresh DKIM keys, so the previous account's DNS records can't be reused — you must update DNS and verify before sending or receiving.

Claim → Add TXT proof to DNS → Verify claim → (completed) → Update DKIM in DNS → Verify domain → Send

Claim methods are available in the Node.js (resend >= 6.14.0), Python (resend >= 2.34.0), Ruby (resend >= 1.6.0), Go (resend-go/v3 >= 3.11.0), Rust (resend-rs >= 0.26.1), and Java (resend-java >= 4.16.0) SDKs, plus the CLI (resend domains claim) and REST API. Not yet in the PHP or .NET SDKs.

Operation Method Notes
Start claim resend.domains.claims.create({ name }) Accepts name (required) + optional region, customReturnPath, openTracking, clickTracking, trackingSubdomain (domains.create body minus tls/capabilities). Returns a domain_claim with domain_id + the TXT record to add
Get claim resend.domains.claims.get(domainId) Latest claim for the placeholder domain — poll status
Verify claim resend.domains.claims.verify(domainId) Triggers async DNS proof + transfer (not synchronous)

Method naming per SDK: Python resend.Domains.Claims.create/get/verify (async: create_async/get_async/verify_async), Ruby Resend::Domains::Claims.create/get/verify, Go client.DomainClaims.Create(&CreateDomainClaimRequest{...})/Get(domainId)/Verify(domainId) (+ *WithContext), Rust domains.claim(opts)/get_claim(id)/verify_claim(id), Java resend.domains().claims().create(ClaimDomainOptions)/get(id)/verify(id).

// 1. Start the claim — returns the placeholder domain id + TXT record to add
const { data: claim, error } = await resend.domains.claims.create({
  name: 'send.acme.com',
});
if (error) {
  console.error(error);
  return;
}
console.log(claim.domain_id); // placeholder domain id for later calls
console.log(claim.record);    // { type: 'TXT', name, value, ttl } — add to DNS

// 2. After adding the TXT record, trigger verification
await resend.domains.claims.verify(claim.domain_id);

// 3. Poll until the claim status is 'completed'
const { data: latest } = await resend.domains.claims.get(claim.domain_id);
console.log(latest.status); // 'pending' | 'verified' | 'completed' | 'blocked' | ...

// 4. Once 'completed', the transferred domain has NEW DKIM records:
//    fetch them, update your DNS, then verify the domain itself.
const { data: domain } = await resend.domains.get(claim.domain_id);
console.log(domain.records); // add these to DNS, then:
await resend.domains.verify(claim.domain_id);
# 1. Start the claim — returns the placeholder domain id + TXT record to add
claim = resend.Domains.Claims.create({"name": "send.acme.com"})
print(claim["domain_id"])  # placeholder domain id for later calls
print(claim["record"])     # {type: 'TXT', name, value, ttl} — add to DNS

# 2. After adding the TXT record, trigger verification
resend.Domains.Claims.verify(domain_id=claim["domain_id"])

# 3. Poll until the claim status is 'completed'
latest = resend.Domains.Claims.get(domain_id=claim["domain_id"])
print(latest["status"])  # 'pending' | 'verified' | 'completed' | 'blocked' | ...

# 4. Once 'completed', fetch the transferred domain's NEW DKIM records,
#    update DNS, then verify the domain itself.
domain = resend.Domains.get(claim["domain_id"])
print(domain["records"])  # add these to DNS, then:
resend.Domains.verify(claim["domain_id"])

A blocked status means a safety check failed — inspect blocked_reason (grace_period, recent_owner_activity, pending_scheduled_emails).

Parameter Reference

Parameter Values Default Notes
region us-east-1, eu-west-1, sa-east-1, ap-northeast-1 us-east-1 Immutable after creation
customReturnPath string (e.g., "bounce") none Results in [email protected] — helps DMARC alignment
tls opportunistic, enforced opportunistic
openTracking true, false Domain default
clickTracking true, false Domain default
capabilities { sending: 'enabled'\|'disabled', receiving: 'enabled'\|'disabled' } sending enabled
trackingSubdomain / tracking_subdomain string none Subdomain for click/open tracking URLs (e.g., "track" → track.example.com). Set on create or update

Common Mistakes

Mistake Fix
Using root domain when a subdomain would be safer Consider send.example.com — avoids MX conflicts with existing email and isolates reputation
Sending before DNS records are added Create returns DNS records — add them to your provider first, then verify
Expecting verify() to be synchronous Verify triggers async check — poll with get() to confirm status
Trying to change region after creation Region is immutable — delete and recreate the domain
MX record value doesn't match region MX must be region-specific (feedback-smtp.{region}.amazonses.com) — use the exact records from the create response
Cloudflare proxy mode enabled Disable proxy (orange → gray cloud) for all Resend DNS records — CNAME proxy breaks DKIM verification
DNS provider auto-appends domain name GoDaddy/Namecheap may turn resend._domainkey.send.acme.com into resend._domainkey.send.acme.com.acme.com — add a trailing dot or enter just the subdomain portion
DNS records added to root instead of subdomain DKIM CNAMEs go on resend._domainkey.send.example.com, not resend._domainkey.example.com
Calling .delete() SDK method is .remove()
Deleting a domain accidentally Delete is permanent with no undo — verify intent before calling
Using enforced TLS with recipients that don't support it Use opportunistic (default) unless you know all recipients support TLS
Not checking error in Node.js SDK returns { data, error }, does not throw — always destructure and check
Forgetting region on create Defaults to us-east-1 — set explicitly for EU/SA/AP data residency requirements
Reusing the old account's DNS records after a claim A claim issues new DKIM keys — fetch the transferred domain with domains.get(), update DNS, then domains.verify()
Treating the claim as done at completed completed only means the transfer finished — the domain still needs its new DKIM records in DNS and a domains.verify() to send
Expecting claims.verify() to be synchronous It triggers an async DNS proof + transfer — poll claims.get() for status
Looking for a claim method in PHP or .NET Claims are in the Node.js, Python, Ruby, Go, Rust, and Java SDKs (plus CLI and REST API) — PHP/.NET don't support them yet