How to Send Emails from Supabase (Beyond the 2/Hour Limit)
Supabase's built-in email sender allows 2 emails per hour. How to send emails from Supabase in production: custom SMTP, Send Email Hook, webhooks, and cron.

Ajay Sohmshetty

Supabase sends auth emails (signup confirmations, magic links, password resets, invites) out of the box, but its built-in sender is capped at 2 emails per hour and only delivers to your own team. To send emails from Supabase in production, connect custom SMTP or a Send Email Hook for auth, then pick a separate path for everything else: inline API calls, database webhooks, a queue, a cron job, or Dreamlit workflows over your database.
Last validated: September 2026 against Supabase's custom SMTP, rate limits, and Send Email Hook docs.
What emails does a Supabase app send?
Most apps send four kinds of email, and each needs a different setup:
- Auth emails: signup confirmation, magic links, password resets, email-change confirmation, invites. Supabase Auth sends these.
- Transactional emails: receipts, "someone replied to your post," "your export is ready." Triggered by something a user did.
- Scheduled emails: daily digests, weekly reports, reminders, anything sent on a cadence or after a delay.
- Marketing and lifecycle emails: onboarding sequences, product announcements, win-back emails. These need unsubscribe links and careful targeting.
What Supabase covers vs. what you need to build
| Email type | Built in? | What you need |
|---|---|---|
| Auth (confirmations, magic links, password resets) | Custom SMTP or a Send Email Hook (the built-in sender is 2/hour, team only) | |
| Transactional (receipts, alerts, replies) | Email API called from your code, an Edge Function, or a webhook | |
| Scheduled (digests, reminders) | Supabase Cron + Edge Function + email API | |
| Marketing and lifecycle (onboarding, announcements) | Email platform synced with, or querying, your database |
What are Supabase's built-in email limits?
Supabase Auth prepares and sends the right email when you call one of its methods:
| Supabase method | When it sends | |
|---|---|---|
| Confirm signup | supabase.auth.signUp() | New user registers (if Confirm Email is on) |
| Magic link or OTP | supabase.auth.signInWithOtp() | Passwordless sign-in |
| Reset password | supabase.auth.resetPasswordForEmail() | User clicks "Forgot password" |
| Change email | supabase.auth.updateUser({ email }) | User changes their address (sent to the new address, plus the current one if Secure Email Change is on) |
| Reauthentication | supabase.auth.reauthenticate() | You call it to confirm identity before a sensitive action, such as a password change |
| Invite | supabase.auth.admin.inviteUserByEmail() | Admin invites a user |
Sending a magic link is one call:
const { data, error } = await supabase.auth.signInWithOtp({
email: 'user@example.com',
options: {
emailRedirectTo: 'https://your-app.com/welcome',
},
})The catch is the sender. Supabase's docs say the default email service is not meant for production use:
- It only sends to addresses in your Supabase organization's team. Anyone else gets an
email_address_not_authorizederror. - It sends at most 2 emails per hour across the whole project, and Supabase can change that number.
- It sends from a Supabase address, not your domain, so emails don't carry your brand or your domain's authentication.
- There's no SLA on delivery or uptime.
Since June 3, 2026, new free-plan projects on the default sender also can't edit their auth templates. Supabase made the change after abuse of free projects for phishing. Configuring custom SMTP restores template editing.
How do I fix "email rate limit exceeded" in Supabase?
Move off the built-in sender, then raise the limit. The error (code over_email_send_rate_limit) means Supabase Auth refused to send because a limit was hit:
- Switch senders. The built-in sender allows 2 emails per hour for the whole project, so your third signup in an hour fails. Configure custom SMTP or a Send Email Hook (both below). Either lets you change the limit, and with custom SMTP it starts at 30 per hour.
- Raise the limit to fit your traffic. Go to Authentication > Rate Limits (
supabase.com/dashboard/project/_/auth/rate-limits) and set the email limit above your busiest hour, with headroom for retries. 30 per hour is a safety default for a new sending domain, not a ceiling. - Check the per-user cooldown. By default a user waits 60 seconds before requesting another OTP or magic link. If one person keeps clicking "resend," that's the limit they hit, and waiting fixes it. Show a countdown on your resend button rather than raising it.
Before a launch or signup campaign, check these numbers. A 30-per-hour cap blocks your 31st signup that hour.
How do I set up custom SMTP in Supabase?
Custom SMTP keeps Supabase in charge of building auth emails, but hands delivery to a provider you choose.
- Pick a provider and verify your sending domain with it (SPF, DKIM, and DMARC records).
- In Supabase, go to Authentication > Emails > SMTP Settings and turn on Enable Custom SMTP.
- Enter the host, port (587 with STARTTLS is the usual choice), username, password, sender email, and sender name.
- Edit your templates under Authentication > Emails. The defaults are bare, so add your branding and a plain-text fallback link. Our Supabase email templates guide covers the variables and a full example.
- Raise the email rate limit, as above.
Supabase's docs list Resend, Amazon SES, Postmark, Twilio SendGrid, ZeptoMail, and Brevo as example providers. Here's where the common ones start, checked in September 2026:
| Provider | Free option | Notes |
|---|---|---|
| Resend | 3,000 emails/month, max 100/day | Pro from $20/month for 50,000 |
| Postmark | 100 emails/month | Basic from $15/month for 10,000 |
| Amazon SES | None (AWS Free Tier credits for new accounts) | $0.16 per 1,000 on the default Essentials plan, $0.10 on à la carte |
| SendGrid | 60-day trial, 100 emails/day | Free plan retired in 2025 |
For a fuller comparison, see best transactional email services.
Trade-offs: you manage SMTP credentials, and you write templates as raw HTML in Supabase's editor. If your emails still land in spam after switching, work through why Supabase auth emails go to spam.
How does the Supabase Send Email Hook work?
The Send Email Hook replaces Supabase's email sending entirely. When Supabase Auth needs to send an email, it calls your hook with the user, the email type, and the token data, and your code builds and sends the message. If both SMTP and the hook are configured, the hook wins.
The hook can be an HTTP endpoint (usually a Supabase Edge Function) or a Postgres function. The usual setup:
- Create an Edge Function that receives the hook payload.
- Build the email for each
email_action_type(signup,magiclink,recovery,invite,email_change,reauthentication, plus the security notifications). - Send it through your provider's API.
- Deploy the function and store the hook secret and API key as secrets.
- Enable the hook under Authentication > Auth Hooks.
Trade-offs: it's real code to write, test, and monitor. Supabase retries an HTTP hook only when it returns a 429 or 503 with a Retry-After header, up to three times within a 5-second budget. Other errors, or a longer provider outage, mean the email doesn't send unless your function handles it. In exchange you get full control over templates (React Email, MJML, whatever you like), sending provider, and logging.
How do I send transactional emails from Supabase?
Supabase has no email module for non-auth emails, so you choose where the send happens. The DIY paths below use an API; Dreamlit is the alternative that runs the workflow over your database without send calls in your app.
Inline API calls in your backend
The simplest option: after your code writes to the database, call an email API.
// After saving a new comment, notify the post author
const comment = await saveComment(postId, authorId, content);
await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
"Authorization": `Bearer ${RESEND_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
from: "notifications@yourapp.com",
to: postAuthor.email,
subject: `New comment on "${post.title}"`,
html: `<p>${comment.authorName} commented: "${content}"</p>`
})
});It's easy to follow and fine for an MVP. The costs show up later: email logic spread across your codebase, every copy change to hardcoded HTML like this needs a deploy, and a slow email API slows down the user's request. Put the send after the database write succeeds, and log the provider's message ID so you can trace "I never got it" tickets.
Database webhooks
Database Webhooks make the database the trigger. They're a wrapper around Postgres triggers and the pg_net extension: when a row is inserted, updated, or deleted, Supabase sends the row to an HTTP endpoint asynchronously.
- Create a webhook on
commentsforINSERTevents. - Point it at an Edge Function.
- The function reads the new row, looks up what it needs, and sends the email.
// Edge Function receiving the webhook payload
Deno.serve(async (req) => {
const { record } = await req.json(); // the new comment row
const { postAuthor, post } = await getPostDetails(record.post_id);
await sendEmail({
to: postAuthor.email,
subject: `New comment on "${post.title}"`,
// ...
});
return new Response("ok");
});Your app code only writes to the database, and emails follow. The trade-offs: you set up and monitor a webhook for each table you watch (one webhook can cover inserts, updates, and deletes), failed requests aren't retried unless you build it, and end-to-end tests mean writing real rows.
Queues
For higher volume or stricter reliability, write a job to a queue and let a worker send the email. Supabase Queues is built on the pgmq extension, so the queue lives in your database, and a Cron job or Edge Function can drain it. You get retries and a buffer during provider outages. The cost is more moving parts to monitor. Hosted notification services like Knock or Customer.io take on some of that work, but they still need your users and events, sent from your code or synced in from another source.
How do I send scheduled emails from Supabase?
Use Supabase Cron. It's built on pg_cron, runs jobs from every second up to once a year, and a job can call an Edge Function over HTTP. A weekly digest looks like this:
- A Cron job runs every Monday at 9am and calls an Edge Function.
- The function queries users who should get the digest, plus the data for each one.
- It sends one email per user, in batches your provider's rate limit can handle.
- It records who was sent what, so a rerun doesn't double-send.
The hard part isn't the scheduler. It's the query logic as conditions grow ("active in the last 30 days, not on the free plan, hasn't opted out") and making reruns safe.
How do I send onboarding and marketing emails from Supabase?
The problem here is keeping your data in sync. Tools like Mailchimp or Klaviyo keep their own copy of your users, so you need exports, an integration, or sync code to keep them current. Any lag or failed sync means you can email someone who churned or miss someone who just signed up.
Two ways around it:
- Sync on write: every time a user is created or updated, push the change to your email tool, and backfill existing users once. Plan for failures and deletes.
- Query at send time: keep the logic next to your data, and have the sender query Supabase when it's time to send. The list reflects your data at send time.
Whichever you choose, the emails that matter most in the first weeks are behavioral. They're triggered by what a user did or didn't do after signing up: a welcome email pointing at the one setup step that matters, a nudge after 48 hours to users who haven't hit it, a reminder before a trial ends. Our guides to welcome emails and drip campaigns cover what to send and when to stop.
Which Supabase email approach should you choose?
Supabase email approaches compared
| Approach | Covers | Complexity | Templates | Reliability |
|---|---|---|---|---|
| Custom SMTP | Auth only | Low | Basic (Supabase HTML editor) | Provider-dependent |
| Send Email Hook | Auth only | High | Full (in code) | Brief retries on 429/503 only |
| Inline API calls | Transactional | Low | Full (in code) | You build retries |
| Database webhooks | Transactional | Medium | Full (in code) | No built-in retries |
| Queue + worker | Transactional, scheduled | High | Full (in code) | Retries built in |
| Dreamlit | Auth, transactional, lifecycle, recurring, broadcasts | Low | AI agent or visual editor; real-data preview | Workflow retries; bounce and complaint handling |
| Supabase Cron + Edge Function | Scheduled | Medium | Full (in code) | You make reruns safe |
A simple rule:
- Just launching: custom SMTP for auth, inline API calls for the few transactional emails you have.
- Several event-driven emails: database webhooks or a queue, so your app code only writes data.
- Recurring emails: Supabase Cron calling an Edge Function, with a record of what was sent.
- You want workflows without building the sending layer: Dreamlit connects to your Supabase database, and your AI agent builds, previews, and tests the emails before you approve publishing.
Where Dreamlit fits
For Supabase email, Dreamlit is the option where you don't build the triggers, retries, and templates yourself. Add it to your AI agent, connect your Supabase database, and ask for a welcome email or trial reminder. It builds the workflow, shows a preview with real data, and keeps it in draft until you publish.
It can also handle Supabase Auth delivery through the documented Auth Hook setup. For lifecycle email, follow the worked agent tutorial to build and test your first sequence.
Start a 7-day free trial or connect your agent. 7-day free trial. Card required. Cancel anytime.
References
- Supabase: Send emails with custom SMTP
- Supabase: Auth rate limits
- Supabase: Send Email Hook
- Supabase: Auth error codes
- Supabase changelog: Email template customisation on the free tier
- Supabase: Database Webhooks
- Supabase: Queues
- Supabase: Cron
Frequently asked questions
Can I send emails using Supabase?
Yes, for auth emails. Supabase Auth sends signup confirmations, magic links, password resets, invites, and email-change emails. For anything else, such as receipts, notifications, digests, or onboarding emails, you call an email API from your own code, an Edge Function, or a database webhook. Or connect Dreamlit to Supabase and let your AI agent build email workflows that react to what your users do, without adding send calls to your app.
How many emails can Supabase send per hour?
The built-in email service sends 2 emails per hour and only to members of your Supabase organization. After you configure custom SMTP, the limit starts at 30 per hour, and you can raise it under Authentication > Rate Limits.
How do I fix 'email rate limit exceeded' in Supabase?
Configure custom SMTP (or a Send Email Hook) so you're off the 2-per-hour built-in sender, then raise the email limit under Authentication > Rate Limits to match your real signup volume. If one user sees it right after clicking resend, the per-user cooldown (60 seconds by default) is the likely cause, and waiting fixes it.
Does Supabase support SMTP?
Yes. Under Authentication > Emails > SMTP Settings, turn on Enable Custom SMTP and enter your provider's host, port, username, password, sender email, and sender name. Supabase's docs list Resend, Amazon SES, Postmark, Twilio SendGrid, ZeptoMail, and Brevo as examples.
Can I customize Supabase auth email templates on the free plan?
Only with custom SMTP, for projects created since June 3, 2026. New free-plan projects on Supabase's default email provider can't edit their auth templates. Older projects, paid plans, and any project with custom SMTP can.
What is the best email service for Supabase?
For auth emails over SMTP, Resend and Postmark are the simplest to set up, and Amazon SES is the cheapest at high volume. For non-auth emails the same providers work through their APIs. Pick based on volume, price, and whether you want to own the sending code. Dreamlit fits when you want your AI agent to build workflows over your database, rather than calling an SMTP or REST transport yourself.
About the Author

Co-Founder & CEO
Ajay is Co-Founder and CEO of Dreamlit AI, which runs user activation for software companies. He was previously co-founder and CTO of Harmonic AI and a software engineer at Google. Full bio →
Other articles

Supabase Email Templates (2026): Variables, Setup, Fixes
Supabase email templates explained: where they live, every template variable, the June 2026 free-plan change, custom SMTP setup, and fixes for common breakages.

Build Lifecycle Email with Your AI Agent: A Supabase Tutorial
Build a Supabase welcome sequence through Dreamlit MCP: let your AI agent read the schema, draft emails, preview real data, send a test, and ask before publishing.