Back to blog
SupabaseEmailTutorialDenoEdge Functions

How to Send Email from Supabase

Send transactional email from Supabase Edge Functions using the SendPigeon SDK. Complete guide with Deno examples, secrets management, and local testing.

SendPigeon TeamFebruary 17, 20265 min read

To send email from Supabase, create an Edge Function, import the SendPigeon SDK with the npm: prefix, and call pigeon.send(). Supabase Edge Functions run on Deno — TypeScript works natively with no build step.

TL;DR

Quick setup:

  1. supabase functions new send-email
  2. supabase secrets set SENDPIGEON_API_KEY=sp_live_xxx
  3. Import npm:sendpigeon and call pigeon.send()

Best for: Welcome emails, password resets, notifications triggered by database changes.


Create an Edge Function

supabase functions new send-email

This creates supabase/functions/send-email/index.ts.

Set Your API Key

Store the API key as a secret:

supabase secrets set SENDPIGEON_API_KEY=sp_live_your_key_here

For local development, create supabase/functions/.env:

SENDPIGEON_API_KEY=sp_live_your_key_here

Send Email from a Supabase Edge Function

// supabase/functions/send-email/index.ts
import { SendPigeon } from "npm:sendpigeon";

const pigeon = new SendPigeon(Deno.env.get("SENDPIGEON_API_KEY")!);

Deno.serve(async (req) => {
  if (req.method !== "POST") {
    return new Response("Method not allowed", { status: 405 });
  }

  const { to, name } = await req.json();

  const { data, error } = await pigeon.send({
    from: "hello@yourdomain.com",
    to,
    subject: `Hey ${name}!`,
    html: `<h1>Welcome</h1><p>Thanks for signing up.</p>`,
  });

  if (error) {
    return Response.json({ error: error.message }, { status: error.status ?? 500 });
  }

  return Response.json({ emailId: data.id });
});

Supabase Edge Functions use Deno.serve() — the built-in Deno HTTP server. No framework needed.


Send Welcome Email on User Signup

Trigger an email when a new user signs up by setting up a Database Webhook on the auth.users table:

// supabase/functions/welcome-email/index.ts
import { SendPigeon } from "npm:sendpigeon";

const pigeon = new SendPigeon(Deno.env.get("SENDPIGEON_API_KEY")!);

Deno.serve(async (req) => {
  const payload = await req.json();

  // Database Webhook payload for auth.users
  const { email, raw_user_meta_data } = payload.record;
  const name = raw_user_meta_data?.name || "there";

  const { error } = await pigeon.send({
    from: "hello@yourdomain.com",
    to: email,
    subject: `Welcome, ${name}!`,
    html: `
      <h1>Welcome to our app!</h1>
      <p>Your account is ready. Here's what to do next:</p>
      <ul>
        <li>Complete your profile</li>
        <li>Explore the dashboard</li>
      </ul>
    `,
  });

  if (error) {
    console.error("Welcome email failed:", error.message);
    return Response.json({ error: error.message }, { status: 500 });
  }

  return Response.json({ success: true });
});

Send Email on Database Change

Use Supabase Database Webhooks to send an email when a row is inserted. Set up the webhook in the Supabase dashboard (Database > Webhooks) to call your Edge Function on INSERT.

// supabase/functions/order-confirmation/index.ts
import { SendPigeon } from "npm:sendpigeon";

const pigeon = new SendPigeon(Deno.env.get("SENDPIGEON_API_KEY")!);

Deno.serve(async (req) => {
  const { record } = await req.json();

  // record contains the inserted row
  const { customer_email, customer_name, order_id, total } = record;

  const { error } = await pigeon.send({
    from: "orders@yourdomain.com",
    to: customer_email,
    subject: `Order #${order_id} confirmed`,
    html: `
      <h1>Order Confirmed</h1>
      <p>Hey ${customer_name}, your order #${order_id} for $${total} has been placed.</p>
      <p>We'll notify you when it ships.</p>
    `,
  });

  if (error) {
    console.error("Order email failed:", error.message);
  }

  return Response.json({ success: true });
});

CORS for Browser Requests

If calling your Edge Function from a browser, add CORS headers:

// supabase/functions/_shared/cors.ts
export const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
// supabase/functions/contact/index.ts
import { SendPigeon } from "npm:sendpigeon";
import { corsHeaders } from "../_shared/cors.ts";

const pigeon = new SendPigeon(Deno.env.get("SENDPIGEON_API_KEY")!);

Deno.serve(async (req) => {
  // Handle CORS preflight
  if (req.method === "OPTIONS") {
    return new Response("ok", { headers: corsHeaders });
  }

  const { name, email, message } = await req.json();

  const { error } = await pigeon.send({
    from: "contact@yourdomain.com",
    to: "you@yourdomain.com",
    subject: `Contact form: ${name}`,
    html: `<p><strong>From:</strong> ${name} (${email})</p><p>${message}</p>`,
    replyTo: email,
  });

  if (error) {
    return Response.json({ error: "Failed to send" }, {
      status: 500,
      headers: corsHeaders,
    });
  }

  return Response.json({ success: true }, { headers: corsHeaders });
});

Deploy

supabase functions deploy send-email

Your function is live at:

https://<project-id>.supabase.co/functions/v1/send-email

Call it with your Supabase anon key:

curl -X POST https://<project-id>.supabase.co/functions/v1/send-email \
  -H "Authorization: Bearer YOUR_SUPABASE_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to": "user@example.com", "name": "Johan"}'

Test Locally

Run your function locally:

supabase start
supabase functions serve send-email

The function runs at http://localhost:54321/functions/v1/send-email.

To catch emails locally instead of sending them, use the SendPigeon CLI:

npx @sendpigeon-sdk/cli dev

View captured emails at localhost:4100. See the local email testing guide for full setup.


Frequently Asked Questions

What runtime do Supabase Edge Functions use?

Supabase Edge Functions run on the Deno runtime. TypeScript and JavaScript work natively — no transpilation or build step. The runtime supports Web Standard APIs, Deno APIs, and npm packages via the npm: import prefix.

Can I use npm packages in Supabase Edge Functions?

Yes. Use the npm: prefix in your import: import { SendPigeon } from "npm:sendpigeon". No node_modules or package.json required — Deno resolves and caches the package automatically.

How do I store secrets in Supabase?

Run supabase secrets set SENDPIGEON_API_KEY=your_key to store secrets for production. Access them with Deno.env.get("SENDPIGEON_API_KEY"). For local development, place them in supabase/functions/.env.

Can I send email when a database row is inserted?

Yes. Set up a Database Webhook in the Supabase dashboard (Database > Webhooks) to trigger your Edge Function on INSERT, UPDATE, or DELETE. The function receives the row data in the request body.

Does SendPigeon work with Deno?

Yes. The SendPigeon SDK uses the Fetch API, which Deno supports natively. Import with npm:sendpigeon and it works the same as in Node.js.


Next Steps


Other Platforms

Deploying elsewhere? We have guides for:

See all framework guides.