Docs / Supabase

Supabase custom SMTP for auth emails

Supabase Auth sends sign-up confirmations, magic links and one-time codes, invitations, email change and password reset messages. Out of the box they go through a built-in service meant for testing: it only delivers to your project's team, sends a couple of messages an hour, and every message comes from Supabase's address, not yours. Custom SMTP replaces it for all of these.

✓
You can get them on the token management page. For security reasons, a token is shown only once — at the moment it is created.

SMTP connection settings

SettingValue
SMTP hostsmtp.postwing.app
Port587
EncryptionSTARTTLS (the connection is upgraded to TLS before login)
UsernameThe login of an SMTP token for your domain
PasswordThe password of that token — shown once, when the token is created
ℹ
Every mode is also available on a high port: 8465 (SSL/TLS), 8587 (STARTTLS) and 8025 (plain). Many hosting providers and clouds block outbound 25, 465 and 587 — if the connection times out, switch to the matching high port.

Step 1 — enable custom SMTP

In the Supabase dashboard, open Authentication → Emails → SMTP Settings, turn on Enable Custom SMTP and fill in:

FieldValue
Sender emailnoreply@your-domain.com — on your verified domain
Sender nameYour app's name
Hostsmtp.postwing.app
Port number587
UsernameThe login of an SMTP token for your domain
PasswordThat token's password

Supabase advises a sending domain used only for authentication — say auth.your-domain.com — kept apart from marketing mail, so a campaign's complaints never slow down a password reset. Add it as its own domain here and use its address as the sender.

Step 2 — raise the rate limit

Enabling custom SMTP sets Supabase's own limit to 30 emails per hour. That is enough for testing and too low for a launch. Raise it under Authentication → Rate Limits.

⚠
Bots that register throwaway or stolen addresses make your domain send confirmations nobody asked for, and those complaints land on your reputation. Turn on CAPTCHA protection in Supabase before raising the limit far.

Configure it as code

The same settings through the Management API — handy for several projects or environments:

Management API
# Access token: https://supabase.com/dashboard/account/tokens
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 '{
    "smtp_admin_email": "noreply@your-domain.com",
    "smtp_sender_name": "Acme",
    "smtp_host": "smtp.postwing.app",
    "smtp_port": 587,
    "smtp_user": "token-login@your-domain.com",
    "smtp_pass": "your-token-password"
  }'

For the local stack, in supabase/config.toml; supabase config push applies the file to a linked project:

supabase/config.toml
# supabase/config.toml
[auth.email.smtp]
enabled = true
host = "smtp.postwing.app"
port = 587
user = "token-login@your-domain.com"
pass = "env(SMTP_TOKEN_PASSWORD)"
admin_email = "noreply@your-domain.com"
sender_name = "Acme"

[auth.rate_limit]
# Emails per hour
email_sent = 100

On self-hosted Supabase, the Docker setup reads the same values from its .env file:

.env
# .env next to docker-compose.yml (self-hosted Supabase)
SMTP_ADMIN_EMAIL=noreply@your-domain.com
SMTP_HOST=smtp.postwing.app
SMTP_PORT=587
SMTP_USER=token-login@your-domain.com
SMTP_PASS=your-token-password
SMTP_SENDER_NAME=Acme

Send app email from Edge Functions

Custom SMTP covers Supabase Auth only. For your own messages — receipts, notifications — Edge Functions are the natural place, but Supabase blocks outgoing connections to ports 25 and 587 from them. Call the REST API over HTTPS instead:

Store the credentials
supabase secrets set MAIL_TOKEN_LOGIN=token-login@your-domain.com MAIL_TOKEN_PASSWORD=your-token-password
supabase/functions/order-confirmation/index.ts
// supabase/functions/order-confirmation/index.ts
// Call it from your backend or a database webhook — never let the browser pick the recipient.
Deno.serve(async (req) => {
  const { email, orderId } = await req.json();

  const res = await fetch("https://api.postwing.app/external/send_email_simple/", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      to: [email],
      subject: "Order #" + orderId + " confirmed",
      body: "<p>Thanks — your order is confirmed.</p>",
      sender: "Acme <noreply@your-domain.com>",
      idempotency_key: "order-" + orderId,
      auth: {
        username: Deno.env.get("MAIL_TOKEN_LOGIN"),
        password: Deno.env.get("MAIL_TOKEN_PASSWORD"),
      },
    }),
  });

  return new Response(await res.text(), { status: res.status });
});

Send a test email

Sign up with an address outside your Supabase team, or send a password reset from Authentication → Users. If it arrives, custom SMTP is live — the built-in service would have refused that recipient. Failures show up in the project's Auth logs with the SMTP error.

Troubleshooting

ErrorCause and fix
Error sending confirmation email Supabase could not hand the message over. Check the Auth logs: usually wrong credentials or the wrong port.
email rate limit exceeded Supabase's own hourly limit. Raise it under Authentication → Rate Limits.
Only team members receive email Custom SMTP is not enabled or was not saved.
Links are expired on first click A mail scanner opened the link first. Send the one-time code instead.
Edge Function hangs on SMTP Port 587 is blocked there. Use the REST API or port 465.
Mail goes to spam Sender email is not on your verified domain.

Frequently asked questions

Why does Supabase only send auth emails to my team members?

That is the built-in email service. Until you configure custom SMTP, Supabase Auth refuses to deliver to addresses outside the project's team, limits sending to a couple of messages an hour and offers it best-effort only, for testing. Enabling custom SMTP lifts the recipient restriction.

What is Supabase's email rate limit with custom SMTP?

Once custom SMTP is on, Supabase starts you at 30 emails per hour — a limit of its own, separate from your SMTP provider. Raise it under Authentication → Rate Limits once you know your sign-up volume. Hitting it returns an 'email rate limit exceeded' error to your users.

Which port should Supabase use?

587, which Supabase's own examples use; the connection is upgraded with STARTTLS. Port 465 with implicit TLS works too. If you are self-hosting behind a network that blocks both, use 8587.

Can I set custom SMTP for local development?

Yes. The [auth.email.smtp] section of supabase/config.toml configures the local stack, and supabase config push applies the same file to a linked project. Keep the password out of the file with env() substitution.

Can I send non-auth emails from Supabase over SMTP?

Not from Edge Functions on port 587: Supabase blocks outgoing connections to ports 25 and 587 there. Call the REST API with fetch instead, as shown above — or use port 465 if you need SMTP.

Why are magic links already expired when users click them?

Corporate mail scanners such as Microsoft Defender open every link in a message on delivery, and that visit consumes the one-time token. Supabase recommends sending the 6-digit code ({{ .Token }}) instead, or linking to a page of your own that the user confirms from.

Next steps