← Back to all posts

Stop Hardcoding Email Templates: Why Your Transactional Templates Belong in an API

2026-10-16 · MailAnvil Team

When your food delivery app sends 40,000 order receipts a month, "the template is a string inside a service class" stops being acceptable. An Indonesian fintech we talked to kept their loan due-date reminder HTML inside a Java constant. Changing one line of copy — fixing a typo in "jatuh tempo" — required a code deploys, a code review, and a 4 PM release train. Template copy moved at the speed of slowest repo, not the speed of marketing team.

This is the argument for a template API: treat transactional email templates as managed resources, not source code.

What actually breaks with inline templates

Three failure modes keep showing up across Indonesian teams — the e-commerce team running your 11.11 flash-sale receipts, the logistics platform sending package-status updates, the neo-bank sending KYC confirmations:

  1. The silent rollback. A template change goes out, open rates on Gmail Indonesia drop 8%, nobody can tell which version caused it because the HTML lived in arush branch and the "fix" deploy overwrote it.
  2. The variable mismatch. Backend sends {{recipient_name}}, template expects {{nama_penerima}}. Interpolation returns empty string, customer receives "Halo ," and your support inbox finds out first.
  3. The sync drift. Dev has one version, staging another, production a third. Nobody knows which HTML is live.

The API-shaped fix

A template API treats each template as a versioned resource:

GET  /v1/templates          # list
POST /v1/templates          # create — returns tpl_ id
PATCH /v1/templates/:id     # new version, old one stays queryable

Send calls reference the template by ID instead of shipping HTML:

{
  "template_id": "tpl_01JABC...",
  "variables": {"nama_penerima": "Budi", "total": "Rp 149.000"},
  "from": "[email protected]"
}

The provider does interpolation server-side, so a missing variable is a provider-side validation error at send time — not a broken email in a customer's inbox. You get:

Migration path that doesn't hurt

If your team built the same way most Indonesian startups do:

  1. Extract the inline HTML into a versioned template via POST /v1/templates. Keep the API send unreferenced for a week.
  2. Add an Idempotency-Key to your sends so a retry never double-sends a receipt.
  3. Switch the send route to template_id behind a feature flag. Ship to 5% of traffic first.
  4. Watch webhook events (delivered, bounced) for a week. Roll back = flip the flag, not revert an HTML string in the repo.

What a good template API looks like

Not every provider gets this right. Things worth checking before committing: server-side variable validation at create (fail fast on {{}} mismatches), immutable versions with timestamps, raw HTML escape by default to prevent injection through user-supplied variables, and plain-text fallback generation so deliverability doesn't tank.

This is why we built MailAnvil's template endpoints the boring way: Zod-validated schemas, versions stored as rows in D1, variables validated before a mail event reaches SES. Works the same whether you call it from Node, Go, or — increasingly common — from an AI agent through our MCP server.

If your email template currently lives in a .java constant — switch it to an API-managed template this week. It's a one-hour migration that removes "they received an email that says Halo ," from your future support tickets.

Try MailAnvil free at mailanvil.com.