DEV Community

Selfloom
Selfloom

Posted on • Originally published at github.com

Build a $0/month contact form backend with Google Sheets

Static sites do not need a monthly form backend.

If you build portfolio sites, landing pages, or simple client websites, the contact form is usually the awkward bit. You do not want to run a server just to collect a name, email, and message. But many hosted form services charge $10-20/month once you want Google Sheets, notifications, or no branding.

A small Google Apps Script can do the same job for free.

The architecture

The flow is simple:

HTML form -> Google Apps Script doPost -> Google Sheet
                                      -> email notification
                                      -> optional auto-reply
Enter fullscreen mode Exit fullscreen mode

Your form sends a POST request to a Google Apps Script web app. The script validates the submission, filters obvious spam, writes a row to Google Sheets, and sends an email notification with MailApp.sendEmail().

No server. No database. No SMTP provider. No monthly invoice.

Core handler

The endpoint is a doPost function:

function doPost(e) {
  try {
    return handlePost(e);
  } catch (err) {
    console.error(err);
    return jsonResponse('error', 'Internal server error.');
  }
}
Enter fullscreen mode Exit fullscreen mode

Inside handlePost, the useful pipeline is:

  1. Parse JSON or form-urlencoded input.
  2. Check a honeypot field.
  3. Validate required fields.
  4. Reject malformed email addresses.
  5. Apply simple keyword spam filtering.
  6. Rate limit repeated submissions.
  7. Append a row to Google Sheets.
  8. Send the owner an email notification.
  9. Optionally send an auto-reply.

A honeypot field is especially useful. Bots often fill every field they see. Humans never fill a hidden _gotcha field. If that field has a value, return a fake success and do not write to the sheet.

Google Sheets as the database

For a contact form, Google Sheets is a perfectly reasonable database. It is searchable, exportable, and easy for non-technical clients to understand.

function ensureSheet(sheetName) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName(sheetName);

  if (!sheet) {
    sheet = ss.insertSheet(sheetName);
    sheet.appendRow(['Timestamp', 'Name', 'Email', 'Message', 'Source URL']);
    sheet.setFrozenRows(1);
    sheet.getRange('D:D').setWrap(true);
  }
}
Enter fullscreen mode Exit fullscreen mode

Then append the submission:

function appendRow(sheetName, row) {
  SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName(sheetName)
    .appendRow(row);
}
Enter fullscreen mode Exit fullscreen mode

Email notifications without SMTP

Apps Script includes MailApp.sendEmail(), so the script can notify the site owner without SendGrid, Mailgun, or SMTP setup.

MailApp.sendEmail({
  to: cfg.recipientEmail,
  subject: 'New Contact Form Submission',
  body: `${name} <${email}> sent:\n\n${message}`
});
Enter fullscreen mode Exit fullscreen mode

That is enough for many client sites.

HTML integration

Your frontend only needs a normal submit handler:

const res = await fetch(SCRIPT_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: form.name.value,
    email: form.email.value,
    message: form.message.value,
    _gotcha: form._gotcha.value
  })
});

const json = await res.json();
Enter fullscreen mode Exit fullscreen mode

Deploy the Apps Script as a web app, set access to "Anyone", copy the deployment URL, and paste it into your form JavaScript.

When this is a good fit

This pattern works well for:

  • portfolio contact forms
  • agency landing pages
  • small business websites
  • waitlists
  • internal request forms
  • low-volume lead capture

It is not the right tool for file uploads, payment forms, or very high-volume transactional workloads.

Source code

I published a complete MIT-licensed implementation here:

https://github.com/ttcd77/form-handler

It includes Google Sheets storage, email notifications, honeypot spam protection, keyword filtering, rate limiting, auto-replies, and an example HTML form.

The project also has a one-time paid version for teams that want setup UI and multi-form quality-of-life features, but the open-source script is fully usable on its own.

Disclosure: this article was drafted with AI assistance and manually checked before posting.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.