@xenterprises/fastify-xemail
v1.3.0
Published
Fastify plugin for email via Postmark or SendGrid (legacy) — transactional emails, templates, bulk sending, validation, and contact management.
Readme
@xenterprises/fastify-xemail
Fastify 5 plugin for email via Postmark or SendGrid (legacy) — send transactional, template, attachment, and bulk emails through one fastify.xEmail decorator. SendGrid additionally supports address validation and Marketing contacts/lists. For Fastify apps that need email with the least possible wiring.
Migration note: SendGrid support is legacy. New integrations should use
provider: 'postmark'; existing SendGrid registrations keep working unchanged.
Install
npm install @xenterprises/fastify-xemail fastify@5fastify@^5.0.0 is a peer dependency.
TypeScript declarations ship with the package — importing the plugin types the
fastify.xEmail decorator automatically (index.d.ts).
Minimal example
import Fastify from 'fastify';
import xEmail from '@xenterprises/fastify-xemail';
const fastify = Fastify();
await fastify.register(xEmail, {
provider: 'postmark', // or 'sendgrid' (legacy default)
apiKey: 'your-postmark-server-token',
fromEmail: '[email protected]',
});
await fastify.xEmail.send(
'[email protected]',
'Welcome!',
'<h1>Welcome!</h1><p>Thanks for signing up.</p>'
);The plugin reads no environment variables. All configuration arrives via the
register options object; reading process.env.POSTMARK_SERVER_TOKEN and passing
it in is the consumer's job.
Providers
| Provider | Status | Sending methods | validate, contacts, lists |
|----------|--------|-----------------|------------------------------|
| postmark | Migration target | send, sendTemplate, sendWithAttachments, sendBulk, sendPersonalizedBulk | Throw "not supported by provider 'postmark'" |
| sendgrid | Legacy (default) | All sending methods | Fully supported |
Postmark's API has no equivalents for email validation or contact/list
management, so those methods exist in the decorator under Postmark but throw
[xEmail] '<method>()' is not supported by provider 'postmark'.
Options
| Name | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| provider | 'sendgrid' \| 'postmark' | 'sendgrid' | No | Email provider. sendgrid is legacy; postmark is the migration target |
| apiKey | string | — | Yes | SendGrid API key (needs Mail Send; Marketing APIs for contact/list methods) or Postmark server API token |
| fromEmail | string | — | Yes | Verified sender email address |
| fromName | string | — | No | Sender display name |
| active | boolean | true | No | Set false to disable the plugin entirely (no decorator is added) |
Invalid options fail fast at registration with errors like:
xemail: option `apiKey` must be a string, e.g. `app.register(xEmail, { apiKey: 'SG.your-api-key' })`Decorators
The plugin adds one decorator: fastify.xEmail. No request decorators.
send(to, subject, html, text?, extraOptions?)
Send an email. Plain text is auto-generated from the HTML if text is omitted.
Under SendGrid, extraOptions is merged into the message (e.g. replyTo, cc,
categories). Under Postmark, extraOptions accepts replyTo, cc, bcc,
headers, and messageStream (mapped to Postmark's PascalCase fields).
Returns { success, statusCode, messageId } (Postmark reports a fixed
statusCode: 200 on success and throws on failure).
sendTemplate(to, subject, templateId, dynamicData?, extraOptions?)
Send using a provider template. SendGrid: templateId is the dynamic template
ID (d-xxx) and dynamicData is sent as dynamicTemplateData. Postmark:
templateId is a template alias (string) or numeric template ID and
dynamicData becomes the TemplateModel. The subject is always included in the
template data. Returns { success, statusCode, messageId }.
sendWithAttachments(to, subject, html, attachments)
Send an email with attachments. Each attachment needs content (base64), filename,
and type (MIME type); disposition defaults to 'attachment'. Returns
{ success, statusCode, messageId }.
sendBulk(to, subject, html)
Send the same email to an array of recipients. SendGrid uses one
sgMail.sendMultiple call; Postmark sends one email per recipient in chunks of
500 concurrent requests. Returns { success, count, statusCode }.
sendPersonalizedBulk(messages)
Send different content per recipient. messages is an array of
{ to, subject, html, text? }. Never throws on individual failures — returns
one result per recipient: { success: true, to, statusCode } or
{ success: false, to, error }. Postmark uses the batch endpoint (chunked at
500 messages); a failure of the whole batch call marks that chunk's recipients
as failed instead of throwing.
validate(email) — SendGrid only
Validate an address via the SendGrid Email Validation API. Returns
{ email, valid, verdict, score, result }. On API failure it returns a soft result
{ email, valid: false, verdict: 'Unknown', error } instead of throwing.
Throws under provider: 'postmark'.
addContact(email, data?, listIds?) — SendGrid only
Add or update a SendGrid Marketing contact. data accepts firstName/lastName
(or first_name/last_name) and customFields (spread into the contact);
listIds is an array of list IDs. Returns { success, jobId, email }.
searchContact(email) — SendGrid only
Search for a contact by email. Returns { found: true, contact } or { found: false }.
deleteContact(contactId) — SendGrid only
Delete a contact by ID. Returns true on success (HTTP 200/202), false otherwise.
createList(name) — SendGrid only
Create a marketing contact list. Returns { success, list }.
getLists() — SendGrid only
Get all contact lists. Returns an array (empty when none exist).
deleteList(listId) — SendGrid only
Delete a list by ID. Returns true on success (HTTP 200/202/204), false otherwise.
Routes
None. This plugin adds no routes.
Error behavior
- Registration throws fail-fast
Errors naming the plugin and the option, with a registration example (see Options above). - Method argument validation throws synchronously-rejecting
Errors prefixed with[xEmail], e.g.[xEmail] 'to' is required for send(). - SendGrid API failures are logged via
fastify.log.error(structured error, never the API key) and re-thrown wrapped:[xEmail] Failed to send email: <reason>.validate()is the exception — it returns a soft result instead of throwing, since validation failures are expected in normal operation. Postmark failures are wrapped the same way. - SendGrid-only methods under Postmark throw
[xEmail] '<method>()' is not supported by provider 'postmark'. - With
active: falsethe plugin returns before registering anything, sofastify.xEmailisundefined.
Requirements
- Node.js >= 20
- Fastify ^5.0.0 (peer dependency)
License
Proprietary — All Rights Reserved, X Enterprises. See LICENSE.
