# Send email with Vue

> Send email from a Vue app with Email Fast: the browser SDK with a public ef_pub_ key and a server-locked template, or a small backend endpoint — both shown.

Canonical: https://emailfast.dev/send-email-with/vue

A Vue component can send email with no backend at all. 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. Initialize with your public key, name a server-stored template,
and hand `sendForm` the form element. If you run Nuxt or any backend, the second
pattern keeps a secret key server-side instead.

```bash
npm install @email-fast/browser
```

```html
<!-- ContactForm.vue -->
<script setup>
import { ref } from "vue";
import emailfast from "@email-fast/browser";

emailfast.init({ publicKey: "ef_pub_..." }); // public by design — safe to ship

const state = ref("idle");

async function submit(e) {
  state.value = "sending";
  try {
    await emailfast.sendForm("default", "contact_form", e.target);
    state.value = "sent";
  } catch {
    state.value = "error";
  }
}
</script>

<template>
  <form @submit.prevent="submit">
    <input name="name" required />
    <input name="email" type="email" required />
    <textarea name="message" required />
    <button :disabled="state === 'sending'">Send</button>
    <p v-if="state === 'sent'">Thanks — we got it.</p>
    <p v-if="state === 'error'">That didn't go through. Try again.</p>
  </form>
</template>
```

## Or: send through your own backend

With Nuxt, one server route keeps the secret key out of the bundle entirely:

```ts
// server/api/contact.post.ts — Nuxt; the key stays on the server
import { EmailFast } from "@email-fast/nodejs";

const ef = new EmailFast({ apiKey: process.env.EMAILFAST_API_KEY!, baseUrl: "https://api.emailfast.dev" }); // ef_sandbox_… while testing

export default defineEventHandler(async (event) => {
  const { message, formId } = await readBody(event);
  const { id } = await ef.send({
    to: "team@yourdomain.dev",
    subject: "New contact message",
    html: "<p>{{message}}</p>",
    data: { message },
    idempotency_key: `contact-${formId}`, // generated once per render — a double-submit replays
  });
  return { id };
});
```

## Gotchas

- Never reference a secret `ef_live_` key in component code — `VITE_…` env vars are inlined into the client bundle by design. Only `ef_pub_` may appear in the browser.
- In Nuxt, keep the browser-SDK form client-side: wrap it in `<ClientOnly>` or call `init` in `onMounted` — the send has to happen in the browser.
- Form field `name` attributes map to template variables: `name="email"` fills `{{email}}`.
- Use `@submit.prevent` — forgetting `.prevent` reloads the page mid-send.

## Next

- [Browser quickstart](/docs/quickstart-browser) — templates, `sendForm`, migrating from EmailJS
- [Frontend email without a backend](/features/frontend)
- [The email API](/features/email-api) — for the server-route variant
- [All docs](/docs)
