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 agentisendEnvironment
| Variable | Required | What it is |
|---|---|---|
AGENTISEND_API_KEY | yes | A key with sending_access. |
MAIL_FROM | yes | The From address, on a domain you have verified. The contact form sends to it, too. |
AGENTISEND_BASE_URL | no | Defaults 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;
}
},
};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}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.