Hono

One route that works the same on Node, Bun, Deno, and Workers.

Hono runs on several runtimes, and only one thing differs between them: where the key comes from. Read it from the context binding rather than from a global and the same file deploys everywhere.

Install

Any Hono runtime
npm install hono

The route

src/index.ts
// src/index.ts
import { Hono } from 'hono';
import { env } from 'hono/adapter';

type SendResult = { id: string; status: string };
type Refusal = { error: { code: string; message: string; next?: string } };

const app = new Hono();

app.post('/signup', async (c) => {
  // Works on Workers (bindings), Node, Bun, and Deno alike.
  const { EMAILSSH_API_KEY } = env<{ EMAILSSH_API_KEY: string }>(c);
  const { email } = await c.req.json<{ email: string }>();

  const response = await fetch('https://emails.sh/v1/emails', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${EMAILSSH_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      from: 'Acme <hello@acme.com>',
      to: [email],
      subject: 'Welcome to Acme',
      html: '<p>Confirm your address to finish signing up.</p>',
      idempotencyKey: `signup-${email}`
    })
  });

  if (!response.ok) {
    const refusal = (await response.json()) as Refusal;
    console.error('emails.sh refused the send', refusal.error.code, refusal.error.next ?? refusal.error.message);
    return c.json({ error: 'Could not send the confirmation email.' }, 502);
  }

  const { id } = (await response.json()) as SendResult;
  return c.json({ id });
});

export default app;

On Node, set EMAILSSH_API_KEY in the process environment. On Workers, npx wrangler secret put EMAILSSH_API_KEY. The route above needs no change either way.