--- title: "Sending Email from a React App: A Backend Problem in Disguise" author: "Maksym Kuzmitskyi (MaximusFT)" published: "2026-07-31T22:59:51.879Z" updated: "2026-08-23T21:50:00.204Z" url: "https://zyvop.com/sending-email-from-a-react-app-a-backend-problem-in-disguise-k1vg8" canonical: "https://ma-x.im/blog/react-playbook-mails" tags: ["backend","Email","react","React Playbook"] --- # Sending Email from a React App: A Backend Problem in Disguise ![Cover image]() ![Sending Email from a React App: A Backend Problem in Disguise]() "When someone signs up, just send them a welcome email." It's the kind of request that gets tossed into a standup like it's nothing, and the person saying it genuinely believes it's a five-minute task. Send an email. How hard could it be? Here's the thing that makes email sneaky: the part you'd expect to be hard is trivial, and the part you'd expect to be trivial is where the whole thing lives. Triggering an email from your app is a single function call. _Actually getting that email into someone's inbox_ — not their spam folder, not bounced, not silently dropped — is a genuinely deep backend problem involving deliverability, sender reputation, authentication protocols, and a rendering environment stuck two decades in the past. And almost none of it is a React problem, which is exactly why it catches frontend developers off guard. So this article is a map of where the real work is. Because the most useful thing I can tell you about email in a React app is which parts to not do yourself, and why the boundary sits where it does. ## The Line: The Client Never Sends Email Start with the hard boundary, because it's the one people are tempted to cross. **Your React app does not send email. Ever.** It triggers a request to your backend, and your backend sends the email. This isn't a style preference, it's a security wall. Sending email requires credentials — API keys for your email service — and anything in your React bundle is public. Ship that key to the client and you've handed the entire internet the ability to send email _as you_, which means spammers now have a free megaphone wearing your domain's reputation. Within days your domain is blacklisted and even your legitimate mail stops arriving. So the flow is always the same shape as [payments]() and [auth](): the client asks, the server acts, and the credential never leaves the server. ```ts // The client's entire involvement: ask the backend to do it. async function requestWelcomeEmail(userId: string) { await fetch('/api/emails/welcome', { method: 'POST', body: JSON.stringify({ userId }), }); } ``` That's the whole frontend story. There is no `sendEmail` in your components, no email API key in your environment variables with a public prefix, no SMTP in the browser. The moment email touches the client, you've done it wrong. Everything interesting from here happens on the server. ## Why You Don't Talk to SMTP Yourself On the backend, the naive mental model is that you connect to a mail server over SMTP and send. You _can_ do that. You shouldn't, and understanding why is the core of this whole topic. Getting an email delivered is not about transmitting it — it's about being _trusted_ enough that the receiving server puts it in the inbox instead of the spam folder or the trash. That trust is a system: your domain needs authentication records (SPF, DKIM, DMARC) that prove you're allowed to send as your domain; your sending IP needs a _reputation_ built over time; you need to handle bounces and complaints so you stop mailing dead addresses; and you need to not trip the spam filters that treat unknown senders with suspicion. Running your own mail server means owning all of that — reputation, blacklist monitoring, authentication, the works — and it is a full-time infrastructure job that has nothing to do with your product. This is why transactional email services — [Resend](), Postmark, SendGrid — exist, and why using one isn't laziness but the correct architecture. They've spent years building sender reputation, they handle the authentication protocols, they manage bounces and deliverability, and they give you a clean API. Your backend calls that API; the hard, invisible work of _actually getting delivered_ is theirs. ```ts // Your server. The service owns deliverability; you own intent. import { Resend } from 'resend'; const resend = new Resend(process.env.RESEND_API_KEY); // server-only secret app.post('/api/emails/welcome', async (req, res) => { const user = await getUser(req.body.userId); await resend.emails.send({ from: 'hello@yourdomain.com', to: user.email, subject: 'Welcome aboard', react: , // more on this below }); res.json({ queued: true }); }); ``` Same lesson as every article in this series: the raw capability (SMTP) is a footgun, and the value is a service that owns the messy, reputation-dependent edges. You are buying deliverability, not a send button. ## The Genuinely Weird Part: Email Rendering Is Stuck in 2005 Here's the one place your React skills partly transfer, and it comes with a nasty surprise. You'd assume you can build an email template with normal components and modern CSS. You cannot, and this trips up every frontend developer exactly once. Email clients — Outlook especially, but not only — render HTML with engines that are decades behind browsers. No flexbox, no grid, no modern CSS, inconsistent support for things you consider baseline. The reliable way to lay out an email is _tables_, the way the web worked in 2005, with inlined styles because `