What you're doing and why it's painful
Launching a production app involves at least four separate dashboards: your domain registrar or DNS provider, your email service, your payment processor, and your deployment platform. Each one has its own quirks, and they all need to talk to each other. Miss a DNS record and your emails land in spam. Use the wrong signing secret and your webhooks silently fail.
This guide walks through the whole stack in order — DNS first, then email, then payments, then environment variables — so nothing depends on something that isn't set up yet.
Prerequisites
- A domain name (registered anywhere, but this guide uses Cloudflare for DNS)
- A Cloudflare account
- A Resend account (for transactional email)
- A Stripe account (test mode is fine to start)
- A Vercel project deployed and ready
Part 1 — DNS (Cloudflare)
Step 1 — Point your domain to Cloudflare
If you registered elsewhere, update your registrar's nameservers to Cloudflare's. In the Cloudflare dashboard, add your domain — it will show you two nameservers to set:
ns1.cloudflare.com
ns2.cloudflare.com
Update these in your registrar's control panel (GoDaddy, Namecheap, Google Domains, etc.). Propagation takes anywhere from a few minutes to a few hours.
Step 2 — Add DNS records for Vercel
In Cloudflare DNS, add:
| Type | Name | Value | Proxy |
|---|---|---|---|
| A | @ (apex) | 76.76.21.21 | Proxied |
| CNAME | www | cname.vercel-dns.com | Proxied |
Then in your Vercel project, go to Settings → Domains and add your domain. Vercel will confirm it's resolving correctly.
Part 2 — Email (Resend + Cloudflare DNS)
Step 3 — Add your domain in Resend
Log in to resend.com, go to Domains, and click Add domain. Enter your domain name. Resend will display the DNS records you need to add.
Step 4 — Add the email authentication DNS records
In Cloudflare DNS, add all records Resend provides. Typically:
SPF (authorises Resend to send on your behalf):
Type: TXT
Name: @
Value: v=spf1 include:amazonses.com ~all
(use the exact value Resend gives you)
DKIM (cryptographic signature on outgoing mail):
Type: TXT
Name: resend._domainkey
Value: (long key string from Resend)
DMARC (tells receivers what to do on failure):
Type: TXT
Name: _dmarc
Value: v=DMARC1; p=none; rua=mailto:[email protected]
After adding them, return to Resend and click Verify domain. The status will show Active once the records propagate.
Part 3 — Payments (Stripe)
Step 5 — Create your product and price
In the Stripe Dashboard (start in test mode):
- Go to Product catalogue → Add product
- Name your product and add a description
- Add a price — set it to Recurring if selling subscriptions, or One time for one-off purchases
- Note the price ID (
price_xxx) — you'll use this in your checkout code
Step 6 — Create a webhook endpoint
- Go to Developers → Webhooks → Add endpoint
- Enter your production endpoint:
https://yourdomain.com/api/webhooks/stripe - Select events to listen for — at minimum:
checkout.session.completed,payment_intent.payment_failed - Click Add endpoint
- Copy the Signing secret (starts with
whsec_) — you'll need it in Part 4
Part 4 — Environment Variables (Vercel)
Step 7 — Add all secrets to Vercel
In Vercel → Project Settings → Environment Variables, add the following for the Production environment:
| Variable | Where to find it |
|---|---|
RESEND_API_KEY | Resend dashboard → API Keys → Create API key |
STRIPE_SECRET_KEY | Stripe Dashboard → Developers → API keys (secret key) |
STRIPE_WEBHOOK_SECRET | The whsec_ signing secret from Step 6 |
Set each variable to Production scope. After adding all three, go to Deployments and trigger a redeploy (or push a new commit).
Verify everything is working
DNS: Run dig yourdomain.com — the answer should show Cloudflare's IP.
Email: Send a test email using Resend's dashboard API tester. Check the response headers for dkim=pass and spf=pass.
Stripe: Use the Stripe CLI (stripe listen --forward-to localhost:3000/api/webhooks/stripe) to test locally first. For production, send a test webhook from the Stripe Dashboard → Webhooks → your endpoint → Send test event.
Vercel: In your deployment, log process.env.RESEND_API_KEY?.slice(0, 5) to confirm the variable loaded (never log the full value).
Common errors and gotchas
Emails landing in spam Missing or incorrect DKIM/SPF records. Use mail-tester.com to get a detailed report. The most common cause is adding the DKIM record under the wrong subdomain.
Stripe webhooks returning 400
Signing secret mismatch. Confirm STRIPE_WEBHOOK_SECRET in Vercel matches the whsec_ value from the Stripe Dashboard endpoint page — not the CLI secret.
www not resolving
In Vercel, you need to add both the apex domain and the www subdomain separately under Project Settings → Domains. Adding just one doesn't automatically cover the other.
Variables not loading after adding them to Vercel Environment variables only take effect on new deployments. Trigger a redeploy — existing deployments don't pick up changes.
Redirect loops on the domain Cloudflare SSL/TLS is set to Flexible. Change it to Full (strict) under SSL/TLS → Overview. Flexible causes loops when your origin also enforces HTTPS.