
How I Track Free Contact Requests Without Webhooks
Published on Sat Jul 19 2025
π Introduction
When I launched the Contact page on my portfolio site, I wanted to offer a way for visitors to connect with me β either for collaboration, hiring, or casual inquiries. While Calendly helped with booking slots, I needed a way to track contact form submissions β without paying for webhooks.
This blog shows how I implemented free tracking using:
- β Google Sheets (as a backend log)
- β Custom Netlify Functions (sendmail.js)
- β Email metadata as a fallback log
If youβre running a serverless site and want a transparent way to log messages or inquiries, this guide is for you.
π§© Architecture Overview
My system is simple and cost-free:
[Frontend Form] --> [Netlify Function: sendmail.js] -->
ββ> Compose Email via maildata.js
ββ> Send Email using Resend (with Zoho reply-to)
ββ> Authenticate request via auth.js
ββ> Log metadata into Google Sheet (logToSheet.js)
This way, even if Calendly doesn't send webhooks, I still have a record of who contacted me and why.
π Step 1: Email Metadata Includes Contact Purpose
In my maildata.js, I generate a custom email body using user inputs like:
const html = `
<h3>New Contact Request</h3>
<p><strong>Name:</strong> ${name}</p>
<p><strong>Email:</strong> ${email}</p>
<p><strong>Reason:</strong> ${reason}</p>
<p>They would like to connect with you.</p>
`;
This helps me trace contact requests even if the Sheet logging fails. Every email carries the intent clearly.
π¨ Step 2: Sending the Email
The main serverless function sendmail.js:
- Parses request body (user name, email, etc.)
- Generates email body via maildata.js
- Sends via Resend API using a custom subdomain sender
- Includes a reply-to address hosted on Zoho Mail
- Calls auth.js (Auth Serverless Function) to validate the API call
- Logs data to Google Sheets
await resend.emails.send({
from: 'contact@mail.kaushikparui.com',
to: TO_ADDRESS,
subject: "New Contact Request",
html,
reply_to: 'kaushik@yourdomain.com', // Zoho-hosted inbox
});
This keeps communication one-way by default, while still enabling replies.
π Step 3: Authenticating Requests
Before logging anything, my Netlify function calls a local auth.js module:
if (!validateToken(headers['x-api-key'])) {
return {
statusCode: 401,
body: 'Unauthorized',
};
}
This layer prevents spam or unauthenticated write attempts to your Google Sheet.
π Step 4: Logging to Google Sheets
To avoid email-only logs, I used the Google Sheets API to append entries via logToSheet.js. I'm using a GCP Server Key, and the credentials are stored encrypted in Base64 inside Netlify environment variables.
Hereβs the core:
await sheets.spreadsheets.values.append({
spreadsheetId,
range: `${sheetName}!A1`,
valueInputOption: "USER_ENTERED",
requestBody: {
values: [[timestamp, name, email, reason, status]],
},
});
This allows me to have a timestamped history of all contact requests.
A sample row in my sheet looks like:
[2025-07-18 23:40, John Doe, johndoe@email.com, Portfolio Feedback, β Sent]
π§ Why This Works (Even Without Calendly Webhooks)
Calendly does not support free webhooks. So I needed a way to rely only on form submissions from my website β not on Calendly to report back.
By embedding key data in both:
- π§ The email (sent via Resend with Zoho reply-to)
- π The sheet (via Google Sheets API)
β¦I ensure I never lose track of a message.
If the Sheet fails, I still have the email. If email fails, I can catch it in error logs. Itβs fail-safe without cost.
βοΈ Optional Improvements (I May Add Later)
- β Add status tracking (e.g., Sheet row = pending, replied, ignored)
- π Set up daily email digest from Sheet data (using Google Apps Script)
- π Include Calendly meeting ID manually if applicable
- π Secure Netlify Function with auth.js (done β )
π Final Thoughts
Not every project needs enterprise-grade infra. My site runs serverless on Netlify, uses free tools, and yet I still:
- Track all contact form messages
- Store visitor intent data securely
- Stay in control of my own backend logic
If you're an indie developer, freelancer, or job-seeker β this setup gives you professional-grade tracking without cost.
Feel free to fork this idea, tweak it, or ask me questions on LinkedIn.
Stay sharp β and may your inbox be full of interesting opportunities! πΌ