Calling the API from a browser
Why calling Mailfully straight from your frontend exposes your API key, and the server-side proxy pattern that replaces it.
The Mailfully API is designed to be called from your server, not from your users' browsers. The reason is the key: anything your frontend can send with, your visitors can read.
Why the key is the real problem
Any key shipped to a browser is readable by every visitor who opens developer tools, and a send key lets anyone who finds it send mail as your verified domain and burn your quota. That is true however the request itself is handled: the key sitting in the page's JavaScript is readable either way, so moving the call server-side is the fix rather than any adjustment to how the browser sends it.
The pattern that works
Put a route on your own server that holds the key, validates its own input, and calls Mailfully. The browser calls your route; your route calls Mailfully.
import { Mailfully } from "mailfully";
const mailfully = new Mailfully({ apiKey: process.env.MAILFULLY_API_KEY ?? "" });
export async function POST(request: Request) {
const { email } = await request.json();
if (typeof email !== "string" || !email.includes("@")) {
return new Response("Invalid email", { status: 400 });
}
const { data, error } = await mailfully.emails.send({
from: "orders@mail.acme.com",
to: email,
subject: "Your order shipped",
text: "Order 4212 shipped.",
});
if (error !== null) {
return new Response(error.message, { status: error.statusCode ?? 500 });
}
return Response.json(data);
}
Scope the key down
Give the key your server route holds only the scopes it needs, rather than the default. Narrower scoping limits what's at risk if that server is ever compromised. See API key security for how scopes are chosen and secrets are stored.
If a key has already reached a browser
Treat it as leaked. See A leaked API key for how to rotate or revoke it and audit what it could reach.
