Send email from React: call your backend, which calls the Inboxili API
Updated
There is no React package for Inboxili, and there should not be one: an API key in browser code is public. React talks to your own server, and your server uses the Node, Python or PHP SDK.
React runs in the visitor's browser, so anything in its code or environment is public. The Inboxili API key can send email as your domain, which means it must live only on a server you control.
The architecture
Read the diagram with one change in mind: "Your application" is your server, and the browser's React code talks to it instead.
- React submits a form to your endpoint, such as
POST /api/contact. - Your server validates the input, applies rate limits, and calls the Inboxili SDK.
- Your endpoint returns a simple success or error to React.
The React side
import { useState, type FormEvent } from "react";
type Status = "idle" | "sending" | "sent" | "error";
export function ContactForm() {
const [status, setStatus] = useState<Status>("idle");
const [error, setError] = useState<string | null>(null);
async function onSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
const form = e.currentTarget;
const data = Object.fromEntries(new FormData(form));
setStatus("sending");
setError(null);
try {
const res = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error ?? "Something went wrong.");
setStatus("sent");
form.reset();
} catch (err) {
setError(err instanceof Error ? err.message : "Something went wrong.");
setStatus("error");
}
}
if (status === "sent") return <p role="status">Thanks, we have your message.</p>;
return (
<form onSubmit={onSubmit}>
<label>Name <input name="name" required maxLength={100} /></label>
<label>Email <input name="email" type="email" required /></label>
<label>Message <textarea name="message" required minLength={5} maxLength={5000} /></label>
{/* Honeypot: hidden from people, filled by bots */}
<input name="website" tabIndex={-1} autoComplete="off" style={{ position: "absolute", left: "-9999px" }} aria-hidden="true" />
<button disabled={status === "sending"}>{status === "sending" ? "Sending…" : "Send"}</button>
{status === "error" && <p role="alert">{error}</p>}
</form>
);
}
The server side
Pick the backend you already have:
- Next.js: a route handler or server action in the same project.
- Express: the
POSTroute and mailer service. - Other: any language with an Inboxili SDK or plain HTTP.
For a complete, abuse-resistant handler, see the contact form workflow. It covers the honeypot field above, escaping, fixed recipients and rate limits.
Common mistakes
| Mistake | Why it hurts | Instead |
|---|---|---|
| REACT_APP_INBOXILI_KEY or VITE_INBOXILI_KEY | Bundled into public JavaScript | Keep the key in a server environment variable |
| Letting the form choose the recipient | Turns your domain into a spam relay | Fix the recipient in server code |
| No loading state | Users click Send repeatedly and send duplicates | Disable the button while sending, as above |
| Showing raw server errors | Leaks configuration | Return a generic message, log the detail |
| Calling the API from a proxy that forwards the key from the browser | The key still reaches the browser | Attach the key only on the server |
Frequently asked questions
- Can I put the Inboxili API key in a React environment variable?
- No. Anything bundled into a React app is visible to every visitor. A leaked key can send email as your domain until you revoke it.
- Do I need a server just to send one email?
- Yes, a small one: an Express route, a Next.js route handler, or a serverless function. It also gives you somewhere to validate input and limit abuse.
Official SDKs
Build with Inboxili
Create a workspace, verify a domain, and make your first API call.