Hosted email templates
A template is a subject, an HTML body, a text body, and a list of the variables they use. You store it once and reference it by slug from POST /v1/emails, from a broadcast, or from an automation step. The content lives in immutable versions, and the version a send uses is the one you published, not the one you last saved.
Saving is not publishing, and that is the whole design
A template row points at a published version. Adding a version never changes what sends. You edit, you preview against sample data, you look at the compatibility warnings, and then you publish, which is a separate call. Versions are immutable, so the copy that went out in March is still readable in March's form. Deleting a template is soft and makes it immediately unsendable rather than removing the history of what it sent.
A missing variable fails the send rather than rendering blank
If a template names {{ first_name }} and the send does not supply it and it has no default, the API returns a 422 naming every variable that is missing. The alternative is an email that goes out saying "Hi ," to forty thousand people, which is a failure nobody notices until a customer screenshots it. Variables are declared with an optional default and an optional required flag, and if you do not declare them they are extracted from the subject and the bodies for you.
Preview fills the gaps so you can look at it
The preview endpoint renders a template, including an unpublished draft, filling any variable you did not supply with a plausible sample value and telling you which ones it filled in. It also returns warnings from an email-HTML compatibility check, which is the list of things that render one way in your browser and another way in Outlook. The render endpoint is the strict sibling: it renders exactly as a send would, and refuses in exactly the same way.
It is not a template language, on purpose
The syntax is {{ variable }} and nothing else. No conditionals, no loops, no expressions, no partials, no filters. A template language in a mail body is a program running against customer data with no test suite and no code review, and the failure mode is a rendering error at send time in front of a customer. If you need logic, do it in your application, where you can test it, and pass the result in as a variable.
The same template serves both halves of the product
POST /v1/emails takes a template object of an id, which may be the slug, and variables. A broadcast takes template_id and optionally template_version_id, so a campaign can be pinned to a specific version. An automation step takes the template by slug in its with block. One place to change the wording, three places it takes effect.
The API
Every one of these exists today and answers to the key you already have. Nothing on this list is planned.
- POST /v1/templates
- Create a template, optionally with its first version and published in one call.
- POST /v1/templates/:id/versions
- Add a version. This does not change what sends.
- POST /v1/templates/:id/publish
- Make a version the one that sends.
- POST /v1/templates/:id/preview
- Render with samples for anything missing, plus email-HTML warnings.
- POST /v1/templates/:id/render
- Render exactly as a send would, and fail the same way.
Sending a stored template
The template is referenced by slug. Anything you set alongside it overrides the rendered value.
curl -sS https://emails.sh/v1/emails \
-H "Authorization: Bearer $EMAILSSH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme <hello@acme.com>",
"to": ["new.user@example.com"],
"template": {
"id": "verification-email",
"variables": {
"first_name": "Ada",
"verify_url": "https://acme.com/verify?token=8f2c1a"
}
},
"idempotency_key": "verify:8f2c1a"
}'What this does not do
Named here rather than left for a trial to discover. If something on this list is how your team works, that is a real reason to pick another product, and we would rather you knew now.
There is no visual builder
You write the HTML, or your assistant does. There is no drag-and-drop editor, no block library, and no MJML compiler. If somebody who does not write code needs to change the wording without a deploy, they can edit a template in the dashboard, but they are editing HTML.
No conditionals, loops, or partials
{{ variable }} is the entire syntax. A receipt with a variable number of line items has to arrive as a pre-rendered block of HTML in a variable, or be sent as html on the request instead.
There is no template test-send endpoint
Preview and render return the rendered output, and the only endpoint that puts a test copy in your own inbox is the broadcast test. To try a transactional template for real, send it to yourself with POST /v1/emails.
No localisation and no per-locale variants
One template is one language. Multiple languages means multiple templates and choosing the slug in your own code.
Questions
Can I keep templates in my repository instead?
Yes, and for a lot of teams that is the right answer: pass html and text on the request and the markup lives next to the code that sends it, in review, in your test suite. Hosted templates exist for the cases where the copy has to change without a deploy, and for broadcasts and automations, which need something to point at.
What happens to mail already sent when I publish a new version?
Nothing. A message is rendered at send time and the result is what was delivered. Versions are immutable, so the exact content of an older send is still readable through the version that produced it.
Can I use React Email or MJML?
Compile it yourself and store or send the output. This product takes HTML and text. Anything that produces HTML works, and nothing about the toolchain is our business.
Does a template know about the unsubscribe link?
Broadcasts add the List-Unsubscribe headers themselves, on every copy, whatever the template says. If you want a visible unsubscribe link in the body, put one in the template.
Send your first email today.
3,000 emails a month on the transactional side and 1,000 contacts on the marketing side, both free, no card. Enough to run real traffic through it before you decide.
Related