TypeScript · transactional email

Send email you own , end to end

Sending domains you prove and hold the DKIM key to, idempotent sends that drop the suppressed and sign the rest, and delivery events that feed suppression and signed webhooks — transactional email as a library you embed, over a transport you choose. No platform owns your domains, your logs, or your list.

Open QuxMail$ npm i @quxkit/mail-kit
Open source
mail-kit crystal
0
suppressed addresses you email by accident — the send drops them before the wire
DKIM
signed on every message, with a key no platform holds but you
1
idempotency key — retries and replays converge on one message, never two
transports you send over — SES, SMTP, memory, or your own
What it is

One library, whole — not a platform you rent

Domains you prove and hold

A DNS checklist verifies your sending domain and mail-kit generates and holds the DKIM key — not a platform that can revoke it. Your sending identity stays yours.

Sends that can’t double-fire

Every send is idempotency-keyed, suppression-filtered, MIME-built and DKIM-signed before it queues. Retries and replays converge on one message, never two.

Honest suppression

Bounces and complaints suppress automatically — per tenant and globally — and a send drops a suppressed address before the wire. No accidental re-sends, no list hidden inside “contacts”.

Signed webhooks

Delivered, bounced, complained, opened — every event fans out to your endpoint as a signed, retried webhook. You get the truth about your mail, and it is verifiable.

Your database, your wire

mail-kit writes to your Postgres through a narrow executor and goes out over a transport seam you fill — SES, SMTP, memory, or your own. No second service to operate.

Isolated by tenant

Every domain, message, suppression and webhook row carries an opaque tenantId, so tenancy.protect isolates the mail schema like any other — one send-log per tenant, by construction.

By the shape of it

A platform that owns your domain owns your deliverability. Hold the DKIM key yourself.

4 seams
25% Domains
25% Messages
25% Events
25% Webhooks

Domains, messages, events, webhooks — the framed box mail-kit owns, over your database and the transport you choose.

How it works

Three moves, in order

01
Verify a domain

Walk the DNS checklist; mail-kit generates and holds the DKIM key. Your domain is proven and the sending identity is yours.

02
Send

Call send() with from, to, subject and html. It checks suppression, builds MIME, signs, queues and retries — idempotent on your key.

03
React to what came back

Delivered, bounced, complained, opened — events update suppression and fire signed webhooks to whoever asked.

mail-kit.ts
import { createMail } from '@quxkit/mail-kit';

// your executor, your transport — SES, SMTP, or your own
const mail = createMail({ db: sql, transport: ses });

// idempotent: retries & replays converge on one message, never two
const message = await mail.send(tenantId, {
  from: '[email protected]',
  to: user.email,
  subject: 'Welcome',
  html,
  idempotencyKey: signupId,
});                            // suppression-filtered · MIME-built · DKIM-signed

A platform that owns your domain owns your deliverability. Hold the DKIM key yourself.

mail-kit

Embed it in the app you already run