Wire Any Website Form to Google Sheets & Email — Apps Script Tutorial (2026)

No backend, no Vercel, no $5/month server. This 15-minute tutorial connects your contact form, partner form, or any HTML form to a Google Sheet that auto-saves submissions and emails you a notification — all using free Google Apps Script.

bolt What you'll build
FrontendHTML form on your site (already exists)
BackendGoogle Apps Script (free, serverless)
StorageGoogle Sheet — every submission becomes a new row
NotificationEmail to your inbox when someone submits
Cost₹0 — Google's free tier handles thousands of submissions/month
Setup time~15 minutes

If you've ever needed a contact form, a "Become a Partner" form, an early-access waitlist, or any form on a static site, you've probably hit the same wall: the form has nowhere to go. You either pay for Formspree / Netlify Forms / Basin (~$10/month after a tiny free tier), or roll your own backend.

Google Apps Script gives you a third option that's completely free, serverless, and doesn't require an account anywhere except Google: write a small function that receives your form's POST request, appends a row to a Google Sheet, and emails you a notification. It works for thousands of submissions a month before you ever hit a quota.

This guide walks through the exact setup we use on shrinkto.com/become-a-partner, end to end. Copy the code, paste, deploy, and you're done.

Step 1: Create the Google Sheet

This sheet will store every form submission as a new row.

  1. Open sheets.new in your browser That URL creates a fresh Google Sheet instantly. (You can also go to drive.google.com and create one manually.)
  2. Rename the sheet Click "Untitled spreadsheet" at the top and give it a meaningful name like ShrinkTo Partner Inquiries. This name is just for your own organisation.
  3. Add column headers in row 1 Type each column header into row 1, one per cell. For the partner form, use these headers in this order:

    Timestamp · Name · Email · Company · Website · Partnership Type · Message · Source · User Agent

    You can adjust headers later, but adding them now makes the sheet readable from day one.
  4. Bold the header row Select row 1, click the Bold button (or press Ctrl+B / Cmd+B). Optional but worth doing — the bold row visually separates headers from data.

Step 2: Open the Apps Script editor

Apps Script lives inside Google Sheets — you don't install anything separately.

  1. From your sheet, click the Extensions menu At the top of the sheet, click Extensions, then click Apps Script. A new tab opens with the script editor.
  2. Rename the project In the new tab, click "Untitled project" at the top and rename it to something like ShrinkTo Partner Form Handler.
  3. Delete the default code You'll see a default function myFunction() {} — select all the code in the editor and delete it.

Step 3: Paste the doPost handler

Copy the entire code block below into the empty Apps Script editor. Replace YOUR_EMAIL@example.com with the email where you want notifications to land.

// ============================================
//  ShrinkTo Partner Form Handler
//  Receives form submissions, appends to sheet, sends email.
// ============================================

const NOTIFICATION_EMAIL = 'YOUR_EMAIL@example.com'; // <-- change this
const SITE_NAME = 'ShrinkTo';

function doPost(e) {
  try {
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
    const data = e.parameter; // form-urlencoded fields
    const timestamp = new Date();

    // Append a row matching your column headers
    sheet.appendRow([
      timestamp,
      data.name || '',
      data.email || '',
      data.company || '',
      data.website || '',
      data.partnershipType || '',
      data.message || '',
      data.source || '',
      data.userAgent || ''
    ]);

    // Send email notification to you
    const subject = 'New ' + SITE_NAME + ' partner inquiry: ' + (data.name || 'Unknown');
    const htmlBody = '<h2>New partner inquiry</h2>' +
      '<p><strong>Name:</strong> ' + escapeHtml(data.name) + '</p>' +
      '<p><strong>Email:</strong> <a href="mailto:' + escapeHtml(data.email) + '">' + escapeHtml(data.email) + '</a></p>' +
      '<p><strong>Company:</strong> ' + escapeHtml(data.company || '-') + '</p>' +
      '<p><strong>Website:</strong> ' + escapeHtml(data.website || '-') + '</p>' +
      '<p><strong>Type:</strong> ' + escapeHtml(data.partnershipType || '-') + '</p>' +
      '<p><strong>Message:</strong></p><p>' + escapeHtml(data.message || '-').replace(/\n/g, '<br>') + '</p>' +
      '<hr><small>Source: ' + escapeHtml(data.source || '-') + '</small>';

    MailApp.sendEmail({
      to: NOTIFICATION_EMAIL,
      replyTo: data.email || NOTIFICATION_EMAIL,
      subject: subject,
      htmlBody: htmlBody
    });

    return ContentService
      .createTextOutput(JSON.stringify({ success: true }))
      .setMimeType(ContentService.MimeType.JSON);

  } catch (err) {
    Logger.log(err);
    return ContentService
      .createTextOutput(JSON.stringify({ success: false, error: err.toString() }))
      .setMimeType(ContentService.MimeType.JSON);
  }
}

function doGet(e) {
  return ContentService
    .createTextOutput(JSON.stringify({ status: SITE_NAME + ' partner endpoint active' }))
    .setMimeType(ContentService.MimeType.JSON);
}

function escapeHtml(s) {
  if (!s) return '';
  return String(s)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

Save the script: Ctrl+S / Cmd+S, or click the floppy-disk icon. The first save will prompt you to confirm the project name — just click OK.

Step 4: Deploy as a Web App

This is the step that gives you a public URL your form can POST to.

  1. Click "Deploy" → "New deployment" Top-right of the Apps Script editor, click the blue Deploy button, then New deployment.
  2. Click the gear icon next to "Select type" From the dropdown that appears, choose Web app.
  3. Configure the deployment
    • Description: "ShrinkTo partner form v1" (any label works)
    • Execute as: Me (your email) — this runs the script with your Google account permissions, which is what lets it write to your sheet and send email from your address
    • Who has access: Anyone — required so your website can call this URL without authentication
  4. Click Deploy The first time, Google will pop up an authorization window. Click Authorize access, choose your account, and click Allow. You'll see a "Google hasn't verified this app" warning — click "Advanced" then "Go to (your project name) (unsafe)". This warning appears because the app is yours and unverified by Google's review process; it's safe because you wrote the code.
  5. Copy the Web App URL After deployment, you'll see a URL that looks like https://script.google.com/macros/s/AKfy.../exec. Copy this entire URL — you'll paste it into your website next.

Step 5: Connect your website form

Open the page that has your form (in this case, become-a-partner.html). Find the line that looks like this:

const APPS_SCRIPT_URL = 'PASTE_YOUR_GOOGLE_APPS_SCRIPT_DEPLOYMENT_URL_HERE';

Replace the placeholder with the URL you copied from Apps Script:

const APPS_SCRIPT_URL = 'https://script.google.com/macros/s/AKfy.../exec';

Save the file and re-deploy your site. That's it on the website side.

Step 6: Test end-to-end

This is the most satisfying part — proving the whole thing works.

  1. Open your live partner page Visit shrinkto.com/become-a-partner in a real browser (not localhost — the form needs to call the deployed Apps Script URL).
  2. Fill out the form with test data Use your real email so you can verify the notification arrives. The other fields can be anything.
  3. Click submit You should see a "Sending…" state for 1–3 seconds, then a green success message.
  4. Check your Google Sheet Open the sheet — you should see a new row at the bottom with the test data and a current timestamp.
  5. Check your inbox You should have an email from your own Gmail address (Apps Script sends as the deploying user) with the partner inquiry details. The "Reply To" header is set to the form submitter's email so you can reply directly.

Common issues and how to fix them

"The form backend is not configured yet" message

You forgot to replace the APPS_SCRIPT_URL placeholder in your HTML. Open the page source, search for PASTE_, and replace with your actual deployment URL.

"Script function not found: doPost"

You deployed before saving, or you saved a different file. Go back to the Apps Script editor, make sure the code is in Code.gs (not a new file), save, then re-deploy as a new version (not a new deployment — re-use the same one).

Submissions go to sheet but no email arrives

Check the spam folder first. If still nothing, open Apps Script, click Executions in the left sidebar, find the latest run, and check the logs for errors. The most common cause is hitting Gmail's daily quota (100 emails for free Gmail accounts, 1500 for Workspace) — usually only an issue at scale.

"Authorization required" error in browser console

You deployed with "Who has access: Only myself" instead of "Anyone". Open your deployment, click the pencil/edit icon, change the access setting, and re-deploy as a new version.

Submissions stop working after editing the script

Editing the code requires creating a new deployment version for the changes to go live. In the Deploy menu, click Manage deployments, click the pencil icon, change "Version" to "New version", and click Deploy. The URL stays the same.

Customising for your form

The script above is generic. Two common customisations:

Different form fields

Match the order of sheet.appendRow([...]) in the script to your sheet's column headers. If you add a field called budget in your form, add a corresponding header in the sheet, then add data.budget || '' to the appendRow array in the matching position.

Send a confirmation email to the submitter too

Add a second MailApp.sendEmail call after the first one:

MailApp.sendEmail({
  to: data.email,
  subject: 'Thanks for reaching out to ' + SITE_NAME,
  htmlBody: '<p>Hi ' + escapeHtml(data.name) + ',</p>' +
            '<p>Thanks for your interest in partnering with us. ' +
            'We\'ll get back to you within 2–3 business days.</p>' +
            '<p>— ' + SITE_NAME + ' team</p>'
});

Free tier limits to know about

Google Apps Script's free quotas are generous for typical website forms:

  • Email: 100/day for free Gmail accounts, 1,500/day for Google Workspace
  • Script runtime: 6 minutes per execution (way more than a form needs)
  • Daily script triggers: 90 minutes total per day (each form submission = ~1 second)
  • Sheet writes: Effectively unlimited for normal use

For most small sites and indie projects, you'll never come close to these limits. If you do, you've grown to the point where a real backend (Supabase, Firebase, etc.) makes sense anyway.

Security considerations

The deployed Apps Script URL is public — anyone with the URL can POST to it. Three things to be aware of:

  1. Spam. Bots will eventually find the endpoint. Add a hidden honeypot field (a form input that's invisible via CSS) and reject submissions where it's filled in. Or add a Cloudflare Turnstile token.
  2. Rate limiting. Apps Script doesn't have built-in rate limits. If spam becomes an issue, add a check at the start of doPost using PropertiesService to track submissions per IP per hour.
  3. PII storage. Anything you collect goes into your Google Sheet. Don't ask for sensitive data (passwords, financial info) in a form like this — use a proper authenticated backend for that.

You just built a serverless form backend

This same pattern works for any form — newsletter signups, beta waitlists, feedback forms, RSVP pages. Reuse the script with different sheets and different field mappings.

handshake See it live: ShrinkTo's partner page →