inbound-email-webhook
v0.1.0
Published
Type-safe parser and HMAC signature verifier for inbound email webhooks. Turn incoming email into typed JSON. Works with InboxBridge today; more providers on the roadmap.
Maintainers
Readme
inbound-email-webhook
Type-safe parsing and HMAC signature verification for inbound email webhooks. Turn incoming email into typed JSON, and verify it actually came from your provider — in a few lines, with zero mail-server infrastructure.
npm install inbound-email-webhook- Typed payloads — one canonical
InboundEmailtype, inferred from a Zod schema so the runtime validator and the static types never drift. - Signature verification — constant-time HMAC-SHA256 check, with an optional timestamp freshness window.
- Framework adapters — drop-in Express middleware and a Web-Fetch helper (Next.js App Router, Remix, Hono, Workers).
- No dependencies beyond Zod. ESM + CJS. Node ≥ 18.
Quick start
Web Fetch (Next.js App Router, Remix, Hono, Workers)
// app/api/inbound-email/route.ts
import { verifyRequest } from 'inbound-email-webhook/next'
export async function POST(request: Request) {
const result = await verifyRequest(request, {
secret: process.env.INBOXBRIDGE_SIGNING_SECRET!,
})
if (!result.ok) return result.response // 401 or 400, ready to send
const email = result.email // fully typed InboundEmail
console.log(`Email from ${email.from.email}: ${email.subject}`)
return Response.json({ ok: true })
}Express
import express from 'express'
import { inboundEmailWebhook, type InboundEmailRequest } from 'inbound-email-webhook/express'
const app = express()
app.post(
'/webhooks/inbound-email',
express.raw({ type: 'application/json' }), // raw body is required for HMAC
inboundEmailWebhook({ secret: process.env.INBOXBRIDGE_SIGNING_SECRET! }),
(req, res) => {
const email = (req as InboundEmailRequest).inboundEmail!
// ... create a ticket, post to Slack, kick off a job
res.json({ ok: true })
},
)Low-level (any runtime)
import { verify, parseInboxBridge } from 'inbound-email-webhook'
// Verify over the EXACT raw body bytes — never a re-serialized object.
const ok = verify({
rawBody,
signature: headers['x-inboxbridge-signature'],
secret: process.env.INBOXBRIDGE_SIGNING_SECRET!,
// Optional freshness check:
timestamp: headers['x-inboxbridge-timestamp'],
toleranceSeconds: 300,
})
if (!ok) throw new Error('Invalid signature')
const email = parseInboxBridge(rawBody) // throws on a malformed payloadThe payload
interface InboundEmail {
id: string
receivedAt: string // ISO-8601
from: { email: string; name: string }
to: { email: string; name: string }[]
cc: { email: string; name: string }[]
bcc: { email: string; name: string }[]
replyTo: string | null
subject: string
date: string | null // ISO-8601, from the email's Date header
textBody: string | null
htmlBody: string | null
attachments: {
name: string
contentType: string
contentLength: number
contentId: string | null
downloadUrl?: string // present only when the file was stored
}[]
}Use parseInboxBridge(input) (throws) or safeParseInboxBridge(input) (returns Zod's { success, data | error }). Both accept a raw JSON string or an already-parsed object.
Signature scheme
The signature is HMAC-SHA256(rawBody, signingSecret), hex-encoded, sent in the X-InboxBridge-Signature header as sha256=<hex>. A X-InboxBridge-Timestamp header (Unix seconds) accompanies each delivery. Verify over the raw request body bytes — re-serializing a parsed object changes key order and whitespace and breaks the HMAC.
The timestamp is not currently bound into the signature, so
toleranceSecondsis a freshness check, not a cryptographic replay guarantee.
Providers
This package normalizes inbound email into one canonical shape. Today it ships an adapter for InboxBridge, which gives you an inbound address and delivers parsed, signed JSON to your endpoint.
| Provider | Status | | --- | --- | | InboxBridge | ✅ Supported | | Postmark | 🗺️ Roadmap — PRs welcome | | SendGrid Inbound Parse | 🗺️ Roadmap | | Mailgun Routes | 🗺️ Roadmap | | Amazon SES / SNS | 🗺️ Roadmap |
Adapters are just a payload normalizer + a signature verifier. Contributions that add a provider you can test end-to-end are very welcome.
License
MIT · maintained by InboxBridge
