Email Fast Apply for early access
Migrate

Migrate from EmailJS

Swap one npm package and keep your code — then get the parts EmailJS left out: templates with CC/BCC and Reply-To, variables in every field but To, and the same template sent from a browser or a backend.

Facts verified 2026-08-14 — corrections: hello@emailfast.dev

One import, same code

Migrating from EmailJS is an npm swap:

- import emailjs from "@emailjs/browser";
+ import emailjs from "@email-fast/browser";

  emailjs.init({ publicKey: "ef_pub_..." });

  await emailjs.send("default", "template_contact", {
    name: "Ada",
    message: "Hello from the contact form",
  });

sendForm works too, with the same signature:

document.querySelector("#contact").addEventListener("submit", (e) => {
  e.preventDefault();
  emailjs.sendForm("default", "template_contact", e.target);
});

Under the hood, the endpoints /api/v1.0/email/send and /api/v1.0/email/send-form accept the EmailJS body — user_id, service_id, template_id, template_params — so even code you can't touch keeps working against a new base URL.

Templates carry the addressing — including CC and BCC

In EmailJS, a template holds the subject, the body, and the To / CC / BCC / Reply-To fields, with {{variables}} throughout. That model carries over with one deliberate exception — the To address here is a fixed, literal address chosen when the template is created, never a variable, so a public key in your page source can never steer where mail goes — and the CC and BCC fields you asked for are first-class:

One message goes out with a shared body and a single DKIM signature — CC and BCC ride along, they are not re-sent as separate emails.

Step by step: a template that CCs your team

In the dashboard

  1. Open Templates → New template.
  2. Fill the fields. A support-ticket notification might be: - To: helpdesk@yourdomain.com — a fixed address, set here and only here; the browser can never choose the recipient - CC: support-leads@yourdomain.com, {{assigned_rep}} - BCC: archive@yourdomain.com - Reply-To: {{email}} (the visitor — a plain reply goes straight to them) - Subject: Ticket {{ticket_id}}: {{subject}} - Body: your HTML, with {{name}}, {{ticket_id}}, and the rest.
  3. Declare the variables the template accepts (name, email, ticket_id, …). Unknown keys are stripped and wrong types rejected, so a caller can't inject markup.
  4. Save. The template gets a slug (its template_id) — that's all your code needs.

Want the visitor to get their own confirmation email? That's the per-template auto-reply — opted in and daily-capped — never a variable in the To field.

Or over the API

The same template, created with a secret key:

curl https://api.emailfast.dev/v1/templates \
  -H "Authorization: Bearer ef_live_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "support_ack",
    "to_email": "helpdesk@yourdomain.com",
    "from_email": "support@yourdomain.com",
    "subject": "Ticket {{ticket_id}}: {{subject}}",
    "html": "<p>{{name}} wrote in — ticket {{ticket_id}} is open. Replying goes straight to them.</p>",
    "cc": "support-leads@yourdomain.com, {{assigned_rep}}",
    "bcc": "archive@yourdomain.com",
    "reply_to_email": "{{email}}"
  }'

Send it — from the browser, or from your server

Browser (unchanged EmailJS call). The CC/BCC live in the template, so your front-end code doesn't change:

await emailjs.send("default", "support_ack", {
  name: "Ada", email: "ada@example.com",
  subject: "Login help", ticket_id: "T-4821", assigned_rep: "rep@yourdomain.com",
});

Server (REST API). The part EmailJS never offered: send the same stored template from a backend, where a secret key chooses the recipient — the fixed To binds only the public browser lane. This is the path for app notifications, receipts, and workplace automation:

curl https://api.emailfast.dev/v1/emails \
  -H "Authorization: Bearer ef_live_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "escalations@yourdomain.com",
    "template": "support_ack",
    "data": { "name": "Ada", "email": "ada@example.com", "subject": "Login help", "ticket_id": "T-4821", "assigned_rep": "rep@yourdomain.com" }
  }'

The template supplies the subject, body, CC, BCC, and Reply-To; data fills the variables; and any field you set on the request itself overrides the template's default for that one send.

CC and BCC per send, without a template

If you're sending ad-hoc rather than from a template, pass the recipients inline:

curl https://api.emailfast.dev/v1/emails \
  -H "Authorization: Bearer ef_live_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "customer@example.com",
    "cc": ["account-manager@yourdomain.com"],
    "bcc": ["compliance@yourdomain.com"],
    "reply_to": "account-manager@yourdomain.com",
    "subject": "Your invoice",
    "html": "<p>Attached is your invoice.</p>"
  }'

Every address is validated, unsubscribes and suppressions are honoured for CC and BCC recipients too, and the total recipient count is capped so a single call can't fan out into a blast.

The security upgrade

The reason to switch is not the code you keep; it is the abuse you stop. A key shipped in frontend source is public by definition, so the platform has to assume it is in hostile hands: a browser SDK with EmailJS-compatible endpoints — the recipient always comes from the server-stored template, never from the request, so a public key in your frontend can't be abused to spam arbitrary addresses.

Concretely, per key and template:

Try it against the sandbox

Attach the template to a sandbox project first: sandbox keys (ef_sandbox_…) that run the real pipeline dry: real validation, real rendering, real events, a hosted capture inbox — and no email leaves.

Next

Questions, answered plainly

Do I have to rewrite my EmailJS integration?

No. @email-fast/browser keeps the init, send, and sendForm signatures, and the endpoints accept the EmailJS request body — user_id, service_id, template_id, template_params — unchanged.

Can a template CC or BCC people, like EmailJS?

Yes. A template carries CC, BCC, and Reply-To fields, and every field except To — subject, body, CC, BCC, Reply-To — can contain {{variables}} filled from the send. The To address is fixed when the template is created: on the browser lane the recipient never comes from the request. CC recipients are visible to each other; BCC are hidden and never appear in a header.

Can my backend send the same templates, not just the browser?

Yes. The REST API takes { "to": "…", "template": "your_slug", "data": { … } }, so the same stored template that your contact form uses can also be sent server-side from your app — one template, two callers.

Can someone abuse the public key in my page source?

They can call it, but on the browser SDK the recipient always comes from the server-stored template — never from the request — so the worst they can do is send your own template to its configured recipient, inside your rate caps and only from allowlisted origins.

See it for yourself

Sandbox keys run the real pipeline dry — real validation, real events, a hosted inbox, no email sent. Early access is onboarding now.