Send email with Bun

Bun.serve plus fetch: no dependency needed to send.

You need a verified sending domain and an API key from Settings › API Keys. If neither exists yet, the quickstart covers both in a couple of minutes.

Send

const key = Bun.env.SENDING_API_KEY!;
 
Bun.serve({
  port: 3000,
  async fetch(req) {
    const { email, userId } = await req.json();
    const res = await fetch("https://sending.dev/api/v1/emails", {
      method: "POST",
      headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
      body: JSON.stringify({
        from: "Acme <[email protected]>",
        to: email,
        subject: "Welcome",
        html: "<p>You're in.</p>",
        idempotencyKey: `welcome-${userId}`,
      }),
    });
    return Response.json(await res.json(), { status: res.status });
  },
});

A successful call answers 202 with the message id: accepted and queued, not yet delivered. Delivery, opens, bounces and complaints arrive later, on webhooks or in the dashboard.

What changes in Bun

The TypeScript SDK installs and runs under Bun as well; plain fetch is shown here because it is the shortest complete path.

The thing that catches people

Bun loads .env automatically, which is convenient until it hides a missing variable in production where no .env exists. Read the key once at boot and fail loudly if it is not there.

Next

  • All the fields: cc and bcc, reply-to, attachments, templates, scheduling.
  • Webhooks: delivery, bounce and complaint events, signed with HMAC.
  • Domains: SPF, DKIM and MAIL FROM, and why sending is refused until they are in place.
  • MCP: the same operations as tools, when the one writing the code is an agent.

Nearby: JavaScript and TypeScript

All 30 stacks