If you've deployed self-hosted Supabase and tried to sign up a user, you've probably noticed something frustrating: emails don't work out of the box. Unlike Supabase Cloud, which provides a built-in SMTP server for development, self-hosted deployments require you to configure your own email provider. This guide walks through everything you need to set up reliable email delivery for authentication flows.
Why SMTP Configuration Matters
Email is the backbone of most authentication systems. Password resets, magic links, email verification, and team invitations all depend on reliable email delivery. Without proper SMTP configuration, your self-hosted Supabase instance is essentially broken for production use.
The good news: Supabase Auth works with any email service that supports SMTP. The challenge: the configuration isn't well-documented for self-hosted deployments, and there are several gotchas that can leave you debugging for hours.
Choosing an SMTP Provider
Before diving into configuration, you need an SMTP provider. Here are the most common options for self-hosted Supabase deployments:
Transactional Email Services (Recommended)
| Provider | Free Tier | Notes |
|---|---|---|
| Resend | 3,000/month | Developer-friendly, excellent API |
| Postmark | 100/month | Great deliverability, strict sender verification |
| SendGrid | 100/day | Popular choice, extensive documentation |
| Amazon SES | 62,000/month (with EC2) | Cheapest at scale, more setup required |
| Mailgun | 1,000/month | Reliable, good EU data residency options |
Self-Hosted Options
If you want to avoid third-party services entirely, you can run your own mail server with tools like Mailcow, Mail-in-a-Box, or Postal. However, this adds significant operational complexity and deliverability challenges. Unless you have specific compliance requirements, a managed transactional email service is typically the better choice.
Basic SMTP Configuration
For self-hosted Supabase using Docker Compose, SMTP configuration happens through environment variables in your .env file and the Auth service configuration.
Step 1: Update Your .env File
Add the following variables to your .env file:
# SMTP Configuration SMTP_HOST=smtp.resend.com SMTP_PORT=465 SMTP_USER=resend SMTP_PASS=re_your_api_key_here [email protected] SMTP_SENDER_NAME=YourAppName # Email behavior ENABLE_EMAIL_SIGNUP=true ENABLE_EMAIL_AUTOCONFIRM=false
Step 2: Configure the Auth Container
In your docker-compose.yml, ensure the Auth service has access to these environment variables:
auth:
image: supabase/gotrue:v2.158.1
environment:
GOTRUE_SMTP_HOST: ${SMTP_HOST}
GOTRUE_SMTP_PORT: ${SMTP_PORT}
GOTRUE_SMTP_USER: ${SMTP_USER}
GOTRUE_SMTP_PASS: ${SMTP_PASS}
GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL}
GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME}
GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM}
Step 3: Port Selection
SMTP typically uses one of three ports:
- Port 465: SSL/TLS (recommended for most providers)
- Port 587: STARTTLS (common alternative)
- Port 25: Unencrypted (avoid for security reasons)
If you're experiencing timeout issues with port 465, try switching to 587. Some providers and network configurations handle STARTTLS better than implicit TLS.
Step 4: Restart and Test
After updating your configuration:
docker compose down docker compose up -d
Test by signing up a new user through your application or the Supabase Studio interface.
Customizing Email Templates
Default Supabase email templates are functional but generic. For production, you'll want branded templates that match your application's look and feel.
Template Types
Supabase Auth uses these email templates:
| Template | Trigger |
|---|---|
| Confirmation | New user signs up with email |
| Invite | Admin invites user to the platform |
| Recovery | User requests password reset |
| Magic Link | User signs in with magic link |
| Email Change | User changes their email address |
Template Variables
All templates support Go Template syntax with these variables:
{{ .ConfirmationURL }} - The full URL for the action
{{ .Token }} - The raw token
{{ .TokenHash }} - Hashed version of the token
{{ .SiteURL }} - Your configured site URL
{{ .Email }} - User's email address
{{ .NewEmail }} - New email (for email change)
{{ .OldEmail }} - Old email (for email change)
{{ .Data }} - User metadata object
Setting Up Custom Templates
For self-hosted deployments, templates must be hosted at accessible URLs. The Auth service fetches templates from these URLs at runtime.
Option 1: Host Templates on Your Server
Create a simple template server or use your existing web server:
# nginx configuration
location /email-templates/ {
alias /var/www/email-templates/;
default_type text/html;
}
Then configure the Auth service:
auth:
environment:
GOTRUE_MAILER_TEMPLATES_CONFIRMATION: https://yourdomain.com/email-templates/confirmation.html
GOTRUE_MAILER_TEMPLATES_INVITE: https://yourdomain.com/email-templates/invite.html
GOTRUE_MAILER_TEMPLATES_RECOVERY: https://yourdomain.com/email-templates/recovery.html
GOTRUE_MAILER_TEMPLATES_MAGIC_LINK: https://yourdomain.com/email-templates/magic-link.html
GOTRUE_MAILER_TEMPLATES_EMAIL_CHANGE: https://yourdomain.com/email-templates/email-change.html
Option 2: Use Supabase Storage
If you're running Supabase Storage, you can host templates in a public bucket. Just ensure the bucket is accessible from within your Docker network.
Example Template
Here's a production-ready confirmation email template:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Confirm Your Email</title>
</head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="text-align: center; margin-bottom: 30px;">
<img src="https://yourdomain.com/logo.png" alt="Your App" style="height: 40px;">
</div>
<h1 style="font-size: 24px; font-weight: 600; margin-bottom: 20px;">
Confirm your email address
</h1>
<p>Thanks for signing up! Please confirm your email address by clicking the button below.</p>
<div style="text-align: center; margin: 30px 0;">
<a href="{{ .ConfirmationURL }}"
style="background-color: #10b981; color: white; padding: 12px 30px; text-decoration: none; border-radius: 6px; font-weight: 500; display: inline-block;">
Confirm Email
</a>
</div>
<p style="color: #666; font-size: 14px;">
If the button doesn't work, copy and paste this link into your browser:
<br>
<a href="{{ .ConfirmationURL }}" style="color: #10b981;">{{ .ConfirmationURL }}</a>
</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
<p style="color: #999; font-size: 12px; text-align: center;">
You received this email because someone signed up with {{ .Email }}.
If this wasn't you, you can safely ignore this email.
</p>
</body>
</html>
Email Deliverability Best Practices
Setting up SMTP is only half the battle. Poor deliverability means your emails land in spam folders—or don't arrive at all.
Configure DNS Records
Work with your SMTP provider to set up these DNS records:
SPF Record: Authorizes your SMTP provider to send on your behalf.
v=spf1 include:_spf.resend.com ~all
DKIM Record: Cryptographically signs emails to prevent spoofing.
DMARC Record: Tells receiving servers how to handle failed SPF/DKIM checks.
v=DMARC1; p=quarantine; rua=mailto:[email protected]
Use a Subdomain for Transactional Email
Send auth emails from a subdomain like [email protected] rather than your primary domain. This isolates your transactional email reputation from any marketing emails you might send.
Monitor Bounce Rates
Most SMTP providers offer bounce and complaint monitoring. Keep an eye on these metrics—high bounce rates can damage your sender reputation and lead to deliverability issues.
Using the Send Email Hook
For more control over email sending, Supabase offers a Send Email Hook. This is useful when you want to:
- Use a provider that doesn't support SMTP (API-only services)
- Implement complex templating with tools like React Email
- Add custom logic before sending (rate limiting, A/B testing)
The hook is configured as a Postgres function that Supabase Auth calls instead of using SMTP directly. Check the auth providers documentation for implementation details.
Troubleshooting Common Issues
Emails not sending at all
- Check Auth container logs:
docker logs supabase-auth - Verify SMTP credentials are correct
- Ensure the SMTP port isn't blocked by your firewall
- Test your SMTP credentials with a tool like
swaks
Emails going to spam
- Verify SPF, DKIM, and DMARC records are configured
- Check your sender reputation with tools like Mail Tester
- Ensure your From address domain matches your DKIM domain
Connection timeouts
- Try switching between ports 465 and 587
- Check if your VPS provider blocks outbound SMTP (common with some providers)
- Verify the SMTP host is resolvable from within your Docker network
Template changes not applying
- Templates are fetched at runtime—ensure the URL is accessible
- Check for caching at the reverse proxy level
- Verify the template URL is reachable from the Auth container
How Supascale Simplifies This
If manual SMTP configuration feels like unnecessary complexity, you're not alone. Supascale provides a management layer for self-hosted Supabase that includes:
- OAuth Provider UI: Configure Google, GitHub, Discord, and other OAuth providers through a visual interface
- Custom Domains: Set up custom domains with automatic SSL certificates
- Automated Backups: S3-compatible backup storage with one-click restore
While SMTP configuration still requires your own provider, Supascale reduces the operational burden of managing self-hosted Supabase—letting you focus on building your application rather than wrestling with infrastructure.
Conclusion
Configuring SMTP for self-hosted Supabase isn't difficult once you understand the moving parts. The key steps are:
- Choose a reliable SMTP provider
- Configure environment variables in your Docker Compose setup
- Set up DNS records for deliverability
- Customize email templates to match your brand
The extra effort pays off with full control over your authentication emails—no rate limits, no third-party branding, and complete data sovereignty.
For teams looking to reduce the operational complexity of self-hosted Supabase, check out Supascale's pricing—a one-time purchase that includes unlimited projects and ongoing updates.
