# Resend OTP

> Step-by-step guide to configuring Resend for email OTP and password reset in the Convex Auth starter — the account, domain verification, the API key, and the from address you have to change.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/convex/resend
- Markdown: https://ui.ahmedbna.com/docs/convex/resend.md

---

Two flows in the starter send mail through [Resend](https://resend.com): the
email OTP tab, and the password-reset code.

## Before you start

- A [Resend](https://resend.com) account. The free tier is enough to test with.
- A domain you own, with access to its DNS records. You can create an API key
  and send to your own address without one, but you cannot send to anyone else
  until a domain is verified.

## Set it up

**1.** Create a Resend account

Sign up at [resend.com](https://resend.com) and confirm your email address.
The onboarding checklist that greets you covers exactly two things — adding an
API key and sending a test email — and you will do both below.

![Resend onboarding checklist: add an API key and send your first email](https://demo.ahmedbna.com/0434-resend-setup-04.png)

![Resend onboarding checklist with both steps completed](https://demo.ahmedbna.com/0435-resend-setup-05.png)

**2.** Add your domain

1. Click **Domains** in the left sidebar.
2. Click **Add Domain**.
3. Enter the domain or subdomain you want to send from, pick the region
   closest to your users, and click **Add Domain**.

A subdomain like `mail.yourdomain.com` is worth considering: it keeps the
sending reputation of your transactional mail separate from whatever else uses
the root domain.

![Empty Resend Domains page with the Add Domain button highlighted](https://demo.ahmedbna.com/0436-resend-setup-06.png)

![Add Domain form with a domain name and region selected](https://demo.ahmedbna.com/0437-resend-setup-07.png)

**3.** Add the DNS records

Resend generates the records for you — an `MX` and two `TXT` records for DKIM
and SPF, marked **Required**, plus an optional `TXT` record for DMARC. Copy
each one into your DNS provider exactly as shown; do not retype the values.

Then click **I've added the records**. Verification usually lands in a few
minutes, but DNS propagation can take up to 24 hours.

![Resend DNS Records panel showing the required DKIM/SPF records and the optional DMARC record](https://demo.ahmedbna.com/0438-resend-setup-08.png)

> Resend marks DMARC "Recommended" rather than "Required" — mail sends without
> it. Adding a policy like `v=DMARC1; p=quarantine;
>   rua=mailto:dmarc@yourdomain.com` tells receiving servers what to do with mail
> that fails SPF or DKIM, which is what keeps you out of spam folders as your
> volume grows.

**4.** Create an API key

1. Click **API Keys** in the left sidebar.
2. Click **Create API Key**.
3. Name it something you'll recognise later, leave **Permission** on **Full
   access**, and scope it to your domain if you want to restrict it.
4. Click **Add**.
5. Copy the key. Resend shows it once and never again.

![Resend API Keys page with the Create API Key button highlighted](https://demo.ahmedbna.com/0431-resend-setup-01.png)

![Add API Key dialog with a name, Full access permission and domain scope](https://demo.ahmedbna.com/0432-resend-setup-02.png)

![View API Key dialog warning that the key is only shown once](https://demo.ahmedbna.com/0433-resend-setup-03.png)

**5.** Set it on your Convex deployment

```bash
npx convex env set AUTH_RESEND_KEY re_your_key_here
```

Or add `AUTH_RESEND_KEY` in the Convex dashboard under **Settings →
Environment Variables**.

**6.** Use separate keys for dev and prod

`npx convex env set` writes to your development deployment. Production is a
separate deployment with its own environment, so set it there too:

```bash
npx convex env set AUTH_RESEND_KEY re_prod_key_here --prod
```

Separate keys mean you can revoke one without taking the other down, and the
Resend logs tell you which environment a send came from.

> Both `convex/resendOTP.ts` and `convex/passwordReset.ts` ship with a
> placeholder `from: 'BNA UI <ahmdabdelsamea@gmail.com>'`. Change it to an
> address at your verified domain in both files — Resend will reject sends
> from a domain you don't own, and even if it didn't, your users would be
> getting mail from someone else's inbox.

## Test it

```bash
npx convex dev
```

Try the OTP tab and the forgot-password flow. Without `AUTH_RESEND_KEY` set,
both fail — the sign-up and sign-in flows in the password tab work regardless,
since they don't send mail.

Then open **Logs** in the Resend dashboard. Every send shows up there, whether
it was delivered, bounced or rejected, along with the API error if there was
one.

## No local mail catcher

Supabase's local stack ships Inbucket, which catches every message instead
of delivering it. There is no Convex equivalent — testing email here always
sends through Resend, dev key or not. Resend's dashboard **Logs** is the
closest thing to it: every send, delivered or not, shows up there.

## When it does not work

| What you see                      | Usually                                                                |
| --------------------------------- | ---------------------------------------------------------------------- |
| Email never arrives, no error     | `AUTH_RESEND_KEY` unset, or set on the wrong deployment (dev vs. prod) |
| Arrives in spam                   | Domain has no SPF/DKIM, or the from address isn't on that domain       |
| `Could not send email` thrown     | Check the Resend dashboard's Logs for the actual API error             |
| Works in dev, not after deploying | `AUTH_RESEND_KEY` was never set with `--prod`                          |
| Domain stuck on "Pending"         | DNS hasn't propagated yet, or a record was retyped instead of pasted   |

## Next

- [Google](/docs/convex/google) · [Apple](/docs/convex/apple)
- [Authentication](/docs/convex/auth)
- [Resend documentation](https://resend.com/docs)
