Contact Form with Cloudflare Workers

Add a working contact form to your Astro site using a Cloudflare Worker to handle submissions. No backend required.

1. Create the Worker

Inside your Hakuto project, add a Worker at worker/contact.ts:

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== "POST") {
      return new Response("Method not allowed", { status: 405 });
    }
    const { name, email, message } = await request.json();
    // Forward to your email provider (Resend, Postmark, etc.)
    await fetch("https://api.resend.com/emails", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${env.RESEND_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        from: "contact@yourdomain.com",
        to: "you@yourdomain.com",
        subject: `New contact from ${name}`,
        text: `From: ${email}\n\n${message}`,
      }),
    });
    return Response.json({ ok: true });
  },
};

2. Add the form in Astro

Create src/pages/contact.astro with a simple form that posts to your Worker:

<form id="contact-form" class="space-y-4">
  <input name="name" required placeholder="Name" />
  <input name="email" type="email" required placeholder="Email" />
  <textarea name="message" required rows="5"></textarea>
  <button type="submit">Send</button>
</form>

<script>
  const form = document.getElementById("contact-form");
  form?.addEventListener("submit", async (e) => {
    e.preventDefault();
    const data = Object.fromEntries(new FormData(form));
    await fetch("/api/contact", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data),
    });
    alert("Message sent.");
  });
</script>

3. Wire up the route

In wrangler.toml, add a route that forwards /api/contact to the Worker. Set RESEND_API_KEY as a secret with wrangler secret put RESEND_API_KEY.


Deploy. You now have a working contact form, no backend to maintain.