โฆ Sample prompt
Paste this into your coding agent (Claude Code, Cursor, etc.) to have it build the whole flow against your existing site. It tells the agent to keep your current UI untouched and only add the form's functionality.
i How it works
The "Get in touch" form does four things when a visitor clicks Send message:
- Validates name / email / message in the browser (blocks invalid or duplicate submits).
- Saves the enquiry as a new row in a Google Sheet.
- Emails you a notification (
New Contact Form Message from <Name>). - Emails the visitor a friendly acknowledgement.
Steps 2โ4 run inside a Google Apps Script web app. The website only knows that web app's URL. Your Google account, the Sheet, and email sending never touch the frontend or the repo.
โ What you need to provide
| Value | Where it goes | Example |
|---|---|---|
| Google Sheet ID | Apps Script → Script Property SHEET_ID | 1AbCdEf... (from the Sheet URL) |
| Your notification email | Apps Script → Script Property NOTIFY_EMAIL | shajikombanfrancis@hotmail.com |
| Web app URL | website .env → VITE_CONTACT_ENDPOINT | https://script.google.com/macros/s/AKfy.../exec |
Everything else has sensible defaults.
1 Part 1 โ Google Sheet
- Create a Google Sheet, e.g. "Portfolio Enquiries".
- From the URL, copy the ID (the long string between
/d/and/edit):
https://docs.google.com/spreadsheets/d/<SHEET_ID>/edit - You do not need to add headers โ the script creates a tab named
Enquirieswith headersSubmitted At | Name | Email | Message | Pageon the first submission.
2 Part 2 โ Apps Script web app
- Go to script.google.com → New project.
- Delete the sample
Code.gscontents and paste the contents ofgoogle-apps-script/Code.gsfrom the repo. - (Optional but recommended) Click the gear → Project Settings → tick "Show
appsscript.jsonmanifest file", then replace it withgoogle-apps-script/appsscript.json. SettimeZoneto your preference (defaultAsia/Bahrain). - Project Settings → Script Properties → Add script property โ add:
| Property | Value |
|---|---|
SHEET_ID | your Sheet ID from Part 1 |
NOTIFY_EMAIL | the address that should receive enquiries |
Optional properties
| Property | Default | Purpose |
|---|---|---|
SHEET_NAME | Enquiries | worksheet / tab name |
OWNER_NAME | Shaji | signature inside the acknowledgement email |
ALLOWED_ORIGIN | (unset) | e.g. https://yourdomain.com โ rejects posts whose origin field doesn't start with this |
MAX_PER_EMAIL_PER_DAY | 5 | per-address daily cap |
ACK_FROM_ADDRESS | (unset) | send the visitor acknowledgement from this address, e.g. no-reply@shajikfrancis.com โ requires the "Send mail as" setup in Part 2b |
ACK_FROM_NAME | OWNER_NAME | display name on the acknowledgement, e.g. Shaji Komban Francis |
ACK_REPLY_TO | (unset) | Reply-To on the acknowledgement, e.g. info@shajikfrancis.com |
2b Send the acknowledgement from a custom address (optional)
Google Apps Script can only send "from" an address that the script-running Google account has registered as a verified sender. It cannot spoof an arbitrary address.
- Sign in to the Gmail account that runs this script (the account you deploy with โ
executeAs: USER_DEPLOYING). - Gmail → Settings โ → See all settings → Accounts and Import → "Send mail as" → Add another email address.
- Enter Shaji Komban Francis and no-reply@shajikfrancis.com, leave "Treat as an alias" ticked, Next.
- Google sends a confirmation code to
no-reply@shajikfrancis.com. You must be able to receive mail there โ set up a catch-all or a real mailbox/alias on theshajikfrancis.comdomain, get the code, and confirm.
(If the domain is Google Workspace, an admin can instead add it as a send-as alias without a code.) - Once it shows as verified, add the Script Properties:
Property Value ACK_FROM_ADDRESSno-reply@shajikfrancis.comACK_FROM_NAMEShaji Komban FrancisACK_REPLY_TOinfo@shajikfrancis.com(optional โ where replies should land) - Redeploy a new version (the manifest gained the
gmail.settings.basicscope, so Google will ask you to re-authorize once).
shajikfrancis.com that includes include:_spf.google.com, and โ recommended โ set up DKIM. Without SPF alignment some providers may put the acknowledgement in spam. If ACK_FROM_ADDRESS is not a verified sender, the script logs a warning and sends from the account's default address instead (the enquiry and your notification are unaffected).3 Deploy & backend check
- Deploy → New deployment → type Web app:
- Description:
contact form v1 - Execute as: Me
- Who has access: Anyone
- Click Deploy.
- Description:
- Google will prompt for authorization (Sheets + send email). Approve it. If you see "Google hasn't verified this app", click Advanced → Go to <project> (unsafe) โ this is your own script.
- Copy the Web app URL (ends in
/exec).
Quick backend check
Open the /exec URL in a browser. You should see:
{"ok":true,"service":"contact","ts":"2026-..."}
4 Part 3 โ Point the website at it
- In the project root, copy the example env file:
cp .env.example .env - Edit
.envand set the URL from Part 2:VITE_CONTACT_ENDPOINT=https://script.google.com/macros/s/AKfy.../exec - Rebuild / redeploy the site:
npm run build
VITE_* values at build time. After changing .env you must rebuild (and redeploy). On Vercel/Netlify, set VITE_CONTACT_ENDPOINT as an environment variable in the project settings and trigger a redeploy..env is git-ignored. Only .env.example is committed.
โป Updating the script later
Edit the code in the Apps Script editor, then Deploy → Manage deployments → edit (pencil) → Version: New version → Deploy. The /exec URL stays the same, so no website change is needed. Creating a new deployment instead would give a new URL and require updating .env.
โ Testing
1. Validation (no backend needed)
Run npm run dev and open the Contact section:
| Action | Expected |
|---|---|
| Click Send message with all fields empty | Three inline errors; nothing sent |
Enter name + not-an-email + message, submit | "Please enter a valid email address." |
| Fix a field | Its error clears as you type |
Submit valid data with no VITE_CONTACT_ENDPOINT set | Friendly error + mailto: link; can retry |
2. End-to-end (after Parts 1โ3)
- Fill the form with a real email you can check and submit.
- Button shows "Sendingโฆ" and is disabled (no double submit).
- On success: green "Thank you! Your message has been sent successfullyโฆ" and the fields clear. The page does not reload.
- Within a few seconds:
- a new row appears in the Google Sheet,
- your
NOTIFY_EMAILinbox gets "New Contact Form Message from <Name>" (reply-to is set to the visitor), - the visitor address gets the acknowledgement email โ check its From:
no-reply@shajikfrancis.comifACK_FROM_ADDRESSis set and verified, otherwise the script account's default address (see Executions for a "not a verified Send mail as alias" warning if you expected the former).
- Submit again immediately → "You've just sent a message. Please wait a minuteโฆ" (client + server rate limit).
3. Security checks
| Test | Expected |
|---|---|
Put <script>alert(1)</script> in the message | Stored/emailed as plain text, tags stripped; no HTML runs |
Start the message with =SUM(1+1) | Sheet cell stores it as literal text (leading '), not a formula |
| Inspect the built JS / network tab | Only the /exec URL is visible โ no Sheet ID, no email credentials |
Submit from an unlisted origin (if ALLOWED_ORIGIN set) | {"ok":false,"error":"forbidden"} → generic error on site |
Apps Script logs
Apps Script editor → Executions (left sidebar) shows every doPost run, its status, and console.error output for failures.
! Limits & notes
- Email quota: consumer Gmail allows ~100 recipients/day via
MailApp(Workspace: ~1,500). Each submission sends 2 emails. CacheServicebacks the rate limiter; caps reset if the cache is evicted early. For hard guarantees, switch the counter toPropertiesService.- The acknowledgement's From address is the script account's default unless
ACK_FROM_ADDRESSis set to a verified "Send mail as" sender (Part 2b) โ then it sends as e.g.no-reply@shajikfrancis.comwith display nameACK_FROM_NAME. - The owner notification always sends from the script account with
Reply-Toset to the visitor, so you can reply straight from your inbox. - HTTPS is automatic:
script.google.comand static hosts (Vercel / Netlify / Pages) are HTTPS-only.