Skip to content

Send emails with Bun

Learn how to send transactional emails using PostStack and Bun.

PostStack itself runs on Bun, so the TypeScript SDK is well-exercised against the runtime — no Node compatibility shims and no surprise gaps. Use the same `@poststack.dev/sdk` package you would in Node; Bun resolves it natively, ships zero polyfills, and `Bun.serve` works with the SDK without any adapter layer. For local development, `bun --hot run server.ts` gives you hot reload while the SDK keeps its persistent connection pool intact.

1. Install the SDK

bash
bun add @poststack.dev/sdk

2. Initialize the client

typescript
import { PostStack } from '@poststack.dev/sdk';

const poststack = new PostStack(process.env.POSTSTACK_API_KEY!);

3. Send an email

typescript
import { PostStack, PostStackError } from '@poststack.dev/sdk';

const poststack = new PostStack(process.env.POSTSTACK_API_KEY!);

const server = Bun.serve({
  port: 3000,
  async fetch(req) {
    if (req.method === 'POST' && new URL(req.url).pathname === '/send') {
      try {
        const { id } = await poststack.emails.send({
          from: 'hello@yourdomain.com',
          to: ['user@example.com'],
          subject: 'Hello from Bun!',
          html: '<h1>Welcome!</h1>',
        });
        return Response.json({ id });
      } catch (err) {
        if (err instanceof PostStackError) {
          return Response.json({ error: err.message }, { status: err.statusCode });
        }
        throw err;
      }
    }
    return new Response('Not found', { status: 404 });
  },
});

console.log(`Listening on ${server.url}`);

4. Handle errors

Bun idioms for error handling, retries, and structured logging when calling the PostStack API.

typescript
import { PostStack, PostStackError } from '@poststack.dev/sdk';

const poststack = new PostStack(process.env.POSTSTACK_API_KEY!);

async function sendWithRetry(payload: Parameters<typeof poststack.emails.send>[0]) {
  for (let attempt = 0; attempt < 3; attempt++) {
    try {
      return await poststack.emails.send(payload);
    } catch (err) {
      if (err instanceof PostStackError) {
        if (err.statusCode === 429) {
          // Honour Retry-After (seconds)
          const retryAfter = Number(err.headers?.['retry-after'] ?? 1);
          await Bun.sleep(retryAfter * 1000);
          continue;
        }
        if (err.statusCode >= 500) {
          await Bun.sleep(2 ** attempt * 1000);
          continue;
        }
        // 4xx — log and re-throw
        console.error('PostStack 4xx', {
          status: err.statusCode,
          requestId: err.requestId,
        });
      }
      throw err;
    }
  }
  throw new Error('sendWithRetry exhausted');
}

Framework integrations

Bun.serve native server

`Bun.serve({ fetch })` is the lightest fit. Instantiate the client at module scope, call `await poststack.emails.send(...)` inside the fetch handler, and return `Response.json({ id })`. No framework needed.

Hono on Bun

Most production Bun apps reach for Hono for routing and middleware. The SDK works identically — see the dedicated Hono guide for full request handlers, validation, and middleware patterns.

Background workers

Use Bun’s built-in worker threads (`new Worker(new URL(...))`) for fire-and-forget sends or scheduled jobs. The SDK and its connection pool can run inside the worker scope without changes.

CLI scripts and cron

For one-shot sends from a `bun run scripts/notify.ts`, instantiate the client and call `emails.send` directly. Use `Bun.serve` only when you have an HTTP boundary to handle.

Common pitfalls

  • Spawning a new client per request

    Instantiate `new PostStack(...)` once at module scope. Each handler invocation reusing the same client benefits from connection pooling and TLS keep-alive.

  • Forgetting `await` in a fetch handler

    If you return the promise without awaiting it, Bun closes the connection before the API call finishes. Always `await poststack.emails.send(...)` and then return the response.

  • Mixing Bun and Node-only deps

    The PostStack SDK is pure-JS and runs identically on Bun and Node. If you pair it with a library that imports `node:` modules without the prefix, Bun’s resolver may complain — pin the SDK at the latest version to avoid this.

FAQ

Does the SDK work natively on Bun?

Yes — PostStack itself runs on Bun, so the SDK is regularly tested on every release. No polyfills or compatibility flags needed.

Can I use it with Hono?

Yes. See the dedicated Hono guide. The SDK plugs in as a normal import — no adapter required.

What about hot reload?

`bun --hot` keeps the runtime alive between file changes, so the SDK’s connection pool persists. Cold restarts re-establish the pool transparently.

Related guides

Ready to send emails with Bun?

Create a free account and get your API key in under a minute.