How to Send Emails in Astro (2026 Guide)

Astro is a static-first framework, but it supports server-side rendering (SSR) and API endpoints. With SSR enabled, you can send emails from server endpoints just like any Node.js backend. API keys stay on the server.
This guide covers Astro server endpoints, form handling, and production patterns for email sending.
Enable SSR
You need SSR mode to run server-side code. Add an adapter:
npx astro add node # or vercel, netlify, cloudflare// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server', // or 'hybrid' for mixed static + server
adapter: node({ mode: 'standalone' }),
});Install
npm install sequenzynpm install resendnpm install @sendgrid/mailAdd your API key to .env:
SEQUENZY_API_KEY=sq_your_api_key_hereRESEND_API_KEY=re_your_api_key_hereSENDGRID_API_KEY=SG.your_api_key_hereCreate a shared email client:
import Sequenzy from "sequenzy";
export const sequenzy = new Sequenzy();
// Reads SEQUENZY_API_KEY from env automaticallyimport { Resend } from "resend";
export const resend = new Resend(import.meta.env.RESEND_API_KEY);import sgMail from "@sendgrid/mail";
sgMail.setApiKey(import.meta.env.SENDGRID_API_KEY);
export { sgMail };Server Endpoint
import type { APIRoute } from "astro";
import { sequenzy } from "../../lib/email";
export const POST: APIRoute = async ({ request }) => {
const { email, name } = await request.json();
if (!email || !name) {
return new Response(JSON.stringify({ error: "email and name required" }), {
status: 400,
});
}
try {
const result = await sequenzy.transactional.send({
to: email,
subject: `Welcome, ${name}`,
body: `<h1>Welcome, ${name}</h1><p>Your account is ready.</p>`,
});
return new Response(JSON.stringify({ jobId: result.jobId }));
} catch {
return new Response(JSON.stringify({ error: "Failed to send" }), {
status: 500,
});
}
};import type { APIRoute } from "astro";
import { resend } from "../../lib/email";
export const POST: APIRoute = async ({ request }) => {
const { email, name } = await request.json();
if (!email || !name) {
return new Response(JSON.stringify({ error: "email and name required" }), {
status: 400,
});
}
const { data, error } = await resend.emails.send({
from: "Your App <noreply@yourdomain.com>",
to: email,
subject: `Welcome, ${name}`,
html: `<h1>Welcome, ${name}</h1><p>Your account is ready.</p>`,
});
if (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
});
}
return new Response(JSON.stringify({ id: data?.id }));
};import type { APIRoute } from "astro";
import { sgMail } from "../../lib/email";
export const POST: APIRoute = async ({ request }) => {
const { email, name } = await request.json();
if (!email || !name) {
return new Response(JSON.stringify({ error: "email and name required" }), {
status: 400,
});
}
try {
await sgMail.send({
to: email,
from: "noreply@yourdomain.com",
subject: `Welcome, ${name}`,
html: `<h1>Welcome, ${name}</h1><p>Your account is ready.</p>`,
});
return new Response(JSON.stringify({ sent: true }));
} catch {
return new Response(JSON.stringify({ error: "Failed to send" }), {
status: 500,
});
}
};Form Handling in Astro Pages
Handle form submissions directly in an Astro page:
---
// src/pages/contact.astro
import { sequenzy } from "../lib/email";
let success = false;
let error = "";
if (Astro.request.method === "POST") {
const data = await Astro.request.formData();
const email = data.get("email") as string;
const message = data.get("message") as string;
if (!email || !message) {
error = "All fields are required";
} else {
try {
await sequenzy.transactional.send({
to: "you@yourcompany.com",
subject: `Contact from ${email}`,
body: `<p><strong>From:</strong> ${email}</p><p>${message}</p>`,
});
success = true;
} catch {
error = "Failed to send message";
}
}
}
---
<html>
<body>
<h1>Contact Us</h1>
<form method="POST">
<input name="email" type="email" placeholder="Your email" required />
<textarea name="message" placeholder="Message" required></textarea>
<button type="submit">Send</button>
</form>
{success && <p style="color: green">Message sent!</p>}
{error && <p style="color: red">{error}</p>}
</body>
</html>Going to Production
1. Verify Your Domain
Add SPF, DKIM, DMARC DNS records.
2. Use import.meta.env for Server Variables
In Astro, env vars without the PUBLIC_ prefix are server-only.
3. Choose Your Output Mode
output: 'server'for full SSRoutput: 'hybrid'for mostly static with some server routes
Beyond Transactional
Sequenzy handles transactional sends, marketing campaigns, automated sequences, and subscriber management from one SDK. Native Stripe integration for SaaS.
Wrapping Up
- Server endpoints (
src/pages/api/*.ts) for API-style email sending - Astro page forms for server-rendered form handling
- SSR adapters for deployment to Node, Vercel, Netlify, etc.
Pick your provider, enable SSR, and start sending.