Quickstart
This walks through wiring mailery into an existing Express + MongoDB app and sending your first email. Assumes you have:
- Node 20+
- A running MongoDB (any version 4.4+; we test against 7.x)
- A queue backend: either Redis 6+ (Bull driver, default) or just Mongo (Agenda driver)
- A SendGrid API key — or skip this and use the bundled
NullProviderfor local testing
1. Install
yarn add mailery
# or
npm install maileryPeer dependencies (install the queue driver you want):
# Default: BullMQ + Redis
npm install bullmq ioredis
# Or: Agenda on the same Mongo, no Redis required
npm install agenda @agendajs/mongo-backend bottleneckPlus express ^4 || ^5. mailery brings in mongodb, mjml, handlebars, @sendgrid/mail, and zod as direct deps.
2. Initialize
In your server entry point:
import express from 'express'
import { MongoClient } from 'mongodb'
import {
Mailer,
MongoContactAdapter,
SendGridProvider,
createAdminRouter,
createPublicRouter,
} from 'mailery'
const mongo = await MongoClient.connect(process.env.MONGODB_URI!)
const db = mongo.db()
const mailer = await Mailer.init({
db,
adapter: new MongoContactAdapter({
db,
collection: 'users', // YOUR existing collection
emailField: 'email',
idField: '_id',
tagsField: 'tags',
tagsWritable: true,
}),
queue: { driver: 'bull', redis: { url: process.env.REDIS_URL! } },
providers: {
sendgrid: new SendGridProvider({
apiKey: process.env.SENDGRID_API_KEY!,
webhookVerificationKey: process.env.SENDGRID_WEBHOOK_KEY,
}),
},
defaultProvider: 'sendgrid',
publicUrl: 'https://yourdomain.com', // base URL for /m/* endpoints
unsubscribeSecret: process.env.MAILER_UNSUB_SECRET!,
senderAddress: '12 Main Street, Brooklyn NY 11201, USA',
fromDefaults: { name: 'Jeff', email: 'jeff@yourdomain.com' },
})3. Mount the routers
const app = express()
// Admin UI + REST. Gate with your existing auth middleware.
app.use('/admin/mailer', requireAdmin, createAdminRouter(mailer))
// Tracking endpoints (open pixel, click redirect, unsubscribe, provider webhooks).
// MUST be reachable by email clients and your provider — don't gate this.
app.use('/m', createPublicRouter(mailer))Visit https://yourdomain.com/admin/mailer and you'll see the React admin UI.
4. Register events
Tell mailery what events your app fires:
mailer.registerEvent({ name: 'Created', dedupePolicy: 'once-per-contact' })
mailer.registerEvent({ name: 'Hit Free Limit', dedupePolicy: 'once-per-day' })
mailer.registerEvent({ name: 'Cancelled', dedupePolicy: 'every-time' })The dedupe policy controls how mailery deduplicates repeated fire() calls for the same contact + event. See Events.
5. Subscribe contacts and fire events
When a user signs up:
const user = await db.collection('users').insertOne({ email, name, tags: [] })
const externalId = String(user.insertedId)
// Record consent.
await mailer.upsertSubscription({ externalId, source: 'signup' })
// Fire an event — any flow triggered by 'Created' will pick this up on the next tick.
await mailer.fire('Created', externalId)6. Run a worker process
The web process you just set up handles HTTP. A second process needs to run background workers — this is what actually advances flow state and dispatches sends:
// worker.ts (run alongside server.ts)
const mailer = await Mailer.init({ /* same config as web */ })
await mailer.startWorkers()You can also run a single combined process during development (don't set workerless: true).
7. Author a flow + template
Two ways:
- Admin UI — visit
/admin/mailer/templates, create a template, then go to/admin/mailer/flowsand create a flow that sends it on theCreatedevent. - Directly in MongoDB — insert documents into
mailer_templatesandmailer_flows. See Direct DB for full cookbooks.
8. Test locally
Skip the SendGrid step and use the bundled NullProvider to see what would be sent without actually delivering anything:
import { NullProvider } from 'mailery'
const provider = new NullProvider()
// ...
const mailer = await Mailer.init({ providers: { null: provider }, defaultProvider: 'null', /* ... */ })
// In tests:
expect(provider.sent[0]?.subject).toContain('Welcome')For a complete working example, see examples/express-mongo in the repo.
9. Wire up SendGrid for production
When you move from NullProvider to real sends, you need to point your sender domain at SendGrid (SPF + DKIM + DMARC) and configure SendGrid's Event Webhook to post back to mailery. If your DNS is on Cloudflare, this is one command:
npx mailery setup-sendgrid \
--domain news.example.com \
--webhook-url https://yourdomain.com/m/webhooks/sendgrid \
--cloudflareIt's idempotent — safe to re-run, only writes when state drifts. See Deliverability → Automated setup for what it does and how to set up the required SENDGRID_API_KEY + CLOUDFLARE_API_TOKEN. If you're not on Cloudflare, drop --cloudflare and the script prints the CNAMEs to paste into your DNS provider, then handles SendGrid setup the rest of the way.