Home/Email API/EmailJS vs a backend

EmailJS, and why client-side sending isn't enough

EmailJS exists for one honest reason: sending email from a contact form without writing a backend. Here's where that holds up, and where it doesn't.

Verified By George Hartley, Co-founder · Updated July 20, 2026

The job EmailJS does

A static site needs a contact form; a contact form needs to send email; email needs a server you don't have. EmailJS bridges that: your front-end calls their API with a public key, their servers send through your connected mail account. For a portfolio site's contact form, that's the whole job, done.

Where client-side sending stops

Everything in the browser is public: your template IDs, your public key, your rate limits, all inspectable and scriptable by anyone who opens devtools. There's no way to send transactional email safely (a password reset triggered purely client-side is an attacker's toy), no delivery events you can trust, and the free tier caps fast.

The pattern that scales keeps the form client-side and moves the send server-side: the form captures the contact against a public endpoint, and the email fires from the platform, where secrets live and events flow.

We built that public endpoint the paranoid way, and it shows what client-side surfaces demand: it always returns the same success response whether or not the contact exists, so nobody can use your form to enumerate email addresses, and it's idempotent, so double-submits change nothing. That is the level of thinking your contact form needs, and it's exactly what a client-side sending key can never give you.

We ship a browser-safe public client for exactly this pattern, @nitrosend/sdk/browser, so the front-end capture is typed and clean while every send stays server-side. And the loop it feeds is real: one of our customers wired their website contact form into a three-email welcome flow, built by asking Claude, in one instruction. Form captures, flow nurtures, nothing sent from the browser.

The key design enforces the boundary mechanically. Server code holds a secret key. The browser package takes only a public website key, requires an origins allowlist so another site can't lift the key and reuse it, and is hardcoded to refuse a secret key outright if someone tries to initialize it with one. The safe pattern shouldn't depend on developers reading the docs. It should be the only pattern that runs.

Full parameters and responses live in the REST API docs and the API reference.

The trap
Gmail app password
abcd efgh ijkl mnop
✕ 500 emails/day cap
✕ breaks when 2FA settings change
✕ a full-inbox credential in your code
The fix
POST /v1/messages
Authorization: Bearer ns_live_…
✓ scoped API key, revoke anytime
✓ 8,000 free sends, no daily cliff
✓ delivery events you can act on

Go deeper

The server-side send is one call from Node.js or TypeScript, and serverless functions mean "a backend" is ten lines on Vercel or Cloudflare.

First send in thirty seconds.

Simple pricing. Unlimited contacts.

Every plan includes full stack emailing: Flows, Newsletter Campaigns and Transactional Email, plus our NitroWheel LLM and all agent integrations (Claude, ChatGPT, Codex, Cursor and others). Pay for what you send, not who you store.

Free
$0
forever
  • Emails 8,000then 500/mo
  • Email types Transactional & Marketing
  • AI actions 20/mo
  • Contacts Free & Unlimited
  • Brands 1
  • Seats 1
Start free
Ultra
$100
per month
  • Emails 125,000/month
  • AI actions 5,000/mo
  • Brands 5
  • Seats 10 · Domains 3
  • Frontier AI Included
  • Dedicated IP Available
Get started
Enterprise
$300
per month
  • AI actions Unlimited
  • Unlimited brands & domains Included
  • SSO / SAML Included
  • 99.9% SLA Included
Get started

Free forever. No credit card required. See full comparison →