Send transactional emails with Nodemailer
Contents
If your app sends email with Nodemailer, you can send that email through PostHog. Change the transport, and keep every sendMail call. Each message becomes one email sent from your code, so PostHog shows its sends, metrics, and engagement events.
Before you start, create a project secret API key with the Transactional email scope, and configure a channel with a verified sender.
Install
Create the transport
Pass a PostHog client with your secret key to createPostHogTransport, and give the result to Nodemailer:
The client sends email to the app host of your PostHog Cloud region. For a reverse proxy or a self-hosted instance, set appHost on the client. See set up the client for the key scopes and hosts.
Send an email
Your sendMail calls stay the same:
info.messageId is PostHog's message ID. Use it with posthog.emails.get to check the email's status.
- Sender:
fromneeds an address on a domain that your project verified. Withoutfrom, PostHog uses the project's default sender. - Recipients: A message has one
toaddress.cc,bcc, andreplyTotake a list. PostHog keeps the display name offromand sends the other addresses without their names. - Attachments: You can attach files, and inline images that have a
cid.
Set PostHog options
Put PostHog's options for one message in posthog:
template_keynames the email, such asorder-shipped. PostHog groups the email's metrics and sends under it. Without it, the transport usesdefaultTemplateKeyfromcreatePostHogTransport, ornodemailer. Use letters, digits, hyphens, and underscores.distinct_idrecords the email against the person with this distinct ID. Without it, PostHog uses a person whose distinct ID is thetoaddress. See link emails to your users.idempotency_keyidentifies this one email. PostHog sends an email only once for each key, for 24 hours after it queues the email. See retry safely.capture_engagementturns the email's engagement events on or off. It defaults totrue.
In TypeScript, importing posthog-node/nodemailer adds posthog to Nodemailer's message type.
Send mail from a queue
The client retries network errors, rate limits, and a 503 up to two times, with the same idempotency key. When a job queue retries a failed send, each retry is a new sendMail call. Without an idempotency_key, each call gets a new random key, and the recipient can get the email twice.
So when you queue mail, set idempotency_key from a stable ID of the email, such as the order ID and the email's name:
PostHog remembers a key for 24 hours after it queues the email. If a queue retries a job later than that, the same key doesn't stop a second email, so stop retrying a send after 24 hours.
A failed sendMail rejects with an error that tells you if a retry can succeed:
error.retryableistruewhen a retry with the sameidempotency_keycan succeed. After a network error or a503, PostHog might have queued the email, so retry with the same key, not through another provider.error.retry_afteris the number of seconds that PostHog asked you to wait, when it sent one.error.code,error.status,error.detail, anderror.attrcome from the errors table.
Messages PostHog can't send
PostHog never drops part of a message. If PostHog can't send part of a message, sendMail rejects before PostHog gets a request, and sends nothing. The error has code: "EPOSTHOGUNSUPPORTED", and error.refusal tells you why:
refusal | The message has |
|---|---|
multiple_to | no to address, or more than one. Send one message for each recipient. |
unsupported_header | a custom header, an attachment with its own headers, inReplyTo, references, list, or a sender that isn't the from address. |
unsupported_part | an amp, icalEvent, watchHtml, alternatives, or raw part, or an attachment with a raw source. |
unsupported_priority | a priority other than normal. |
envelope_mismatch | an envelope with a different sender or other recipients than the message. |
inline_without_cid | an inline attachment with no cid. |
invalid_template_key | a posthog.template_key with characters other than letters, digits, hyphens, and underscores. |





