Render without sending

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.
The unsubscribe link used while rendering is a sample URL: a render has no recipient, so it has no real unsubscribe token. It is there to prove the link exists, not to work.

Authorization

Role in the organizationThis endpoint
OWNERAllowed
ADMINAllowed
MEMBERAllowed
A key carries the role its creator holds in this organization — never a role on AgentsMail itself. See API keys.

Request body

FieldTypeRequiredDescription
templateIdstringnoRenders a template of your organization
contentstringnoRenders raw HTML — wins over the template's own content
subjectstringnoDefaults to the template's defaultSubject
variablesobjectnoSample 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

FieldTypeDescription
successbooleanIndicates if the operation was successful
data.htmlstringThe rendered document, merge tags resolved
data.textstringIts plain-text counterpart
data.subjectstringThe subject after resolution
data.unknownVariablesstring[]Tags the product does not know — left visible in the output
data.warningsstring[]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
}