Send Transactional Email API
Send a template-bound transactional email with per-send variables and delivery tracking.
Trigger an email from your application using a transactional definition created in Growth. Each definition binds a stable slug to an email template and can optionally override that template's subject.
Request
POST /api/v1/email/transactional/sendSend the organization API key with x-api-key or Authorization: Bearer, and send a JSON body.
| Field | Type | Required | Description |
|---|---|---|---|
transactional | string | Yes | Definition slug or id. slug is accepted as an alias. |
email | string | Yes | Recipient email address. |
firstName | string | No | Recipient first name. |
lastName | string | No | Recipient last name. |
variables | record of strings | No | Send-scoped values used by template placeholders. |
tags | string[] | No | Tags added to the recipient's contact. |
curl -X POST https://your-growth-app.convex.site/api/v1/email/transactional/send \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"transactional":"freebie-kit","email":"[email protected]","firstName":"Jane","variables":{"resourceName":"The Essential Launch Guide","resourceDescription":"Your guide is ready to download.","downloadUrl":"https://example.com/kit","expiresAt":"July 31, 2026","organizationName":"Acme"},"tags":["kit"]}'Variables fill @{key|fallback} placeholders in the template. The ready-made Resource delivery template uses the following values:
| Variable | Required for the preset | Used for |
|---|---|---|
downloadUrl | Yes | The download button destination. |
resourceName | No | The subject, heading, and button label. |
resourceDescription | No | A short explanation above the button. |
expiresAt | No | Human-readable link availability, such as July 31, 2026. |
organizationName | No | The team name in the help message. |
The top-level firstName field personalizes the greeting. Optional variables use the template's readable fallback when omitted; downloadUrl should always be provided because it powers the CTA. The contact email always remains the destination, while send variables can override names for that render.
Response
A successful request returns 200:
{
"sent": true,
"contact": {
"email": "[email protected]",
"status": "subscribed"
}
}The recipient is upserted as a subscribed contact with source api. Growth records the send and tracks delivery, opens, and clicks.
Unsubscribes Do Not Block This Endpoint
This endpoint sends on the transactional stream. A contact who unsubscribed
from your marketing email still receives it — they are paying for the product
these emails deliver.
Only a hard bounce or a spam complaint blocks every stream, because in those cases the address is dead or the recipient reported you.
Errors
Refused sends carry a stable code alongside the human-readable message, so
your application can tell "the customer did not get what they paid for" from
routine noise:
{
"code": "contact_bounced",
"message": "The address hard-bounced and is suppressed for every stream. The email was not delivered."
}| Status | code | Meaning |
|---|---|---|
400 | — | The JSON body is invalid, a required field is missing, or the recipient address is invalid. |
401 | — | The API key is missing or invalid. |
404 | — | The transactional definition cannot be found in this organization. |
409 | — | The definition is archived or its template was deleted. |
409 | contact_bounced | The address hard-bounced. Nothing was delivered — worth alerting on. |
409 | contact_complained | The recipient reported a previous email as spam. Nothing was delivered — worth alerting on. |
409 | contact_not_found | The contact does not belong to this organization. |
409 | provider_not_configured | No email provider is configured for this organization. |
contact_unsubscribed exists in the response contract but is never returned on
this endpoint: a marketing unsubscribe no longer blocks transactional email.