<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: yehuda</title>
    <description>The latest articles on DEV Community by yehuda (@yehuda_4012c3f0).</description>
    <link>https://dev.to/yehuda_4012c3f0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3978165%2F661d35e9-a317-446b-a276-03a39abf03f9.png</url>
      <title>DEV Community: yehuda</title>
      <link>https://dev.to/yehuda_4012c3f0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yehuda_4012c3f0"/>
    <language>en</language>
    <item>
      <title>How to add a contact form to your static site — no backend, no monthly fee</title>
      <dc:creator>yehuda</dc:creator>
      <pubDate>Wed, 10 Jun 2026 18:14:03 +0000</pubDate>
      <link>https://dev.to/yehuda_4012c3f0/how-to-add-a-contact-form-to-your-static-site-no-backend-no-monthly-fee-8i9</link>
      <guid>https://dev.to/yehuda_4012c3f0/how-to-add-a-contact-form-to-your-static-site-no-backend-no-monthly-fee-8i9</guid>
      <description>&lt;p&gt;I got tired of paying for form services or spinning up a backend just to handle contact form submissions. So I built RG Forms — a contact form endpoint backed entirely by a Google Sheet you own. No server, no monthly fee, no third-party storing your data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Most form tools store your submissions on their servers. You pay monthly, you depend on their uptime, and your data lives in their database. RG Forms does the opposite: every submission goes straight into a Google Sheet in your own Google Drive, sent by an Apps Script that you own and control.&lt;/p&gt;

&lt;p&gt;RG Forms provisions that sheet and script for you in about 90 seconds. After that, your endpoint runs forever at no cost — completely independent of any RG Forms server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built for static sites
&lt;/h2&gt;

&lt;p&gt;If you host on GitHub Pages, Netlify, Vercel, Cloudflare Pages, or just plain HTML on a CDN, you've hit this wall: there's no backend to receive a form POST. The usual workarounds are a paid form service, a serverless function you have to write and maintain, or standing up a whole backend just for a contact form.&lt;/p&gt;

&lt;p&gt;RG Forms is built exactly for this. Your endpoint is a plain HTTPS URL you POST to straight from client-side JavaScript — no build step, no serverless function, no server of any kind. Drop the fetch call into your page and you're done. It pairs naturally with any static-site generator (Hugo, Jekyll, Astro, Eleventy, Next export) and any no-code builder that lets you add a snippet of JS. Your static site stays static; the form just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it's built
&lt;/h2&gt;

&lt;p&gt;RG Forms is a fully static web app. There's no RG Forms server, no database, no backend. Every API call during setup goes directly from your browser to Google using your own OAuth token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Setup (one time, in your browser):

  Your Browser
      ├─── Google OAuth      ──▶  Short-lived token (memory only)
      ├─── Google Drive API  ──▶  Creates Sheet + Drive folder
      └─── Apps Script API   ──▶  Creates &amp;amp; deploys form handler

Live endpoint (after provisioning):

  Your Website / App
      └─── POST to script URL
                └─── Apps Script (in your Google account)
                          ├─── Appends row to Google Sheet
                          ├─── Sends email notification
                          └─── Returns { result: "success" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That OAuth token lives only in browser memory — never sent to an RG Forms server, never written to disk, gone when you close the tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gets created in your Drive
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Drive folder&lt;/strong&gt; named after your form&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Google Sheet&lt;/strong&gt; with your column headers, plus a hidden &lt;code&gt;_manifest&lt;/code&gt; tab the script reads on every request&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;Apps Script web app&lt;/strong&gt; deployed as a permanent HTTPS endpoint that appends rows and sends email&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Posting to your endpoint
&lt;/h2&gt;

&lt;p&gt;One detail worth calling out: post with &lt;code&gt;Content-Type: text/plain&lt;/code&gt;. That avoids a CORS preflight Apps Script can't respond to, and the script still parses the body as JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// POST directly from your static site — no server proxy needed.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FORM_SCRIPT_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// { name, email, phone, message, ... }&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// { result: 'success' } or { result: 'error', error: '...' }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The row appears in your Sheet and you get an email. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's included
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Email notifications&lt;/strong&gt; on every submission, with a configurable subject&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CC / BCC&lt;/strong&gt; without exposing addresses in your frontend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reply-to mapping&lt;/strong&gt; — set a form field (like email) as the reply-to address&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honeypot spam protection&lt;/strong&gt; — a hidden field bots fill out and the script silently discards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple forms&lt;/strong&gt; on one sheet as separate tabs sharing a single endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit fields any time&lt;/strong&gt; — add, remove, or relabel without reprovisioning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RGFORMS.md export&lt;/strong&gt; — an AI skill file you can hand to Claude Code, Cursor, Copilot, or Windsurf so it wires up the form for you automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations to know about
&lt;/h2&gt;

&lt;p&gt;Apps Script on a free Google account caps email notifications at roughly 100/day. There's a one-time manual script authorization after provisioning (a Google requirement for scripts deployed via API), and the Apps Script API has to be enabled on your account first — RG Forms detects this and links you straight to the toggle. Spam protection is honeypot-only, so high-traffic forms should add reCAPTCHA on the frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Formspree, Typeform, and similar tools are great but either cost money at scale or lock your data in their system. Since Apps Script runs inside Google's infrastructure under your own account, it's free within Google's generous quotas and your data never leaves your control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rgforms.com/" rel="noopener noreferrer"&gt;rgforms.com&lt;/a&gt; — takes about 90 seconds to set up. There's a full &lt;a href="https://rgforms.com/how-it-works/" rel="noopener noreferrer"&gt;how-it-works walkthrough&lt;/a&gt; too.&lt;/p&gt;

&lt;p&gt;Would love feedback, especially if you've hit the same frustration with form backends on static sites.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
      <category>googlesheets</category>
    </item>
  </channel>
</rss>
