Do you spin up a $5/month DigitalOcean droplet just to send an email? No.
Today, we are going to wire up a Serverless architecture using a Vercel frontend and Firebase Functions backend to handle a contact form with Gmail dispatch and reCAPTCHA security—all for (virtually) free.
The Stack
Frontend: Astro (HTML/JS/CSS) hosted on Vercel.
Backend: Firebase Cloud Functions (Node.js).
Security: Google reCAPTCHA v3.
Email: Nodemailer with Gmail App Passwords.
Phase 1: The Firebase Setup (Backend)
First, we need a place to run our logic.
Go to the Firebase Console and create a new project.
Upgrade your project to the Blaze Plan (Pay as you go).
Note: Sending emails requires outbound networking, which is locked on the free "Spark" plan. However, the Blaze plan has a massive free tier. Unless you are sending millions of emails, you won't be charged.
Install the Firebase CLI tools locally:
codeBash
npm install -g firebase-toolsInitialize your project in a new folder:
codeBash
mkdir backend-functions cd backend-functions firebase login firebase init functionsSelect JavaScript when asked.
Phase 2: Coding the Mailer
Navigate to your functions folder. We need a few tools:
codeBash
npm install nodemailer cors axiosOpen index.js. We will create a function that validates the reCAPTCHA token and sends the email via Gmail.
Important: You cannot use your standard Gmail password. Go to your Google Account -> Security -> 2-Step Verification -> App Passwords and generate one.
codeJavaScript
const functions = require("firebase-functions");
const nodemailer = require("nodemailer");
const cors = require("cors")({ origin: true });
const axios = require("axios");
// Configure your Gmail transporter
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: "your-email@gmail.com",
pass: "your-generated-app-password", // NOT your real password
},
});
exports.sendContactEmail = functions.https.onRequest((req, res) => {
cors(req, res, async () => {
if (req.method !== "POST") {
return res.status(405).send("Method Not Allowed");
}
const { name, email, message, captchaToken } = req.body;
// 1. Verify reCAPTCHA
const secretKey = "YOUR_RECAPTCHA_SECRET_KEY";
const verifyUrl = `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${captchaToken}`;
try {
const captchaResponse = await axios.post(verifyUrl);
if (!captchaResponse.data.success) {
return res.status(400).json({ error: "Bot detected." });
}
// 2. Send Email
const mailOptions = {
from: "Your Website <your-email@gmail.com>",
to: "admin@gvlab.com", // Where you want to receive the mail
subject: `New Message from ${name}`,
text: `From: ${email}\n\nMessage:\n${message}`,
};
await transporter.sendMail(mailOptions);
return res.status(200).json({ success: true });
} catch (error) {
console.error(error);
return res.status(500).json({ error: "Internal Server Error" });
}
});
});Deploy it:
codeBash
firebase deploy --only functionsCopy the Function URL provided in the terminal.
Phase 3: The Frontend (Astro/Vercel)
Now, let's build the UI. We need to register for Google reCAPTCHA v3 to get a Site Key.
In your Astro project (e.g., src/pages/contact.astro):
codeHtml
---
// src/pages/contact.astro
---
<html lang="en">
<head>
<title>Contact Us</title>
<!-- Load reCAPTCHA API -->
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>
</head>
<body>
<form id="contact-form">
<input type="text" id="name" placeholder="Name" required />
<input type="email" id="email" placeholder="Email" required />
<textarea id="message" placeholder="Message" required></textarea>
<button type="submit">Send Transmission</button>
</form>
<script>
const form = document.getElementById('contact-form');
form.addEventListener('submit', async (e) => {
e.preventDefault();
// Execute reCAPTCHA
grecaptcha.ready(function() {
grecaptcha.execute('YOUR_SITE_KEY', {action: 'submit'}).then(async function(token) {
const data = {
name: document.getElementById('name').value,
email: document.getElementById('email').value,
message: document.getElementById('message').value,
captchaToken: token
};
// Call Firebase Function
const response = await fetch('YOUR_FIREBASE_FUNCTION_URL', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.ok) {
alert("Message received.");
form.reset();
} else {
alert("Transmission failed.");
}
});
});
});
</script>
</body>
</html>Phase 4: Deployment
Push your Astro code to GitHub.
Import the repo into Vercel.
Deploy.
You now have a completely serverless, secured contact form. The Vercel app serves the static assets instantly, and Firebase wakes up only when someone hits "Send," costing you zero server maintenance and practically zero dollars.