Migrating from EmailJS
Frontmail was designed so that moving from EmailJS takes minutes: the browser SDK has the same function signatures, the REST API accepts the same fields, and the template syntax is compatible for simple templates.
Step by step
Section titled “Step by step”1. Create your organization and connect the service
Section titled “1. Create your organization and connect the service”Sign up at app.frontmail.dev and connect the same email provider you use in EmailJS (see Services). If you used a Gmail or Outlook service, reconnect it via OAuth; for SMTP or API providers, enter the same credentials.
2. Recreate your templates
Section titled “2. Recreate your templates”Create a template for each EmailJS template. Copy subject, To, From, Reply-To and the content. Then click Detect parameters and set types and required flags – see template syntax below for differences.
3. Map the keys and IDs
Section titled “3. Map the keys and IDs”| EmailJS | Frontmail | Where to find it |
|---|---|---|
Public Key (user_id) |
Public key pk_… |
Security → Public key |
Private Key (accessToken) |
Private key sk_… – server only |
Security → Private keys (shown once) |
| Service ID | Service ID svc_… |
Email services |
| Template ID | Template ID tpl_… |
Templates |
| Domain allowlist | Allowed websites | Security → Allowed websites |
| reCAPTCHA | Cloudflare Turnstile with your own free widget | Keys in Security → Bot protection (Turnstile), per template Settings → Protection → Require CAPTCHA (Turnstile) |
4. Change the import
Section titled “4. Change the import”import emailjs from '@emailjs/browser';import emailjs from '@frontmail/emailjs-compat';Your emailjs.init(), emailjs.send() and emailjs.sendForm() calls keep working – only the IDs
and the key change. See EmailJS compatibility.
Using the CDN script? Replace the EmailJS <script> with the compat build, which exposes the same
emailjs global, so your calls stay unchanged:
<script src="https://cdn.jsdelivr.net/npm/@frontmail/emailjs-compat@0.1.0/dist/emailjs-compat.umd.js" integrity="sha384-RH+fuNzBqTKgxDJBHRmWfOn37fs33L+Or1ujDV7wThynx9FxD9uPptLu80LpCrEc" crossorigin="anonymous"></script>Pin the exact version and keep the integrity attribute – see
Pin the version for where to find
the hash of newer releases.
Alternatively, load the native SDK (@frontmail/browser, same pinning) and rename emailjs. calls
to frontmail. – the arguments are the same, but the promise resolves with status: 'queued' or
'held' instead of 200.
Calling the REST API directly? Change the host to https://api.frontmail.dev. The paths
/api/v1.0/email/send and /api/v1.0/email/send-form and the body fields stay the same.
5. Turnstile instead of reCAPTCHA
Section titled “5. Turnstile instead of reCAPTCHA”If your templates used reCAPTCHA, remove the reCAPTCHA widget and add Turnstile. Just like with
reCAPTCHA, you use keys from your own account: create a free Turnstile widget in Cloudflare, paste
its keys into Security → Bot protection (Turnstile) and click Turn on CAPTCHA for all
templates; new templates then require it by default. Put the widget with your site key into the
form – see Turnstile. The g-recaptcha-response field is not used.
6. Test and switch
Section titled “6. Test and switch”Send a test from the template editor, deploy, and watch History. Keep your EmailJS account until you’ve seen real traffic go through.
7. Optional: move to the native SDK
Section titled “7. Optional: move to the native SDK”@frontmail/browser or a framework SDK gives you typed params, held status, getStatus() and
typed errors.
Template syntax
Section titled “Template syntax”Frontmail uses Handlebars; EmailJS uses a Mustache-like syntax. Common cases:
| EmailJS | Frontmail | Notes |
|---|---|---|
{{name}} |
{{name}} |
Same. Always HTML-escaped in Frontmail. |
{{{html_content}}} |
{{html_content}} with param type html |
Triple braces don’t disable escaping in Frontmail; use the sanitized html type. |
{{#if}}-style sections from Mustache {{#name}}…{{/name}} |
{{#if name}}…{{/if}} |
Use explicit Handlebars blocks. |
Mustache inverted section {{^name}}…{{/name}} |
{{#unless name}}…{{/unless}} |
|
| Lists via sections | {{#each items}}…{{/each}} with a list param |
Define itemFields for validation. |
| – | {{formatDate}}, {{formatMoney}}, {{eq}}… |
Extra helpers. |
Default values set in EmailJS template fields map to the param’s default in Frontmail.
Comparison
Section titled “Comparison”A factual comparison of features relevant for migrating, as of September 2026. Pricing and limits of both services change over time – check the providers’ own pages before deciding.
| EmailJS | Frontmail | |
|---|---|---|
| When monthly quota is used up | Requests are rejected until the next period or an upgrade | Messages are held and sent automatically when credits are added; optional overdraft and auto top-up |
| Prepaid credit packs with validity | Not offered | 5k / 25k / 100k packs valid 3–12 months, on every plan |
| REST API rate limit | 1 request per second | 2–50 requests per second depending on plan, plus /v1/send-batch |
| Template language | Variables with simple sections | Handlebars with conditions, loops, helpers and typed, validated params |
| CAPTCHA | reCAPTCHA v2 | Cloudflare Turnstile, on for all templates in one click |
| Provider failover | Not offered | Fallback service per template, daily health checks |
| Delivery tracking | Not offered | Delivered / bounced / complained / opened / clicked via provider webhooks |
| SDK retries and idempotency | Not documented | Automatic retries with idempotency keys |
| Framework SDKs | Browser SDK, React Native SDK | Browser, React, React Native, Vue, Svelte, Node, EmailJS-compatible |
If you spot an outdated statement here, please tell us at support@frontmail.dev and we’ll fix it.