THE EMAIL TOOLKIT

What the Toolkit does

Upload vendor HTML (or an .eml), and the Toolkit pre-flights and converts it for your ESP: personalization tokens become your platform's merge tags using your team's saved variables, donation/action links are swapped and UTM-tagged, a compliant unsubscribe block is injected when one is missing, broken body structure is repaired, and a pre-flight report checks mobile rendering, dark mode, alt text, leftover tokens, and attribution before anything is sent. Every change lands in a plain-language ledger and your team's history.

Supported platforms: Iterable, Mailchimp, Klaviyo, ActiveCampaign, HubSpot, SendGrid, Salesforce Marketing Cloud, Campaign Monitor, and a custom option. Teams with an Iterable, Mailchimp, or SendGrid key connected on the Account page can upload the processed template to their platform in one click.

Server API

Everything the tool page does, as one deterministic call. Processing only — the API never uploads to an ESP and never sends email.

Endpoint & auth

POST https://theemailtoolkit.com/api/process
X-API-Key: etk_live_...
Content-Type: application/json

One key per account, managed by your team admin on Account → Server API (generate, rotate, revoke; the key is shown once and stored hashed). Rate limit: 60 requests/minute per key.

Request

fieldtypemeaning
htmlstringraw vendor HTML, ≤ 2 MB. Exactly one of html/eml is required
emlstringa raw .eml message instead of html — converted like the tool's .eml upload (HTML body extracted, cid: images embedded)
espstring"iterable", "mailchimp", "klaviyo", "activecampaign", "hubspot", "sendgrid", "sfmc", "campaignmonitor", "custom"
link_replacementsobjectoriginal URL → replacement URL, exact match against href values (≤ 200). Omitted or blank = keep that link
utmobject{"name": "<saved UTM set>"} or inline {source, medium, campaign, content}, plus overwrite (default false). Merged onto every donation/action link, preserving existing parameters unless overwrite
text_replacementsarrayordered {find, replace} literal pairs (≤ 200), applied after automatic token conversion; empty replace deletes
inject_unsubscribebooldefault true; false skips injection and ledgers the warning
unsubscribe_blockstringcustom HTML used instead of the standard block when one is injected
mobile_safety_netbooldefault false; viewport meta + targeted max-width media query, ledgered as Mobile: lines
vendor_variantsobject{"target_url": ..., "vendors": [{"name", "url"}, ...]} (≤ 20) — one HTML per vendor from the processed output, like the tool's vendor zip
qc_reportbooldefault false; include the exportable QC report as {html, filename}
filenamestringlabel for the team-history record and variant filenames (default api-request)

Personalization semantics

Detected tokens (bracket, %%…%%, AMPscript %%=…=%%, %VAR%, *|…|*, ${…}, bare FNAME-style) convert to the target platform using the team's saved variables first, then the platform's stock tags. A fallback embedded in the source token — the Salesforce IIF(…, "Friend", …) pattern — carries into the output ({{defaultIfEmpty FirstName "Friend"}}). A Handlebars greeting {{#if firstName}}{{firstName}}{{else}}Patriot{{/if}} converts to {{defaultIfEmpty FirstName "Patriot"}}; any other Handlebars conditional is left in place, reported in changes, and flagged by pre-flight. Tokens with no known mapping and FreeMarker logic blocks are never deleted — they stay in place and are reported.

Response

{
  "processed_html": "<html>…</html>",
  "changes": [
    "https://old.example/give?amount=10 → https://new.example/give?amount=10&utm_source=em (×3)",
    "Personalization: %%FirstName%% → {{defaultIfEmpty FirstName "Patriot"}} (×2)",
    "Unsubscribe block injected before </body>"
  ],
  "preflight": [{"status": "warn", "text": "…"}, …],
  "history": {"id": 123, "html_stored": true, "size": 48213, "limit": 300000}
}

changes is the complete ledger — one entry per mutation, with full, untruncated URLs, plus explicit notes for anything the API declined to do (unknown replacement URLs, unmappable tokens, FreeMarker/Handlebars logic left for review). Verify a run by reading changes. preflight is the tool page's full pre-flight report, warnings first; it never alters processing. Every run is recorded to team history, attributed as API.

Errors

statuserror
400invalid_json, missing_field, invalid_esp, invalid_link_replacements, invalid_utm, utm_set_not_found, invalid_text_replacements, invalid_vendor_variants, too_many_vendors, invalid_filename, invalid_unsubscribe_block
401missing_api_key, invalid_api_key
402subscription_inactive
413html_too_large (2 MB), request_too_large (request bodies over 10 MB are refused outright)
422too_many_tokens, vendor_output_too_large, eml_conversion_failed
429rate limit

Examples

curl -s https://theemailtoolkit.com/api/process \
  -H "X-API-Key: $ETK_API_KEY" -H "Content-Type: application/json" \
  -d '{"html": "<html>…</html>", "esp": "iterable",
       "link_replacements": {"https://old.example/give": "https://new.example/give?utm_source=em&utm_medium=email&utm_campaign=20261020_oct"},
       "filename": "vendor-blast.html"}'
import requests

r = requests.post(
    "https://theemailtoolkit.com/api/process",
    headers={"X-API-Key": ETK_API_KEY},
    json={"html": raw_html, "esp": "iterable",
          "utm": {"name": "Sept Prospecting"}, "filename": "vendor-blast.html"},
    timeout=120,
)
r.raise_for_status()
out = r.json()
open("processed.html", "w").write(out["processed_html"])
for line in out["changes"]:
    print(line)

Same input + same account state (saved variables, UTM sets) → byte-identical output. The one non-deterministic field is the opt-in QC report's timestamp.

Support