Skip to content
emailkit
Esc
navigateopen⌘Jpreview

Open-source email toolkit for TypeScript

All of email.
One API.

One client — like Prisma for your database or Better Auth for auth — holds your providers, webhooks, and mailboxes. Everything email, one clean API. That's the whole setup

Free and open source. MIT. v3.0.0 · what's new
emailkit.tssend + receive + events
export const emailkit = EmailKit({
  emailDrivers: [
    // your email API + your users' inboxes
    ResendDriver({ apiKey }),
    GmailDriver({ clientId, clientSecret }),
  ],
  hooks: {
    email: {
      onInbound: async (email) => {
        await saveToInbox(email);
        await answerWithAIAgent(email);
      },
      onBounced: async (event) => suppress(event.recipient),
    },
  },
});

await emailkit.sendEmail({
  from: { email: "hello@acme.dev" },
  to: { email: "ada@example.com" },
  subject: "Welcome",
  text: "Your account is ready.",
});

The whole email layer. Nothing scattered, nothing else to wire.

Mix them freely and route by sender. Compare providers

ONE CENTRAL CLIENT//ONE UNIFIED API//EMAIL APIS + MAILBOXES//CONNECT DOMAINS//CONNECT INBOXES//VERIFIED WEBHOOKS//TYPE-SAFE SENDS//FREE. MIT.//

Why emailkit

Your entire email layer.
One client.

No webhook routes scattered across your codebase, no provider SDKs with five different shapes, no OAuth glue. Providers, hooks, and mailboxes are configured in one client — and one intuitive API hides each provider's weirdness behind it.

01

The client

Everything email, in one place

One client is your whole email layer. Providers, webhook handling, lifecycle hooks — declared in one file, not scattered across route handlers and provider dashboards. emailkit verifies and normalizes every event before your code sees it; the route is one line.

Quickstart →
export const emailkit = EmailKit({
  emailDrivers: [
    ResendDriver({ apiKey, webhookSecret }),
    GmailDriver({ clientId, clientSecret }),
  ],
  hooks: {
    email: {
      onInbound: async (email) => saveToInbox(email),
      onDelivered: async (event) => markDelivered(event.messageId),
      onBounced: async (event) => suppress(event.recipient),
    },
  },
});

// app/api/email/route.ts — the whole route
export const { GET, POST } = createNextEmailKitHandler(emailkit);
02

Providers

Email APIs and mailboxes, same client

Transactional APIs like Resend and Mailgun next to real inboxes like Gmail and Outlook — in the same client. Mix them freely and route by sender; the rest of your code never knows the difference.

Multiple providers →
export const emailkit = EmailKit({
  emailDrivers: [
    ResendDriver({ apiKey: resendKey }),     // email API
    GmailDriver({ clientId, clientSecret }), // users' inboxes
    OutlookDriver({ clientId: msId, clientSecret: msSecret }),
  ],
  // route by sender — connected inboxes send as themselves,
  // everything else goes through your email API
  resolveEmailDriver: async (ctx) => {
    if (ctx.operation !== "sendEmail") return "resend";
    return (await findMailbox(ctx.message.from.email)) ?? "resend";
  },
});
03

Connect

Domains and inboxes, one call each

Your users connect a whole domain to your email API, or sign into their Gmail or Outlook. Either way it's a couple of calls — DNS records, OAuth flows, token refresh, and inbound notifications are emailkit's problem. After that, both look identical to your app.

Bring-your-own email →
// their domain, on your email API
const { domain } = await emailkit.domains.ensure({
  emailDriver: "resend",
  domain: "acme.com",
});
// show domain.verification.records in your UI, then:
await emailkit.domains.verify({
  emailDriver: "resend",
  domain: "acme.com",
});

// or their inbox — OAuth, refresh, notifications handled
const { redirectUrl } = await emailkit.mailboxes.connect({
  emailDriver: "gmail",
  email: "acme.support@gmail.com",
});
04

Type safety

TypeScript knows your providers

Configure Resend and sendAt appears. Configure Gmail and mailbox methods appear. Use a feature your provider doesn't have, and your code doesn't compile — capability gaps surface in your editor, not in production.

How it works →
await emailkit.sendEmail({
  from: { email: "hello@acme.dev" },
  to: { email: "ada@example.com" },
  subject: "Tomorrow's reminder",
  text: "Your meeting starts at 10:00.",
  sendAt: new Date("2026-07-16T08:00:00Z"), // Resend has scheduling,
  idempotencyKey: "meeting-reminder-42", //    so these type-check
  sender: { emailDriver: "resend" },
});

Nothing new to run

A library, not a platform. No queue, no required schema, no second copy of your data — your database stays the source of truth.

Nothing gets lost

Replies stay threaded on every provider, and sync() replays webhook events you missed through your existing hooks — no separate recovery path.

Escape hatch included

Need something only one provider offers? providerFetch() calls its API with your configured credentials.

One API everywhere

The provider becomes
an implementation detail.

Pick a provider — or several. Payload shapes, auth schemes, thread formats: each provider's quirks stay under the hood, so swapping is just the driver line. Click through — everything below it stays identical, even when the "provider" is a user's own inbox.

email.ts
import { EmailKit, ResendDriver } from "emailkit";

const emailkit = EmailKit({
  emailDrivers: [ResendDriver({ apiKey: process.env.RESEND_API_KEY! })],
});

await emailkit.sendEmail({
  from: { email: "hello@acme.dev", name: "Acme" },
  to: { email: "ada@example.com" },
  subject: "Welcome",
  text: "Your account is ready.",
});

What it's for

From your first send
to a full email product.

Full power

Automate your users' inboxes

Think AI email support: your customer connects support@theirco.com — their Gmail, their Outlook, or their whole domain — and your app reads every message, drafts replies, keeps threads intact, and sends as them. emailkit is the entire email layer under a product like that.

Users bring their email →

Just sending?

Still the fastest setup

Welcome emails, receipts, notifications. No platform to adopt, no boilerplate to paste: install, add a driver, send. And when you later want inbound, mailboxes, or a second provider, it's the same client — not a rewrite.

Email from your app →

Setup

Install. Send. Receive.

Your first send takes a minute. Inbound isn't much more.

01

Install

One package, every driver included — providers are config, not extra dependencies. Node.js 20.19 or newer.

npm install emailkit
pnpm add emailkit
bun add emailkit
yarn add emailkit
02

Send

Construct the client with a driver and send. More drivers later won't touch this call site.

import { EmailKit, ResendDriver } from "emailkit";

export const emailkit = EmailKit({
  emailDrivers: [
    ResendDriver({ apiKey: process.env.RESEND_API_KEY! }),
  ],
});

await emailkit.sendEmail({
  from: { email: "hello@acme.dev", name: "Acme" },
  to: { email: "ada@example.com" },
  subject: "Welcome",
  text: "Your account is ready.",
});
03

Receive

Add a hook, mount the route, point your provider's webhook at it — inbound email and delivery events arrive verified and normalized. The quickstart walks through mailboxes and domains too.

export const emailkit = EmailKit({
  emailDrivers: [/* ... */],
  hooks: {
    email: { onInbound: async (email) => saveToInbox(email) },
  },
});

// app/api/email/route.ts
export const { GET, POST } = createNextEmailKitHandler(emailkit);

Sponsors

emailkit is free and open source. Development is supported by these companies.

IIC AG

// emailkit

Wire up email once.

One client, one API — from your first welcome email to users connecting their own inboxes and domains. Configure email once, in one place, and it's done. Open source, TypeScript-first, nothing to host.