Supabase · October 3, 2026
Send from a Supabase project
A Supabase Edge Function calls the Pigeon API when a row is inserted. The service role stays in Supabase. The Pigeon key stays in function secrets.
1. Prepare Pigeon
- Verify the domain you will use in From.
- Create a template with the slug welcome, publish it, and leave a {{email}} variable where the address should appear.
- Create a sending API key and copy it once.
2. Store the secrets
In the Supabase project, open Edge Functions, then Secrets. Use https://pigeonfs.com when the function should call the hosted API. Use http://host.docker.internal:4005 when the function runs against Pigeon on your machine.
supabase secrets set \ PIGEON_URL=https://pigeonfs.com \ PIGEON_API_KEY=pg_your_key \ PIGEON_FROM="Ada <ada@yourdomain.com>"
3. Deploy the welcome function
Create supabase/functions/welcome/index.ts. A copy of this handler lives in examples/supabase/welcome in the Pigeon repo. Deploy it, then add a Database Webhook that runs the function when a row is inserted into auth.users or your profiles table.
supabase functions deploy welcome --no-verify-jwt
Use --no-verify-jwt only for a database webhook that cannot send a user JWT. Check a shared secret header inside the function if the URL could be called by anyone else.
import "jsr:@supabase/functions-js/edge-runtime.d.ts"; Deno.serve(async (req) => { const payload = await req.json(); const email = payload.record?.email; if (!email) return new Response("missing email", { status: 400 }); const response = await fetch(Deno.env.get("PIGEON_URL") + "/api/emails", { method: "POST", headers: { Authorization: `Bearer ${Deno.env.get("PIGEON_API_KEY")}`, "Content-Type": "application/json" }, body: JSON.stringify({ from: Deno.env.get("PIGEON_FROM"), to: [email], template: "welcome", variables: { email } }) }); return new Response(await response.text(), { status: response.status }); });
Use case: welcome the person who just signed up
The webhook body includes record.email. The function posts that address to Pigeon with the welcome template. Open Emails in Pigeon and look for a sent or delivered row. A 401 means the key does not match. A 403 domain_not_verified means From is still pending.
Use case: invite someone to a team
Point a second webhook at the same shape of function when a row is inserted into team_invites. Read record.email and record.team_name, and send a template slug of team-invite with those variables. Keep a different sending key if you want the invite mail in its own log.
Do the domain setup from the editor