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 |
previewText | string | null | no | Up to 200 characters after trimming. Rendered at {{previewText}} or {{preheader}}; absent, empty or null renders empty |
preheader | string | null | no | Compatible alias of previewText; different normalized values return 400 |
variables | object | no | Sample values: firstName, lastName, email |
json
{
"previewText": "Timely m’a bloqué mes paiements Stripe pendant 5 jours.",
"content": "<div data-skip-in-text=\"true\" style=\"display:none;font-size:1px;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;mso-hide:all;\">{{previewText}}</div><p>Hello</p><a href=\"{{unsubscribeUrl}}\">Unsubscribe</a>"
}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
200 OK— the rendered email.400 Bad Request— invalid fields, conflicting preview aliases, or required HTML missing.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, missing_preheader_shortcode |
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
}Notes
previewText and preheader name the same value. If both are supplied, they must match after
trimming (including null). A nonempty value without either shortcode outside HTML comments
returns missing_preheader_shortcode. Nothing is inserted into the HTML.