Migrate · EmailJS
Migrate from EmailJS to Formward
EmailJS sends an email from browser JavaScript through a template, using a public key, a service ID and a template ID. It does not keep submissions as records; what it keeps is a history of sent emails, for a limited time.
Formward replaces the email-sending step with a form backend. The form posts to an endpoint, the submission is stored and spam-checked, and you get a notification email. You can keep a small fetch() handler, or drop the JavaScript and give the form an action.
Last checked 7 October 2026. What this guide says about EmailJS comes from EmailJS's own documentation, listed under Sources. Check their current docs if something looks different in your account. The Formward side is the reference in the migration docs.
The change
Replace the emailjs.sendForm call with a fetch() to your Formward endpoint, or remove the script and set the form's action to the endpoint.
Before: EmailJS
import emailjs from "@emailjs/browser";
emailjs.init({ publicKey: "YOUR_PUBLIC_KEY" });
form.addEventListener("submit", async (event) => {
event.preventDefault();
await emailjs.sendForm("YOUR_SERVICE_ID", "YOUR_TEMPLATE_ID", form);
});After: Formward
form.addEventListener("submit", async (event) => {
event.preventDefault();
const res = await fetch("https://forms.formward.eu/f/<formId>", {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
const data = await res.json();
if (!data.ok) throw new Error(data.error);
});Field and feature mapping
| EmailJS | Formward | Notes |
|---|---|---|
| emailjs.init({ publicKey }) | remove | No keys in the browser. The endpoint is protected by allowed origins, rate limits and the honeypot. |
| service ID and template ID | form ID in the endpoint URL | One Formward form per EmailJS template you used for a form. |
| Template recipient | Notification email on the form | Extra recipients from Personal. |
| Template subject | _subject | Or leave the subject to the form's default. |
| Template reply-to | A field named email sets Reply-To. If the visitor's email field has another name, rename it to email or add _replyto. | |
| Auto-reply template | Autoresponder | On Professional and up. |
| reCAPTCHA v2 | _gotcha, optional Turnstile | Add the honeypot; switch on Cloudflare Turnstile per form if you need a challenge. |
| File input with sendForm | file input, multipart | Files are stored with the submission on Professional and up (5 files of up to 10 MiB each on Professional). |
What changes, what stays
Changes
- The submit handler, and the @emailjs/browser dependency goes.
- Recipient, subject and auto-reply move from EmailJS templates to the Formward form.
- The notification email is Formward's, listing the submitted fields, rather than your EmailJS template.
Stays the same
- Your form markup and field names.
- The submit still happens in JavaScript if you want it to, with the same preventDefault pattern.
Differences to test before you switch
Specific to EmailJS
- EmailJS needs JavaScript to send anything. Formward also accepts a plain HTML POST, so the form keeps working with scripts blocked if you set the action.
- EmailJS's send and sendForm resolve with a status and a text. With Accept: application/json Formward returns { ok, id } or { ok, error }, so adjust the success check.
- Formward's notification is a standard email listing the fields. Custom notification branding is on Business.
How Formward behaves
- JSON responses. Send Accept: application/json and a success is a 200 with { ok: true, id }. An error is { ok: false, error } with a status code. Without that header, a successful post redirects or shows Formward's thanks page. Code that only checks res.ok keeps working.
- Status codes. 402 when the plan's monthly submission limit is reached (the body has upgrade: true), 422 when a field fails a validation rule set on the form or required consent is missing, 429 when one address posts too often (with a Retry-After header), and 403 when the origin is not allowed, the Turnstile check fails or files are sent on a plan without uploads.
- File uploads. Uploads need enctype="multipart/form-data" and a plan that includes them: Professional and up (5 files of up to 10 MiB each on Professional). On Free and Personal a post that carries files is refused with 403, so test an upload before you switch a form that takes attachments.
- Redirects. A hidden _redirect is followed only if it is a relative path or a URL on one of the form's allowed origins; otherwise it is ignored and the redirect set on the form applies, then Formward's thanks page. JSON (AJAX) requests are never redirected.
- Spam. If the _gotcha honeypot is filled in, the sender gets a normal success response, but the submission is stored as spam, sends no notification and does not count toward your quota. Rate limits apply on every plan; Cloudflare Turnstile and disposable-email blocking are optional per form.
- Allowed origins. With the list empty, any site can post to the endpoint. Once you add an origin, requests whose Origin header does not match are refused with 403, including server-side requests that send no Origin header.
- Reply-To. The Reply-To of the notification email is taken from _replyto, then _reply_to, then a field named email. So a plain email field sets Reply-To with no rename, and _replyto only overrides it, for example when the email field has another name or the form has several. An invalid address is dropped, and the email then has no Reply-To.
Every status code and body is listed in responses and redirects.
History, integrations and settings
- EmailJS sends email rather than storing submissions, so there is usually no submission history to bring across. Its sent-email history is kept for 7 days on Free and 30 days on paid plans, and the emails already in your inbox remain your record.
- Email services, templates and auto-reply templates belong to your EmailJS account and do not move with the endpoint. Recreate the ones you need: Formward webhooks (including Slack, Discord, Teams and Mattermost), Google Sheets and Baserow sync start on Professional, the autoresponder on Professional, and extra notification recipients on Personal.
Pricing and data location
- EmailJS's free plan covers 200 requests a month with 2 templates; paid plans start at $9 a month for 2,000 requests.
- EmailJS states that its servers are in the USA, operated by Amazon Web Services, and its DPA is based on the EU Standard Contractual Clauses.
- Formward is priced in euros: Free with 50 submissions a month, then Personal at EUR 15, Professional at EUR 30 and Business at EUR 90 a month (EUR 10, 20 and 60 billed yearly).
- Formward stores and processes submissions at Hostup AB in Sweden and sends notification email through Mailjet in the EU. The one optional non-EU sub-processor is Cloudflare Turnstile, on forms where you switch it on.
The full side-by-side, with sources, is in Formward vs EmailJS. Formward's prices are on the plans page, and where each part of Formward runs is on EU data residency.
Step-by-step checklist
- Create the form. In the Formward dashboard, create a form, set the notification email and copy the endpoint.
- Replace the handler. Swap emailjs.sendForm for a fetch() to the endpoint, or remove the script and set the form's action.
- Remove the SDK and keys. Uninstall @emailjs/browser and delete the public key, service ID and template ID from your code.
- Add the honeypot. Add a hidden _gotcha input. A visitor email field named email sets Reply-To; if yours has another name, rename it to email or add _replyto.
- Restrict origins. Add your site's origin, for example https://www.example.com, to the form's allowed origins. Leave the list empty if you also post from a server.
- Test before you switch. Point a copy of the form at Formward and send a few entries: a normal one, one with the honeypot filled, and one with a file if the form takes uploads. Check the dashboard and the notification email.
- Recreate the auto-reply. If you used an EmailJS auto-reply template, set up the autoresponder on the Formward form.
- Retire the EmailJS form. Switch the live form, watch the first real submissions arrive, then disable the form at EmailJS so nothing is split between two inboxes.
Sources
Last checked 7 October 2026
Move one form first
The free plan takes 50 submissions a month with no card. Point a copy of one form at Formward, send a few test entries, and switch the live form once the path works end to end.