Ralph LargoRL
3 min read

How to set up Resend as SMTP on Supabase

Supabase's built-in mailer only delivers to your own team and caps at two messages an hour. Swapping in Resend takes ten minutes, and the step everyone forgets is the rate limit.

  • supabase
  • resend
  • email
  • auth

The SMTP server Supabase gives every project is a demo, not a dependency. It refuses to deliver to any address outside your organisation, it is capped at two messages an hour, and it carries no delivery guarantee. The first real signup breaks it.

I reach for Resend because it speaks plain SMTP, so Supabase needs no code change at all. Six fields in a settings page and you are done.

1. Verify a sending domain in Resend

Resend will not send from a domain it cannot prove you own. Go to Domains, add yours, and copy the DKIM and SPF records it hands you into your DNS. Verification usually clears in a few minutes. The domain has to read Verified before anything below will work.

2. Create an API key

Under API Keys, create one with sending permission, scoped to the domain you just verified. The value is shown once. That re_... string is the SMTP password, not your account password.

3. Fill in Supabase custom SMTP

Open Project Settings → Authentication → SMTP Settings and turn on Enable Custom SMTP. Every value is fixed except the last two.

Host
smtp.resend.com
Port
465
Username
resend
Password
re_your_api_key
Sender email
no-reply@yourdomain.com
Sender name
Your product

The username really is the literal word resend, for every account on the platform. It looks wrong. It is not. Port 465 is implicit TLS and it is the one I reach for first, with 587 for STARTTLS if something on the path blocks it, and 2465 and 2587 as the alternates.

The sender address has to sit on the domain you verified in step one. Get that wrong and Resend rejects the message rather than the connection, which makes it look like Supabase is at fault. That is the failure mode worth memorising.

4. Raise the email rate limit

This is the step everyone skips, and it is the one that will page you. Turning on custom SMTP does not hand you unlimited sending. It moves you from two messages an hour to thirty, which is a number you can still hit on a good morning.

Go to Authentication → Rate Limits and raise Rate limit for sending emails to something your signup volume can live with, staying under what your Resend plan allows. The free tier is 100 emails a day and 3,000 a month.

The same thing without the dashboard

If the project is provisioned from CI, the Management API sets the identical fields. Generate a personal access token first.

configure-smtp.sh
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
  -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "external_email_enabled": true,
    "smtp_host": "smtp.resend.com",
    "smtp_port": 465,
    "smtp_user": "resend",
    "smtp_pass": "re_your_api_key",
    "smtp_admin_email": "no-reply@yourdomain.com",
    "smtp_sender_name": "Your product"
  }'

Self-hosting instead? The same six values go into your .env as SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_ADMIN_EMAIL and SMTP_SENDER_NAME. Every service needs a restart to pick them up.

Checking it actually works

  1. Trigger a magic link or a password reset against an address that is not on your Supabase team. That address is exactly what the built-in mailer used to refuse, so a delivery here proves the swap took.
  2. Watch Emails in the Resend dashboard. A send that appears there and then bounces is a DNS problem. A send that never appears at all is a Supabase problem. That one split saves you an hour of guessing.
  3. Read the Supabase auth logs if nothing shows up on either side.

When it fails, it is nearly always one of three things: the sender domain is not verified yet, the API key was pasted with a trailing space, or the port is blocked. None of them produce a helpful error message. Check them in that order.

Comments

Sign in with GitHub to leave a note. Threads live in this repo’s discussions.