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
npm install honoThe route
// 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.