POST /api/v1/render
Renders an email with sample data and returns the result. It sends nothing, stores nothing and
changes nothing — it is the only endpoint here with no side effect, so call it as often as you
like, before and after every edit.
unsubscribeUrl, today, currentYear and poweredBy are not sample values: the server computes them and
overrides anything you send for them. The date and the year come from the time zone and language set
on the API key's organization, not from the caller and not from the server clock — so a render
answers with the same date the recipient will read.
One of templateId or content is required — sending neither answers 400. Sending both renders
your content with the template's subject, which is how you try an edit before saving it.
json
{
"success": true,
"data": {
"html": "<html>…<p>Hi Camille</p>…</html>",
"text": "Hi Camille …",
"subject": "What shipped in August",
"unknownVariables": ["company"]
}
}unknownVariables lists the {{…}} placeholders that are not part of the vocabulary. They resolve
to an empty string at send time — nothing breaks, but nothing shows either. Fix them here.
HTML without an unsubscribe link is refused, here and at send time. The answer is a
400
naming the problem: add {{unsubscribeUrl}} inside a link in your HTML. Nothing is ever appended
to your markup on your behalf, so a template that renders is a template that can be sent.Authorization
| Role in the organization | This endpoint |
|---|---|
OWNER | Allowed |
ADMIN | Allowed |
MEMBER | Allowed |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
templateId | string | no | Renders a template of your organization |
content | string | no | Renders raw HTML — wins over the template's own content |
subject | string | no | Defaults to the template's defaultSubject |
variables | object | no | Sample values: firstName, lastName, email |
Example
bash
curl -X POST -H "x-api-key: $AGENTMAIL_API_KEY" -H "content-type: application/json" \
-d '{"templateId":"'$TEMPLATE_ID'","variables":{"firstName":"Camille"}}' \
"https://www.agentsmail.io/api/v1/render"Response
201 Created— the created resource.401 Unauthorized— missing or invalid key, or its owner left the organization.429 Too Many Requests— over 120 requests in a minute for this key.
Response fields
| Field | Type | Description |
|---|---|---|
success | boolean | Indicates if the operation was successful |
data.html | string | The rendered document, merge tags resolved |
data.text | string | Its plain-text counterpart |
data.subject | string | The subject after resolution |
data.unknownVariables | string[] | Tags the product does not know — left visible in the output |
data.warnings | string[] | missing_postal_address, dark_mode_contrast_corrected, dark_mode_contrast_unverified, dark_mode_declaration_removed |
Example response
json
{
"success": true,
"data": {
"html": "<html>…</html>",
"text": "August news…",
"subject": "August news",
"unknownVariables": [],
"warnings": []
}
}Agent recipe
Render is the only call with no side effect: loop on it until the document is clean, then create the campaign. An agent that skips this ships{{fistName}} to real inboxes.
js
const render = async (content) =>
(
await fetch(`${base}/render`, {
method: 'POST',
headers: {...headers, 'content-type': 'application/json'},
body: JSON.stringify({content}),
})
).json()
let html = draft
for (let attempt = 0; attempt < 3; attempt++) {
const {data} = await render(html)
if (data.unknownVariables.length === 0) break
html = fixTypos(html, data.unknownVariables) // your own correction pass
}