Docs

Frameworks and agents · Updated 2026-09-22

Send email from SvelteKit

A contact form whose action runs on the server, so the key stays there and the form works without JavaScript.

To send email from SvelteKit, put the send in a form action in +page.server.ts: the action runs on the server, so the API key stays there, and the form works with JavaScript switched off. The file below is executed against a real API on every build of this site's repository.

Install

pnpm add agentisend

Environment

VariableRequiredWhat it is
AGENTISEND_API_KEYyesA key with sending_access.
MAIL_FROMyesThe From address, on a domain you have verified. The contact form sends to it, too.
AGENTISEND_BASE_URLnoDefaults to https://api.agentisend.com.

The action

Save it as src/routes/contact/+page.server.ts. In your own project the signature comes from the generated types: import type { Actions } from './$types' and export const actions = {…} satisfies Actions.

/**
 * SvelteKit — `src/routes/contact/+page.server.ts`.
 *
 * A form action runs on the server, so the API key stays there and the form
 * works with JavaScript switched off. The action returns a plain object, which
 * SvelteKit hands back to the page as `form`.
 *
 * In your own project the signature comes from the generated types:
 * `import type { Actions } from './$types'` and `export const actions = {…} satisfies Actions`.
 * It is written structurally here so this directory typechecks on its own.
 */
import { AgentiSend, AgentiSendError } from 'agentisend';

const agentisend = new AgentiSend();

function mailFrom(): string {
  const from = process.env.MAIL_FROM;
  if (!from) throw new Error('Set MAIL_FROM to an address on a domain you have verified.');
  return from;
}

export interface ActionResult {
  status: number;
  data: { id?: string; code?: string; fix?: string; error?: string };
}

export const actions = {
  default: async ({ request }: { request: Request }): Promise<ActionResult> => {
    const form = await request.formData();
    const email = String(form.get('email') ?? '');
    const message = String(form.get('message') ?? '');
    if (!email || !message) {
      return { status: 400, data: { error: 'Fill in both fields.' } };
    }

    try {
      const { id } = await agentisend.emails.send({
        from: mailFrom(),
        to: mailFrom(),
        reply_to: email,
        subject: `Contact form from ${email}`,
        text: message,
      });
      return { status: 200, data: { id } };
    } catch (err) {
      if (err instanceof AgentiSendError) {
        return { status: err.status, data: { code: err.code, fix: err.fix } };
      }
      throw err;
    }
  },
};
examples/sveltekit-form-action/page.server.ts, executed against the live API on every build

The form

<script>
  export let form;
</script>

<form method="POST">
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button>Send</button>
</form>

{#if form?.data?.id}<p>Sent.</p>{/if}
{#if form?.data?.fix}<p>{form.data.fix}</p>{/if}
src/routes/contact/+page.svelte

What comes back when it goes wrong

Every refused request carries code, message, fix and docs_url. The action returns code and fix to the page, so the person who filled in the form reads the sentence that repairs it. The error catalogue lists every code.

Next