A contact form is one of those small website features that looks deceptively simple. You add a name field, an email field, a message box, and a button. Done, right?
Not quite.
The browser can collect the information, but Astro still needs a secure way to receive that information and send an email without exposing your email-service credentials to visitors.
The good news is that the process is straightforward. In this guide, we'll connect an Astro contact form to email using a server-side API route and Resend. We'll also look at validation, spam protection, deployment, troubleshooting, and the newer Astro Actions approach.
By the end, you'll have a practical setup you can adapt to a portfolio, business website, agency site, blog, or landing page. Once your new site starts collecting incoming traffic, learn How to Remove Duplicate Leads in Google Sheets to keep your database clean and actionable.
Why Connect an Astro Contact Form to Email?
A contact form is essentially a bridge between your website and your inbox. The visitor fills it out on one side, and your email system receives the message on the other.
Astro doesn't automatically send an email simply because a form exists. You need a server-side process that receives the submission and passes it to an email provider.
A typical flow looks like this:
Visitor → Astro form → Server endpoint → Email provider → Your inbox
That separation is important because the browser should never receive your private email API key.
Why Email Matters
Without an email delivery mechanism, your form may look perfectly functional while doing absolutely nothing useful with the submitted information.
Email gives you a simple notification system. Someone submits a project inquiry, support question, booking request, or general message, and your team can receive it almost immediately.
What You Will Build
In this tutorial, we'll create an Astro form containing a name, email address, and message.
The form will send a POST request to an Astro API endpoint. That endpoint will read the submitted form data and use Resend to deliver the message.
Astro's official form recipe recommends receiving form data through a server endpoint and validating submitted values before using them.
How Astro Handles Contact Forms
Astro gives you several ways to handle form submissions. For a simple contact form, an API route is easy to understand and flexible enough for most websites.
Astro also provides Actions, which offer type-safe backend functions and built-in support for validating form data.
Static vs Server Rendering
This distinction matters.
A purely static page cannot execute arbitrary server-side email-sending code when a visitor submits a form. You need an on-demand server environment for the endpoint.
Astro's documentation explains that server endpoints can operate as live API routes when on-demand rendering is enabled.
So, if your website currently generates only static files, configure an appropriate server or serverless adapter before relying on a server endpoint.
API Routes and Actions
An API route gives you an endpoint such as:
/api/contact
You send a request there, process the data, and return a response.
Astro Actions provide another approach. They are particularly attractive when you want automatic input handling, validation, typed backend functions, and less manual request-processing code.
For beginners, an API route is often the clearest way to understand what's happening under the hood.
Choose an Email Delivery Service
You could theoretically connect directly to an SMTP server, but that often creates unnecessary complexity.
A transactional email service handles the delivery infrastructure for you. You provide the recipient, sender, subject, and message, and the service takes care of communicating with mail servers.
Why SMTP Is Not Always Ideal
Direct SMTP configuration can involve ports, authentication, TLS settings, credentials, and hosting restrictions.
That's a lot of plumbing for a contact form.
An email API is more like ordering food from a restaurant: you provide exactly what you want, and the delivery infrastructure is someone else's problem.
Why Use Resend
Resend provides an email API designed for application-generated emails and has dedicated Astro integration guidance.
Its Astro documentation shows both an API-route approach and an Astro Actions approach for sending email from the server.
For this example, we'll use its Node SDK.
Prerequisites for the Project
Before writing code, make sure your Astro project is ready for server-side execution.
Astro Project
You need an existing Astro project with Node.js and the project's dependencies installed.
If your project is already running with npm run dev, you're in good shape.
You'll also want an Astro deployment target capable of running your server-side endpoint.
Resend Account
Create an account with Resend and obtain an API key.
Keep that key private. Treat an email API key like a password, not like a public website setting.
Install the Required Package
Once the project is ready, install the Resend package.
Installing Resend
Run:
npm install resendThis adds the Resend SDK to your Astro project.
Checking package.json
After installation, you should see resend listed in your project's dependencies.
At this point, don't put the API key directly into your Astro component. That's a common mistake, and it's one you definitely want to avoid.
Configure Your Email Environment Variables
Your API key belongs in an environment variable.
Creating .env
Create a .env file in your project's root directory:
RESEND_API_KEY=your_api_key_hereYou can also define other private values, such as the destination email address:
RESEND_API_KEY=your_api_key_here
CONTACT_TO_EMAIL=you@example.comAstro supports environment variables for server-side code, while variables prefixed with PUBLIC_ are exposed to client-side code.
Protecting the API Key
Never rename your secret to something like PUBLIC_RESEND_API_KEY.
The PUBLIC_ prefix exists specifically for values that can safely reach the browser.
Modern Astro versions also provide astro:env for type-safe environment configuration and server-side secrets.
For a straightforward project, import.meta.env.RESEND_API_KEY is enough to get started.
Create the Astro Contact Form
Now let's build the visible part of the feature.
Create or edit your contact page, such as:
src/pages/contact.astro
Form Fields
A simple form might look like this:
<form id="contact-form">
<label for="name">Name</label>
<input id="name" name="name" type="text" required />
<label for="email">Email</label>
<input id="email" name="email" type="email" required />
<label for="message">Message</label>
<textarea id="message" name="message" required></textarea>
<button type="submit">Send Message</button>
<p id="form-status"></p>
</form>The important part isn't the styling. It's the name attribute on every input.
Those names are what your server will use to retrieve the submitted values.
Accessibility and Validation
Use <label> elements and connect them to their corresponding controls with for and id.
The required attribute gives you basic browser-side validation, while type="email" helps catch obviously malformed addresses.
But don't stop there.
Client-side validation improves the user experience; server-side validation protects your application.
Create an Email API Endpoint
Now we need somewhere for the form submission to go.
Create:
src/pages/api/contact.ts
Creating the Endpoint File
Start with:
import type { APIRoute } from "astro";
import { Resend } from "resend";
const resend = new Resend(import.meta.env.RESEND_API_KEY);
export const POST: APIRoute = async ({ request }) => {
// Handle form submission here
};If your Astro project uses a mode where endpoints are prerendered, make sure this endpoint is configured for on-demand execution. Astro's documentation specifically notes that live API routes require server-side/on-demand rendering.
Reading Form Data
Inside the endpoint, read the submitted form:
const formData = await request.formData();
const name = formData.get("name")?.toString().trim();
const email = formData.get("email")?.toString().trim();
const message = formData.get("message")?.toString().trim();Now your server has access to the values submitted by the visitor.
Send the Email With Resend
This is the moment where the pieces connect.
Building the Email
Add validation and send the email:
if (!name || !email || !message) {
return new Response(
JSON.stringify({ error: "All fields are required." }),
{ status: 400 }
);
}
const { error } = await resend.emails.send({
from: "Website Contact <onboarding@resend.dev>",
to: ["you@example.com"],
subject: `New message from ${name}`,
replyTo: email,
text: `
Name: ${name}
Email: ${email}
Message:
${message}
`,
});
if (error) {
console.error(error);
return new Response(
JSON.stringify({ error: "Unable to send message." }),
{ status: 500 }
);
}
return new Response(
JSON.stringify({ success: true }),
{ status: 200 }
);Resend's official Astro example uses its SDK from an Astro server endpoint, keeping the email operation on the server.
Handling API Errors
Don't blindly return a success response.
If the email provider rejects the request, return an error status. Logging the provider's error on the server can also make debugging dramatically easier.
Notice the use of replyTo.
That's useful because you can receive the message at your business inbox while clicking "Reply" takes you back to the visitor's email address.
Connect the Frontend to the Endpoint
The final piece is connecting the form to /api/contact.
Using fetch()
Add a client-side script:
<script>
const form = document.querySelector("#contact-form");
const status = document.querySelector("#form-status");
form?.addEventListener("submit", async (event) => {
event.preventDefault();
status.textContent = "Sending...";
const formData = new FormData(form);
const response = await fetch("/api/contact", {
method: "POST",
body: formData,
});
if (response.ok) {
form.reset();
status.textContent = "Thanks! Your message has been sent.";
} else {
status.textContent = "Something went wrong. Please try again.";
}
});
</script>The browser now sends the form to your Astro endpoint instead of simply refreshing the page.
Showing Success Messages
A small success message makes the form feel much more polished.
You can later replace the text message with a styled alert, animation, redirect, or inline confirmation component.
The important thing is to distinguish between "the request was sent" and "the email was successfully accepted by the email provider."
Add Server-Side Validation
Never assume that data coming from a browser is trustworthy.
Validate Required Fields
At minimum, check that the expected fields exist:
if (!name || !email || !message) {
return new Response(
JSON.stringify({ error: "Missing required fields." }),
{ status: 400 }
);
}You should also validate the email format and impose reasonable length limits on text fields.
Prevent Invalid Requests
For example, a message field doesn't need to accept several megabytes of text.
Set practical limits:
if (message.length > 5000) {
return new Response(
JSON.stringify({ error: "Message is too long." }),
{ status: 400 }
);
}For more advanced projects, use a schema validator such as Zod. Astro Actions can make this particularly convenient because they support form validation as part of the action workflow.
Protect Your Contact Form From Spam
Once your website becomes public, bots will eventually discover your form.
That's not a possibility. It's practically a tradition.
Honeypot Fields
A honeypot is an invisible or visually hidden field that normal visitors won't fill out.
If the field contains a value when submitted, you can treat the request as suspicious.
It's lightweight and doesn't force legitimate users to solve puzzles.
Rate Limiting and CAPTCHA
For higher-traffic websites, consider rate limiting and a CAPTCHA-style service.
You can also limit how frequently a single IP address or session can submit the form.
Spam protection should be part of the design, not something you bolt on after your inbox becomes unusable.
Use Astro Actions Instead of an API Route
API routes aren't your only option.
Astro Actions provide a modern way to create type-safe backend functions that accept form data and validate inputs.
When Actions Make Sense
Actions are especially useful when your project has several server-side operations and you want a consistent architecture.
They can reduce the amount of manual request parsing and error handling you need to write.
Form-Based Actions
Astro supports calling Actions directly from HTML forms with a POST method.
This can even support a zero-JavaScript form submission model, which is useful when you want your form to continue working even if client-side JavaScript doesn't load.
For a small site, either approach works. For a larger Astro application, Actions are worth learning.
Test the Contact Form Locally
Before deployment, test the complete flow.
Successful Submission
Run your Astro development server and submit a real test message.
Check:
- The browser shows the submission state.
- The request reaches
/api/contact. - The server doesn't report an API-key error.
- Resend accepts the message.
- The destination inbox receives it.
- Replying to the message uses the visitor's email address.
Troubleshooting Failed Emails
If the form appears to submit but no email arrives, check the server console first.
Then check your email provider's logs.
Common causes include an invalid API key, an unverified sender domain, an incorrect destination address, or an endpoint that isn't actually running server-side.
Deploy the Astro Contact Form
A contact form that works locally but fails in production usually has one of two problems: the server endpoint isn't running, or the environment variables weren't configured.
Server-Side Hosting
Choose an Astro deployment adapter compatible with your hosting platform.
Your API route needs to execute when the visitor submits the form. Astro's server endpoints are designed specifically for this kind of on-demand server functionality.
Production Environment Variables
Don't upload your local .env file containing secrets.
Instead, add RESEND_API_KEY to your hosting provider's environment-variable settings.
The exact interface depends on the platform, but the principle is always the same:
The secret exists on the server, not in the browser.
Improve Email Deliverability
Getting an email into your inbox is one goal. Getting it there reliably is another.
Verify Your Domain
For production websites, configure and verify your own sending domain with your email provider.
Instead of relying on a temporary development sender, you'll typically want something like:
Website <hello@example.com>
A verified domain gives your email setup a more professional foundation and can improve deliverability.
Use a Professional From Address
Keep the sender address under your own domain.
Then use the visitor's address as replyTo.
This distinction is important. The visitor's email should generally not be impersonated as the sender of your domain's message.
Common Problems and Fixes
Even a short contact form can fail in surprisingly creative ways.
API Key Errors
If Resend reports authentication problems, verify that RESEND_API_KEY exists in the server environment and that the application has been restarted after adding it locally.
Also make sure you haven't accidentally exposed or renamed the secret.
Form Works but No Email Arrives
Check the API response first.
If the API request succeeds, inspect the provider's email logs and spam folder. Then verify your sender configuration.
Remember: a successful browser request doesn't automatically mean successful email delivery.
Best Practices for Astro Contact Forms
A reliable contact form should be simple for visitors and strict behind the scenes.
Security
Keep API keys server-side, validate every submission, limit message sizes, protect against spam, and avoid putting sensitive credentials into frontend JavaScript.
Astro's environment-variable system specifically distinguishes server-only values from public client variables.
User Experience
Keep the form short.
Name, email, and message are often enough.
Show a clear loading state, provide an understandable error message, and tell users what happens after they submit.
A good form should feel like a door that opens immediately—not a maze with twelve rooms.
Conclusion
Connecting an Astro contact form to email doesn't require a complicated backend.
The basic architecture is simple: collect the form data in Astro, send it to a server-side endpoint, validate it, and use an email API such as Resend to deliver the message.
The most important part is security. Keep your API key on the server, validate incoming data, configure an on-demand deployment environment, and protect the form from spam.
Once that foundation works, you can build on it with auto-replies, database storage, file attachments, spam scoring, CRM integrations, or Astro Actions.
In other words, the contact form is just the front door. The server-side email workflow is the machinery behind it.
Frequently Asked Questions
Can I connect an Astro contact form directly to Gmail?
You can build an email workflow around Gmail or SMTP, but a transactional email API is often simpler for application-generated messages. Services such as Resend provide APIs specifically designed for this type of server-side email delivery.
Do I need an Astro backend to send contact-form emails?
You need some server-side or trusted backend process if you want to securely use an email API key. An Astro API route or Astro Action can provide that server-side layer.
Where should I store my Resend API key?
Store it in a server-side environment variable such as RESEND_API_KEY. Do not expose it through a PUBLIC_ variable or hard-code it into browser JavaScript. Astro's documentation explains that public environment variables are made available to client-side code, while server secrets can remain private.
Can an Astro contact form work without JavaScript?
Yes, particularly if you use Astro's form-based Actions. Astro supports form submissions that can work without client-side JavaScript, provided the page and server-side setup support the required on-demand behavior.
Why isn't my email being delivered after the form submits?
Check the server logs, email-provider response, API key, sender configuration, destination address, and provider logs. A form submission succeeding in the browser only confirms that the HTTP request completed; it doesn't necessarily prove that the email was delivered.
Should I use an API route or Astro Action?
For a small, straightforward contact form, an API route is easy to understand and customize. For larger applications, Astro Actions can reduce boilerplate by providing typed server functions and built-in form validation.
What should I use as the email sender?
For production, use a sender address on a domain you control and have verified with your email provider. Use the visitor's submitted address as replyTo rather than pretending the visitor is the sender.
Where can I learn more?
The most useful references are Astro's API-route form recipe, Astro Actions documentation, Astro's environment-variable documentation, and Resend's Astro integration guide.

Comments
Post a Comment