Transactional email examples
Contents
These examples cover the transactional emails that most products send. Each one says which settings to use, how to build its idempotency key, and whether to pass a distinct ID. Then it shows the send.
The examples use Node.js and Python. Every SDK page shows the same calls in its language. Each example assumes that you already set up the client and created the email with the variables that the example sends.
Sign-in code
Send a one-time code when someone signs in without a password.
| Setting | Value |
|---|---|
| Template variables | code, expires_in_minutes |
| Protect sensitive data | On, so PostHog keeps no copy of the code |
| Distinct ID | The user's ID |
| Idempotency key | sign-in-code-{code_id}, one key for each code that you issue |
When the person asks for a new code, issue a new code with a new ID. The new ID makes a new key, so PostHog sends a new email. A retry of the same code sends nothing new.
Email address check
Send a link that confirms that a new user owns their email address.
| Setting | Value |
|---|---|
| Template variables | first_name, verify_link |
| Protect sensitive data | On, so the link's token never goes through a tracking redirect |
| Distinct ID | The new user's ID. Call identify() with the same ID when the user signs up. |
| Idempotency key | verify-email-{verification_id} |
Send it from a background job after the user's row is saved, so a slow send never holds up the signup. Your SDK's page shows a job that retries only the errors that a later attempt can fix.
Team invite
Send an invite to someone who doesn't have an account yet.
| Setting | Value |
|---|---|
| Template variables | inviter_name, team_name, accept_link |
| Protect sensitive data | On, because the link carries the invite token |
| Distinct ID | Leave it out. The invitee isn't a user yet, so you have no distinct ID for them. |
| Idempotency key | invite-{invite_id} |
Without a distinct ID, PostHog records the email on a person whose distinct ID is the email address. To resend an invite, issue a new invite with a new ID.
Receipt with a PDF
Send a receipt with the invoice attached, and a copy to the customer's billing contact. Attachments and copies work only with content from your code, so your code renders the email.
| Setting | Value |
|---|---|
| Template key | receipt |
| Copies | cc to the billing contact, if the customer has one |
| Attachment | The invoice PDF, 512 KB at most for all files together |
| Distinct ID | The customer's user ID |
| Idempotency key | receipt-{order_id} |
The SDKs encode the file to Base64 for you. With the API, you send Base64 yourself. See copies and attachments for the limits, and your SDK's page for its attachment fields.
Measure whether an email works
Each send lands on the recipient's person, so you can measure what people do after an email with the rest of your product data. First, turn on Capture email engagement events in your Workflows settings.
For example, to see how many people finish a password reset after they get the email, build a funnel with these steps:
$workflows_email_delivered, filtered to$email_subjectequals your reset email's subject- Your own event for a finished reset, such as
password_reset_completed
Use $workflows_email_delivered, not $workflows_email_link_clicked, as the first step. An email with Protect sensitive data turned on doesn't track link clicks, so a click step counts nobody.
For this funnel to work, pass the same distinct ID in the send and in the event that your app captures.





