Providers
ts
import { SendGridProvider, NullProvider } from 'mailery'SendGridProvider
Constructor
ts
new SendGridProvider({
apiKey: string // required — SendGrid API key
webhookVerificationKey?: string // ECDSA public key (PEM) for event webhook signature verification
sendRatePerSecond?: number // default 10 (shared IP); raise for dedicated IPs
sandbox?: boolean // default false; true validates without delivering
})Behavior
- Calls
@sendgrid/mailsgMail.send(). - Disables SendGrid's own click + open tracking (mailery does its own).
- Adds
customArgs: { sendId: ... }for webhook event correlation. verifyWebhookuses HMAC-SHA256 over${timestamp}${rawBody}against the ECDSA public key.parseWebhookEventsnormalizes SendGrid's event array intoNormalizedEvent[]:delivered→deliveredopen→openclick→clickbounce(type: 'bounce') →bounce(bounceType: 'hard')bounce(type: 'blocked') →bounce(bounceType: 'soft')dropped→bouncespamreport→spam_reportunsubscribe,group_unsubscribe→unsubscribe
Properties
name | 'sendgrid' |
sendRatePerSecond | as configured (default 10) |
Setup checklist (SendGrid dashboard)
- Authenticate sender domain — SPF, DKIM, DMARC.
- Configure event webhook — POST to
https://yourdomain.com/m/webhooks/sendgrid. - Enable event types — delivered, open, click, bounce, dropped, spamreport, unsubscribe.
- Generate Signed Event Webhook public key — set as
SENDGRID_WEBHOOK_KEYenv var.
NullProvider
In-memory provider for tests and dev. Records every send() call.
ts
new NullProvider()Properties
name | 'null' |
sendRatePerSecond | 1000 (effectively unlimited) |
sent | SendArgs[] — every call appended |
Methods
send(args)— pushesargsontosent, returns{ providerId: 'null-<timestamp>-<counter>', status: 'accepted' }.verifyWebhook()— alwaystrue.parseWebhookEvents()— always[].reset()— clearssent.
Use in tests
ts
import { createTestMailer } from 'mailery/testing'
const { mailer, provider } = await createTestMailer()
await mailer.sendOneOff({ /* ... */ })
expect(provider.sent[0]?.subject).toContain('Welcome')
expect(provider.sent[0]?.to).toBe('alice@example.com')
provider.reset()Implementing your own provider
See MailProvider interface for the contract. Roughly 80-150 lines per provider.