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
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.
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.
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);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";
},
});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",
});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.
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.",
});import { EmailKit, MailgunDriver } from "emailkit";
const emailkit = EmailKit({
emailDrivers: [MailgunDriver({ apiKey: process.env.MAILGUN_API_KEY! })],
});
await emailkit.sendEmail({
from: { email: "hello@acme.dev", name: "Acme" },
to: { email: "ada@example.com" },
subject: "Welcome",
text: "Your account is ready.",
});import { AIInbxDriver, EmailKit } from "emailkit";
const emailkit = EmailKit({
emailDrivers: [AIInbxDriver({ apiKey: process.env.AIINBX_API_KEY! })],
});
await emailkit.sendEmail({
from: { email: "hello@acme.dev", name: "Acme" },
to: { email: "ada@example.com" },
subject: "Welcome",
text: "Your account is ready.",
});import { EmailKit, GmailDriver } from "emailkit";
const emailkit = EmailKit({
emailDrivers: [GmailDriver({ clientId: googleId, clientSecret: googleSecret })],
});
await emailkit.sendEmail({
from: { email: "acme.support@gmail.com" },
to: { email: "ada@example.com" },
subject: "Welcome",
text: "Your account is ready.",
});import { EmailKit, OutlookDriver } from "emailkit";
const emailkit = EmailKit({
emailDrivers: [OutlookDriver({ clientId: msId, clientSecret: msSecret })],
});
await emailkit.sendEmail({
from: { email: "acme.support@outlook.com" },
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.
Install
One package, every driver included — providers are config, not extra dependencies. Node.js 20.19 or newer.
npm install emailkitpnpm add emailkitbun add emailkityarn add emailkitSend
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.",
});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);// 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.

