Send email with Svelte
Send from a component with a public key, or from a SvelteKit form action with a secret one.
Facts verified 2026-07-17 — corrections: hello@emailfast.dev
A Svelte component can send email straight from the browser. Email Fast ships a browser SDK with EmailJS-compatible endpoints — the recipient always comes from the server-stored template, never from the request, so a public key in your frontend can't be abused to spam arbitrary addresses. For a static Svelte site that is the whole answer; on SvelteKit you can instead keep a secret key in a server-only form action — both patterns below.
npm install @email-fast/browser<!-- ContactForm.svelte -->
<script>
import emailfast from "@email-fast/browser";
emailfast.init({ publicKey: "ef_pub_..." }); // public by design — safe to ship
let state = "idle";
async function submit(event) {
event.preventDefault();
state = "sending";
try {
await emailfast.sendForm("default", "contact_form", event.target);
state = "sent";
} catch {
state = "error";
}
}
</script>
<form on:submit={submit}>
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message" required />
<button disabled={state === "sending"}>Send</button>
{#if state === "sent"}<p>Thanks — we got it.</p>{/if}
{#if state === "error"}<p>That didn't go through. Try again.</p>{/if}
</form>Or: a SvelteKit form action
On SvelteKit, $env/static/private guarantees the key can never reach the client:
// src/routes/contact/+page.server.js
import { EMAILFAST_API_KEY } from "$env/static/private"; // ef_sandbox_… while testing
import { EmailFast } from "@email-fast/nodejs";
const ef = new EmailFast({ apiKey: EMAILFAST_API_KEY, baseUrl: "https://api.emailfast.dev" });
export const actions = {
default: async ({ request }) => {
const form = await request.formData();
await ef.send({
to: "team@yourdomain.dev",
subject: "New contact message",
html: "<p>{{message}}</p>",
data: { message: String(form.get("message")) },
idempotency_key: `contact-${form.get("formId")}`, // hidden field set once per render
});
return { ok: true };
},
};Gotchas
- Never import a secret
ef_live_key in component code. Vite inlinesVITE_…vars into the bundle, and SvelteKit refuses$env/static/privatein client files for exactly this reason — let it. - The handler calls
event.preventDefault()itself — Svelte 5 removed the|preventDefaultmodifier, and the explicit call works in every version. - Form field
nameattributes map to template variables:name="email"fills{{email}}. - For the form action, put a
formIdin a hidden input generated when the page renders — a double-submit then replays the same idempotency key instead of sending twice.
Next
- Browser quickstart — templates,
sendForm, migrating from EmailJS - Frontend email without a backend
- The email API — for the form-action variant
- All docs