ShipAgent

Email

Send the site's mail through Resend from your own domain, verified with SPF and DKIM, and prove a real mail arrives.

The site can send sign-in links, codes and notices from your own domain when this check passes and the test mail is in your inbox:

pnpm tsx scripts/check-email.ts --to you@example.com

Its last line must be Email: all checks passed. Email sign-in (magic link and code) works from then on.

You

Value Where you get it
DEFAULT_ADMIN_EMAIL you choose it: an address on your own domain, such as hello@yourdomain.com. Not Gmail or Outlook: those domains cannot be verified
DEFAULT_ADMIN_NAME (optional) the sender name shown in inboxes; empty means the site name from config/site.ts
RESEND_API_KEY resend.com → sign up or log in → API Keys → Create API Key → Permission Full access → Add → copy the re_… key. It is shown once
DNS records you add them at your DNS provider (Cloudflare, Namecheap …). The agent prints the exact type, name and value of each

All mail is sent from DEFAULT_ADMIN_EMAIL, and admin alerts are sent to it. Use a mailbox someone reads, or one that forwards.

Use Full access while setting up: the check reads your domain's status, which a Sending access key cannot do.

The Resend click paths are as of late 2026 (check the dashboard, menus move).

When the agent prints the DNS records, add each one at your DNS provider exactly as shown, then tell the agent "done". Resend lists the same records on its Domains page:

Resend domain page listing the MX, SPF and DKIM records

Copy each name exactly. If your DNS panel shows the full name after saving, check that your domain appears in it only once. DNS changes take minutes, sometimes hours. For a domain on Cloudflare DNS, see Domain.

Agent

  • Writes the sender address and the Resend key into .env.local and runs the check to confirm the key works.
  • Adds your sender's domain (the part after @) to Resend and prints the SPF and DKIM records still missing.
  • After you say "done", reruns the check every few minutes until the domain reads verified. It reads the status from Resend, so it never has to ask you whether it worked.
  • Sends one real sign-in mail to the inbox you name. It is rendered from the app's own template and sent through the app's own mail code.
  • Asks you to confirm the mail arrived (check spam). In spam usually means a DNS record is missing.
  • Then tries the real flow: sign in by email, and both the link and the 6-digit code must arrive.

What a pass looks like

✓ sender DEFAULT_ADMIN_EMAIL = hello@yourdomain.com
✓ RESEND_API_KEY valid (1 domain(s) in the account)
✓ domain yourdomain.com verified (region us-east-1)
✓ test mail sent to you@example.com through lib/mail (resend), message id <id>

Email: all checks passed

Before the records are in, the domain line reads ✗ domain yourdomain.com is pending (or not_started) and the check lists the records to add.

What the app sends

All mail goes out from DEFAULT_ADMIN_EMAIL. Without it, email sign-in and team invitations fail and the other mails are skipped.

Mail To
Sign-in link (valid 5 minutes) the user
Sign-in code (6 digits, valid 10 minutes) the user
Welcome mail, which also adds the user to your contacts each new user
Newsletter welcome the subscriber
Team invitation the invitee
Renewal payment failed the user
Charge refunded after a fraud warning the user
Credits failed to land after a payment DEFAULT_ADMIN_EMAIL
Stripe fraud warning DEFAULT_ADMIN_EMAIL

The app sends no purchase receipt. A paid order is announced on Discord (when DISCORD_WEBHOOK_URL is set) and in the event monitor.

User feedback alerts are separate: they use their own ALERT_RESEND_KEY, ALERT_FROM and ALERT_TO, and stay off unless all three are set.

Alternative: Cloudflare Email Sending. Instead of Resend, the app can send through Cloudflare: DEFAULT_EMAIL_PROVIDER=cloudflare, CLOUDFLARE_EMAIL_API_TOKEN (a token with the permission Account → Email Sending → Send) and CLOUDFLARE_ACCOUNT_ID, with your domain set up for Email Sending in the Cloudflare dashboard. The check cannot read the domain status there, so a delivered test mail is the only proof.

When it fails

The check says What it means Fix
RESEND_API_KEY rejected by Resend wrong or deleted key create a new key and paste it
… (Sending access key …) and cannot show whether the sender's domain is verified the key can send but not read domains use a Full access key during setup, or the agent proves it with a test mail
the sender's domain … is not in the Resend account the domain was never added, or was added under another Resend team the agent adds it
domain … is pending or not_started DNS records missing or not propagated yet add the printed records; the agent checks again in a few minutes
domain … is failed a record is wrong: a typo, or the domain appended twice to the name compare each printed record with your DNS panel; the next check restarts verification
test mail not sent (resend): … naming the domain the sender is on another domain than the verified one DEFAULT_ADMIN_EMAIL must be on exactly the verified domain
test mail sent but not in the inbox spam folder, or SPF or DKIM missing check spam; recheck the records
sign-in mail works in the check but not in the app the app runs with other env values restart the app; in production, sync the deployment env

Going live

  • DEFAULT_ADMIN_EMAIL, DEFAULT_ADMIN_NAME and RESEND_API_KEY go into the production env, then the site is redeployed. For production you may use a second key with Sending access only.
  • NEXT_PUBLIC_SITE_URL must be your real domain: the welcome and newsletter mails build their unsubscribe link from it, and the invitation mail its accept link.
  • Resend has daily and monthly sending quotas. Check your plan before a launch with traffic.