BLOGS
Send Email from Supabase Edge Functions (Deno, 2026)
Step-by-step guide to sending emails from Supabase Edge Functions using Deno and the Pingram SDK — on-demand, database webhook, and auth hook patterns.
Motivation
Supabase’s built-in emails are limited to authentication flows. Edge Functions let you send any email you want — welcome emails, order confirmations, alerts — without that limit and without maintaining a separate server.
Edge Functions run on the Deno runtime inside Supabase’s infrastructure. They deploy globally and can be triggered three ways:
- On-demand — called from your frontend or any HTTP client
- Database webhooks — fired automatically when a row changes
- Auth hooks — fired on signup, sign-in, or password reset
This guide walks through all three patterns using the Pingram SDK. For a broader overview of all email approaches in Supabase (including SMTP and Node.js), see How to Send Emails with Supabase.
Prerequisites
- A Supabase project with the CLI installed (
npm i -g supabase) - A Pingram account (free tier: 3,000 emails/month)
- Your Pingram API Key (
pingram_sk_...) from the dashboard
1. Create Your First Edge Function
Scaffold a new function:
supabase functions new send-email
This creates supabase/functions/send-email/index.ts. Replace the contents with:
import { Pingram } from 'npm:pingram';
const pingram = new Pingram({
apiKey: Deno.env.get('PINGRAM_API_KEY')!
});
Deno.serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers':
'authorization, x-client-info, apikey, content-type'
}
});
}
await pingram.send({
type: 'welcome',
to: { id: 'user_123', email: 'user@example.com' },
email: {
subject: 'Welcome!',
html: '<h1>Welcome to our app!</h1>'
}
});
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' }
});
});
What’s happening here
- The
pingramnpm package works in Deno via thenpm:specifier — nonode_modulesneeded. Deno.env.get()pulls secrets that Supabase manages for you (more on that below).- The
emailfield lets you set the subject and HTML body inline. You can also design templates visually in the Pingram dashboard which will be referenced bytypeinstead. - The CORS preflight handler lets you call this function directly from a browser.
2. Store Your Secrets
Never hard-code API keys. Supabase provides two ways to manage secrets:
Local development
Create a .env file at supabase/functions/.env:
PINGRAM_API_KEY=pingram_sk_...
This file is loaded automatically when you run supabase functions serve.
Production
Push secrets via the CLI:
supabase secrets set PINGRAM_API_KEY=pingram_sk_...
Verify they’re set:
supabase secrets list
3. Test Locally
Start the local Supabase stack and serve the function:
supabase start
supabase functions serve send-email --no-verify-jwt --env-file supabase/functions/.env
Send a test request:
curl -i --request POST 'http://localhost:54321/functions/v1/send-email'
Check your inbox. If the email doesn’t appear, see the troubleshooting section below.
4. Call from Your Frontend
Use the Supabase client library to invoke the function from any frontend framework:
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key'
);
const { data, error } = await supabase.functions.invoke('send-email');
The supabase-js client handles auth headers automatically, so your Edge Function receives the user’s JWT. You can verify the token inside the function if you want to restrict access.
5. Trigger Emails from Database Changes
Supabase Database Webhooks call your Edge Function automatically whenever a row is inserted, updated, or deleted. No polling, no cron jobs.
Example: send a notification when a new order is created
Create an Edge Function that parses the webhook payload:
supabase functions new order-notification
import { Pingram } from 'npm:pingram';
import { createClient } from 'npm:@supabase/supabase-js';
const pingram = new Pingram({
apiKey: Deno.env.get('PINGRAM_API_KEY')!
});
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
);
Deno.serve(async (req) => {
const payload = await req.json();
const order = payload.record;
const { data: user } = await supabase
.from('profiles')
.select('email, first_name')
.eq('id', order.user_id)
.single();
if (!user) {
return new Response(JSON.stringify({ error: 'User not found' }), {
status: 404
});
}
await pingram.send({
type: 'order_confirmation',
to: { id: order.user_id, email: user.email },
email: {
subject: `Order #${order.id} confirmed`,
html: `<h1>Thanks, ${user.first_name}!</h1><p>Your order total: $${order.total}</p>`
}
});
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' }
});
});
The key difference from the on-demand pattern: Supabase sends the webhook payload with a record field containing the row that changed. Your function typically needs to look up related data (like the user’s email) from another table.
Wire the webhook
In the Supabase Dashboard:
- Go to Database > Webhooks
- Click Create a new webhook
- Select the
orderstable and theINSERTevent - Set the type to Supabase Edge Function and choose
order-notification
Every new row in the orders table now triggers an email automatically.
6. Send Emails on Auth Events
Supabase Auth Hooks run an Edge Function when a user signs up, signs in, or resets their password. Use them for branded welcome emails that go beyond the default Supabase auth templates.
supabase functions new auth-welcome
import { Pingram } from 'npm:pingram';
const pingram = new Pingram({
apiKey: Deno.env.get('PINGRAM_API_KEY')!
});
Deno.serve(async (req) => {
const payload = await req.json();
const { user } = payload;
const firstName = user.user_metadata?.first_name ?? 'there';
await pingram.send({
type: 'welcome',
to: { id: user.id, email: user.email },
email: {
subject: 'Welcome!',
html: `<h1>Hey ${firstName}, welcome aboard!</h1>`
}
});
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' }
});
});
The payload shape is different from database webhooks — it contains a user object from the auth event instead of a table row.
Enable the hook in the Supabase Dashboard under Authentication > Hooks and point it at your auth-welcome function.
7. Deploy to Production
Once you’ve verified everything locally, deploy your functions:
supabase functions deploy send-email
supabase functions deploy order-notification
supabase functions deploy auth-welcome
Make sure your production secrets are already set (Step 2). Each function deploys independently and scales automatically across Supabase’s global edge network.
Common Issues and Fixes
Email received from Pingram domain
By default, emails are sent from a Pingram domain. To send from your own domain, verify it in the Pingram dashboard under Settings > Domains. This also improves deliverability.
”Module not found” for npm packages
Deno’s npm: specifier requires Supabase CLI v1.50+. Update with:
npm i -g supabase@latest
CORS errors when calling from the browser
Ensure your function handles OPTIONS requests and returns the appropriate Access-Control-Allow-Origin header (see the code in Step 1).
Not receiving emails
Check the Pingram Logs page. If the email appears in logs but isn’t arriving, look at the log details.
If the email doesn’t appear in logs at all, your API key or region is likely misconfigured. Make sure you’re initializing with the correct region:
const pingram = new Pingram({
apiKey: 'pingram_sk_...',
region: 'us' // or 'eu', 'ca'
});
Edge Function timeouts
Edge Functions have a default timeout of 60 seconds. Most operations and API calls should resolve in seconds, but if you’re doing heavy processing before sending, consider splitting the work into multiple functions.
LLM tools take very long. It’s recommended that they run as background jobs, so they don’t block the operation of the function itself.
Resources
- How to Send Emails with Supabase — covers SMTP, Node.js, and other approaches
- Pingram documentation
- Supabase Edge Functions docs
- Supabase Database Webhooks
- Fixing the Supabase email rate limit
- Best SMTP providers for Supabase