eusend
Frameworks

SvelteKit

Send transactional email from SvelteKit through eusend — from a form action or a server endpoint, with the API key kept in private environment variables.

In SvelteKit, sending belongs in code that only runs on the server: a form action in +page.server.ts, or an endpoint in +server.ts. The eusend SDK uses the native fetch, so it runs on the Node, Vercel, Netlify and Cloudflare adapters alike.

Setup

npm install @eusend_dev/sdk
.env
EUSEND_API_KEY=eu_test_...

Read the key through $env/static/private. SvelteKit refuses to build if a private variable is imported into client code, which is the guarantee you want for an API key.

src/lib/server/eusend.ts
import { Eusend } from '@eusend_dev/sdk'
import { EUSEND_API_KEY } from '$env/static/private'

export const eusend = new Eusend(EUSEND_API_KEY)

Files under src/lib/server cannot be imported from the browser either.

Send from a form action

src/routes/contact/+page.server.ts
import { fail } from '@sveltejs/kit'
import { eusend } from '$lib/server/eusend'
import type { Actions } from './$types'

export const actions = {
  default: async ({ request }) => {
    const form = await request.formData()
    const email = String(form.get('email') ?? '')
    const message = String(form.get('message') ?? '')

    if (!email || !message) return fail(400, { email, missing: true })

    const { error } = await eusend.emails.send({
      from: 'Acme <hello@yourdomain.com>',
      to: 'support@yourdomain.com',
      replyTo: email,
      subject: 'New contact form message',
      text: message,
    })

    if (error) return fail(502, { email, code: error.name })
    return { sent: true }
  },
} satisfies Actions
src/routes/contact/+page.svelte
<script lang="ts">
  import { enhance } from '$app/forms'
  let { form } = $props()
</script>

{#if form?.sent}
  <p>Thanks — we will get back to you.</p>
{:else}
  <form method="POST" use:enhance>
    <input name="email" type="email" value={form?.email ?? ''} required />
    <textarea name="message" required></textarea>
    <button>Send</button>
    {#if form?.code}<p>Could not send your message. Please try again.</p>{/if}
  </form>
{/if}

The SDK returns { data, error } rather than throwing. error.name is the error code — log it, and show the visitor something plainer.

⚠

Send form submissions to your own address with the visitor in replyTo. A form field that chooses the recipient lets anyone send mail from your domain.

Send from an endpoint

src/routes/api/receipt/+server.ts
import { json } from '@sveltejs/kit'
import { eusend } from '$lib/server/eusend'
import type { RequestHandler } from './$types'

export const POST: RequestHandler = async ({ request }) => {
  const { orderId, email } = await request.json()

  const { data, error } = await eusend.emails.send(
    {
      from: 'Acme <billing@yourdomain.com>',
      to: email,
      subject: `Receipt for order ${orderId}`,
      html: `<p>Thanks — order ${orderId} is confirmed.</p>`,
    },
    { idempotencyKey: `receipt-${orderId}` },
  )

  if (error) return json({ code: error.name }, { status: error.statusCode ?? 502 })
  return json({ id: data.id })
}

The idempotencyKey makes a retry return the original email instead of sending a second one.

Next steps

  • Verify a domain to send to real recipients from your own address.
  • Templates to keep email markup out of your routes.
  • Webhooks to learn when a message is delivered, bounced or complained about.