RG App Store logoRG App Store
Brainstorm

Spin up in your Google account

These aren't forms — they're tools you own. Each provisions into your Google account in one click and runs as you: your Sheet is the database, your Gmail sends the mail, your Calendar holds the events. No servers, no subscriptions, no vendor holding your data. Below: the vetted best, the templates behind them, the full 160-app industry directory, and how the hard parts actually work.

How to read the tags

Page a working hosted tool the instant you provision.
API a JSON endpoint for your own site.
Cron set-and-forget background job.
Gmail Workspace+ Workspace only— Workspace raises the email cap (100→1,500/day), adds BAA + domain send-as, and unlocks the Admin SDK.

Spin up today — the vetted best

Top 5

  1. QuoteCraft — A public quote/estimate builder that turns a customer's line-item selections into a branded PDF quote emailed to them and logged in your sheet — a one-page sales tool for any service business.
  2. RSVP Room — A gorgeous single-event RSVP + ticket page that adds guests to your calendar, emails them a personalized PDF ticket with a QR, and gives you a live headcount — perfect for classes, workshops, meetups.
  3. TripQuote Maps — An instant service-area quote page: a customer enters their address, it geocodes the distance from your base and returns a live mileage-based price plus a PDF estimate — for movers, mobile detailers, delivery, any 'we come to you' business.
  4. DailyBrief — Every morning it emails you a tight briefing — today's calendar with driving times and attached docs, what changed in a tracked sheet, and AI-summarized headlines from your source URLs — plus a Friday recap of where your week went.
  5. Weekly Roundup Publisher — It quietly collects everything that landed in a folder or label this week and emails you (or your team) one beautiful, AI-summarized roundup every Friday.
Page

Page apps (17)

QuoteCraft

17/20 Gmail

A public quote/estimate builder that turns a customer's line-item selections into a branded PDF quote emailed to them and logged in your sheet — a one-page sales tool for any service business.

Page

You spin up: A hosted page where a prospect picks services/quantities from your price list; on submit it generates a branded PDF quote (Docs template → PDF via Drive), emails it to them with a copy to you, and appends the lead to your Sheet as a mini-CRM.

Why it's great: Most quote tools are $30/mo SaaS. This gives a plumber, designer, or consultant a self-serve estimate page that produces a real professional PDF instantly and captures every lead — the pricing lives in a sheet they can edit in seconds, no rebuild.

Built on / setup

DocumentApp/DriveApp PDF-generation sample + SpreadsheetApp price list + GmailApp; extends the 'generate & send PDFs from Sheets' and certificate samples.Light setup: fill your services + prices in the sheet and tweak the PDF header/logo once; then the page is fully self-serve.

RSVP Room

17/20 Gmail

A gorgeous single-event RSVP + ticket page that adds guests to your calendar, emails them a personalized PDF ticket with a QR, and gives you a live headcount — perfect for classes, workshops, meetups.

Page

You spin up: A branded event page showing your event details and remaining capacity; guests RSVP, get added as attendees to a real Calendar event, receive a personalized PDF ticket (with a QR code) by email, and you get a live-updating attendee sheet with automatic waitlisting when full.

Why it's great: Eventbrite charges per-ticket fees and owns your attendee list; a Google Form has no capacity cap, no ticket, no calendar sync. This gives a real ticketed page with enforced capacity, auto-waitlist, and a scannable PDF ticket — free and fully owned.

Built on / setup

CalendarApp + DocumentApp→PDF ticket + GmailApp + SpreadsheetApp capacity logic; extends the conference sign-up and certificate/PDF samples.Light setup: set event name/date/capacity in the sheet; page and tickets are live immediately, no keys.

TripQuote Maps

17/20 Gmail

An instant service-area quote page: a customer enters their address, it geocodes the distance from your base and returns a live mileage-based price plus a PDF estimate — for movers, mobile detailers, delivery, any 'we come to you' business.

Page

You spin up: A hosted page where a customer types their address; Maps geocodes and computes driving distance from your base, applies your per-mile + base-fee rules, shows an instant price, and emails a branded PDF estimate while logging the request with the mapped distance to your sheet.

Why it's great: No off-the-shelf tool ties YOUR distance-based pricing to a self-serve customer page with an instant branded estimate. It quietly qualifies leads by location and hands you a distance-stamped record — a genuine wedge Google doesn't ship and SaaS charges monthly for.

Built on / setup

Maps (geocode/directions) + DocumentApp→PDF + GmailApp + SpreadsheetApp pricing rules; extends the Maps driving-distance and PDF samples.Light setup: set your base address and per-mile/base-fee rates in the sheet; the geocoding page works with no key.

Rolodex

16/20 Gmail

A lightweight personal CRM that watches your relationships and pings you when you haven't talked to someone important in too long.

Page

You spin up: A hosted contacts page (doGet) seeded from your Google Contacts (People API) where each person has a 'keep in touch every N days' cadence and a running note log; a weekly cron scans for anyone overdue and emails you a 'Reach out this week: Mom (52 days), Alex (31 days)' list with a one-line prompt of your last note about them.

Why it's great: Google Contacts is a static address book with zero relationship intelligence; paid personal CRMs (Clay, Dex) are subscriptions. The wedge is owned last-contact + cadence data driving a proactive weekly nudge — it makes you a better friend, not just a tidier database.

Built on / setup

People/Contacts (import) + SpreadsheetApp (cadence + notes) + doGet page + weekly time-driven trigger + GmailApp. Directly extends the People/Contacts listing sample plus mail-merge send.Near-zero: on first open it offers to pull in your existing contacts; set a cadence per person or accept the default and the weekly digest self-arms.

Rota

16/20 Gmail

Publish next week's shift schedule to a page your team can actually read, and a nightly cron emails each person their shifts for tomorrow so nobody no-shows.

Page

You spin up: A branded weekly shift-grid page (roles × days) that reads from a Schedule Sheet you fill in, plus a nightly time-driven trigger that emails every scheduled person a 'you're on tomorrow: 9am-5pm, front desk' reminder.

Why it's great: Scheduling apps (Deputy, 7shifts) paywall the reminder feature that actually prevents missed shifts. Here you edit the schedule in a familiar Sheet, the page renders it beautifully for the team, and the automatic day-before nudge — the single highest-value part — is free and set-and-forget.

Built on / setup

SpreadsheetApp + GmailApp + time-driven triggers, extending the timesheet/sign-up ops samples and calendar reminder patterns.Type shifts into the Schedule tab (name, day, start, end, role); the page and nightly reminders work immediately once names have emails in a roster tab.

Stash

15/20 Gmail

A private read-it-later inbox that emails you back the articles you saved and forgot, so your saved-for-later stops being a graveyard.

Page

You spin up: A hosted, password-protected reading list page (doGet) where you paste any URL; it fetches the page title/description via UrlFetchApp and stores it as a card with a 'read/unread/archive' toggle, plus a nightly cron that emails you 3 unread items older than a week ('You saved these — still want them?').

Why it's great: Pocket/Instapaper charge and lock your list in their cloud; here the data lives in YOUR sheet and the killer wedge is the resurfacing digest — no free tool actively drags forgotten saves back in front of you. It turns a static bookmark pile into a self-clearing queue.

Built on / setup

UrlFetchApp (fetch page <title>/og:description) + SpreadsheetApp (the list) + a time-driven trigger + GmailApp for the resurfacing digest. Extends the CRON+HTTP tracker samples (YouTube/stock) and the mail-merge send pattern.Zero-setup: works the instant it provisions. Optionally paste a bookmarklet link it generates so you can save from any browser in one click.

Streak

15/20 Gmail

A one-tap daily habit page that fires you a morning reminder and, the moment you're about to break a streak, sends a 'don't lose your 41 days' warning email.

Page

You spin up: A hosted habit-grid page (doGet) with a big tappable check button per habit that writes today's date to your sheet and shows your current + longest streak as a GitHub-style contribution heatmap; plus two crons — a morning 'time to check in' nudge and an evening 'you haven't logged X, your 41-day streak dies at midnight' rescue email.

Why it's great: Free habit apps either have no teeth (silent) or are paywalled subscriptions harvesting your data. The wedge is the loss-aversion evening rescue email keyed to your ACTUAL streak length — the single most effective anti-lapse mechanic — running forever for free on data you own.

Built on / setup

SpreadsheetApp (date log per habit) + doGet/doPost page + two time-driven triggers + GmailApp. Extends the log-time-and-activities-to-Sheets sample and the mail-merge send pattern.Zero-setup: check a box to add your first habit; the reminder crons are pre-armed on provision.

PTO Board

15/20 Gmail

A one-page team vacation board where anyone requests time off, the manager approves with one click, and approved leave lands on a shared Google Calendar automatically.

Page

You spin up: A hosted, branded page at your Apps Script URL showing a live team availability grid (who's out this week/month), a request form, and a manager approve/deny queue — backed by a Sheet of every request and a shared 'Team PTO' Google Calendar.

Why it's great: Google gives you a calendar but no request/approval workflow and no balance tracking. This adds the owned data (accrued vs. used days per person), the one-click approval that emails the requester, and auto-creates the all-day calendar event only on approval — so the shared calendar is always the source of truth and never has pending noise.

Built on / setup

CalendarApp + SpreadsheetApp + GmailApp, extending the official Vacation Calendar automation and conference sign-up samples.Zero-setup: works the instant it's provisioned. Optionally paste each teammate's starting PTO balance into the Sheet; if you skip it, everyone starts at your default (e.g. 15 days).

Expensr

15/20 Gmail

Employees submit an expense with a photo of the receipt; the receipt is filed in Drive, the claim hits your approval queue, and approved totals roll up per person and per month.

Page

You spin up: A hosted expense-claim page with receipt image upload, an approve/reject queue for you, and a Sheet of every claim with a Drive link to each receipt — plus an approval/denial email to the submitter with the reason.

Why it's great: Expensify-style tools charge per active user for the same loop. This keeps every receipt image in your own Drive folder (auditor-friendly, permanent, yours), logs the structured claim next to it, and gives you a real approval workflow with reasons — the wedge is owned receipt storage plus a running per-person reimbursement total Google can't give you.

Built on / setup

DriveApp + SpreadsheetApp + GmailApp, extending the PDF-generation and form-notification samples (receipts stored to a Drive folder, claims logged to Sheets).Zero-setup: submit and approve immediately. Optionally set expense categories and a per-claim auto-approve threshold in a settings tab.

IntakePortal

14/20 Gmail

A polished client-intake portal that collects details AND a signed engagement PDF, then auto-drops a shared Drive folder and welcome email — the onboarding page freelancers wish they had.

Page

You spin up: A hosted multi-step intake page: the client fills structured fields, types their name to 'sign', and on submit your script generates a signed intake/agreement PDF, creates a client-named Drive folder, shares it with them, and sends a branded welcome email with next steps.

Why it's great: Typeform + PandaDoc + a Drive share is three paid tools and manual glue. This is one page that produces a signed document, a live shared workspace, and a warm welcome in one submit — every artifact lands in the owner's own Drive automatically.

Built on / setup

DocumentApp→PDF + DriveApp (folder create/share) + GmailApp + SpreadsheetApp; extends the certificate/PDF and 'share resources with new hires' samples.Light setup: edit the intake fields and agreement wording once; folder-sharing and PDF work with no keys.

Punch

14/20 Gmail

A dead-simple team timesheet page — teammates clock in/out or log hours by project, and a Friday cron emails you (and each person) a clean weekly hours summary.

Page

You spin up: A branded clock-in/out page (identity = magic-link email, so no passwords) that writes every punch to a Sheet, plus a time-driven trigger that every Friday 5pm emails a per-person and per-project hours rollup for the week.

Why it's great: Real time-clock SaaS (Homebase, When I Work) starts at $20-40/mo per location for exactly this. Here it's free, the raw hours live in your own Sheet (pivot/export however you want for payroll), and the weekly digest means nobody has to remember to 'run the report' — it just shows up.

Built on / setup

SpreadsheetApp + GmailApp + time-driven triggers, extending the official Timesheet and 'log time+activities to Sheets' samples.Zero-setup: clock-in works immediately. Optionally type your project names into a Projects tab so the dropdown is pre-filled; otherwise people free-type the project.

GearDesk

14/20 Gmail

An internal equipment & asset request page: staff request a laptop, monitor, or desk chair; you approve; and every asset's owner, status, and history lives in one searchable Sheet.

Page

You spin up: A hosted request page with a live inventory view (what's available vs. assigned to whom), a request form, and an approve/assign queue — backed by an Assets Sheet and a Requests Sheet, with an email to the requester on every status change.

Why it's great: Most small teams track gear in a spreadsheet nobody updates and a Slack thread nobody can find. This makes the request the thing that updates inventory — approving a laptop request flips that asset to 'assigned' and stamps the owner automatically — so your asset register stays accurate as a side effect of normal use, with a full audit trail.

Built on / setup

SpreadsheetApp + GmailApp, extending the official Employee Equipment Requests sample.Paste your current gear into the Assets tab once (one row per item) and you're live; requests and approvals work with zero further setup.

FeedbackPulse

13/20 Gmail

A public feedback/review page where every submission is instantly scored for sentiment + auto-tagged by theme, so a live dashboard shows you 'what people love vs. what's breaking' without you reading a single row.

Page

You spin up: A hosted doGet page (branded form + dashboard) that on each submit runs Natural Language sentiment + Claude theme-extraction, writes score/theme/summary to your sheet, and renders a live rollup of top complaints and praise.

Why it's great: Google Forms just dumps raw text you'll never read; this turns free-text feedback into a triaged, quantified 'voice of customer' board the instant it exists — a real analytics wedge, all in your Drive.

Built on / setup

HtmlService page + FormApp/SpreadsheetApp + Natural Language API (sentiment) + UrlFetchApp→Claude (theme tagging) — extends the official sentiment-analysis + feedback samples into a self-serve hosted tool.Zero-setup after provision — you get a live URL to share immediately; the dashboard populates itself as responses arrive.

PolyglotDesk

13/20 Gmail

A public multilingual contact/support page: a visitor writes in any language, Claude detects it, translates to yours, drafts a suggested reply, and translates your answer back — turning your inbox into a language-agnostic help desk.

Page

You spin up: A hosted doGet inbox+form where each incoming message is auto-translated and logged to your sheet with a Claude-drafted reply in the sender's language, ready for you to approve and email back.

Why it's great: It collapses 'detect language → translate → understand → reply → translate back' into one page for free; small businesses instantly support customers in any language without hiring or paying Zendesk.

Built on / setup

HtmlService page + LanguageApp/UrlFetchApp→Claude (detect+translate+draft) + SpreadsheetApp + MailApp — extends the official translate add-on and respond-to-feedback samples into a bidirectional support surface.Provision gives you a shareable URL immediately; paste a key and it works — no site required.

AskDoc AI Concierge

11/20 Gmail

A branded 'ask me anything' page grounded ONLY in your own docs/sheet — a private AI helpdesk for your product, policies, or FAQ that answers as you, not the whole internet.

Page

You spin up: A hosted chat page where visitors ask questions and get answers generated by Claude via UrlFetchApp, grounded strictly in the FAQ/policy content you keep in a Google Doc or Sheet; every Q&A is logged so you can spot gaps, and unknowns can trigger an email to you.

Why it's great: A generic chatbot hallucinates and knows nothing about your business; hosted AI-support tools are pricey and route your content through their cloud. Here the knowledge lives in your own Doc, the model call runs from your account, and you own every transcript — a real support page you spin up in minutes.

Built on / setup

UrlFetchApp (Claude API) + DocumentApp/SpreadsheetApp as the knowledge source + GmailApp escalation; extends the Gemini/AI-agent and 'aggregate multiple Docs' samples.Paste-a-key: add your Claude API key and point it at your FAQ doc; the concierge page is then live and self-serve.

Cinema

11/20 Gmail

One tidy watchlist/readlist page that, when you're paralyzed by choice, emails you a single 'watch this tonight' pick from your own backlog.

Page

You spin up: A hosted queue page (doGet) where you add movies/shows/books with a source URL, star-rating after finishing, and a 'want it' vs 'done' state; UrlFetchApp pulls the poster/title from the link, and a weekly cron emails you one weighted-random unwatched pick ('Tonight: Perfect Days — you added it 3 months ago') to kill decision fatigue.

Why it's great: Letterboxd/Goodreads are social networks that own your list and never actively push you to finish your backlog. The wedge is owned data + a proactive 'pick for me' digest that solves the real recurring annoyance ('I have 80 saved things and can't decide'), which no free list app does.

Built on / setup

UrlFetchApp (fetch title/poster/og-image) + SpreadsheetApp (the queue) + doGet page + weekly time-driven trigger + GmailApp. Extends the CRON+HTTP tracker samples and mail-merge send.Zero-setup: add items by pasting a link (poster auto-fetches); the weekly 'pick for me' email self-arms once you have unwatched items.

PayLink Pages

9/20 Gmail

A branded 'pay me' page — pick a product or enter an amount, checkout via your own Stripe, and get an auto-emailed PDF receipt, all hosted from your Google account.

Page

You spin up: A live checkout page: customer selects an item (or types a custom amount), UrlFetchApp creates a real Stripe Checkout session under your Stripe key, and on success your script emails a branded PDF receipt and logs the sale to your sheet.

Why it's great: Stripe Payment Links can't produce your own branded PDF receipts, log to your own ledger sheet, or run custom post-payment logic. This is a real self-hosted storefront-lite with a paper trail you own — and no monthly platform fee on top of Stripe.

Built on / setup

UrlFetchApp (Stripe API) + DocumentApp→PDF receipt + GmailApp + SpreadsheetApp ledger; extends the HTTP/UrlFetch and PDF samples.Paste-a-key: drop in your Stripe secret key and list products in the sheet; everything else works out of the box.

Cron

Cron apps (10)

DailyBrief

16/20 Gmail

Every morning it emails you a tight briefing — today's calendar with driving times and attached docs, what changed in a tracked sheet, and AI-summarized headlines from your source URLs — plus a Friday recap of where your week went.

Cron

You spin up: A daily trigger that pulls your Calendar events (with Maps driving time and linked prep docs), diffs a watched sheet, fetches your source URLs, has Claude write a tight executive brief, and MailApps it to your inbox before you wake up — plus a weekly time-breakdown recap of hours per category.

Why it's great: No paid 'AI daily digest' app can see your private calendar AND your private sheet AND your chosen sources at once, tell you 'leave by 8:40 for traffic,' and quantify your week. This stitches your real day into one narrated brief, set-and-forget forever.

Built on / setup

CalendarApp + Maps + DriveApp/DocumentApp + SpreadsheetApp + UrlFetchApp (fetch sources + Claude) + MailApp + time triggers — extends the meeting-agenda-from-Calendar, log-time-to-Sheets, and multi-source aggregation samples with generative summarization.Paste a key, list your source URLs and pick a send time — one config screen, then it runs itself daily.

Weekly Roundup Publisher

16/20 Gmail

It quietly collects everything that landed in a folder or label this week and emails you (or your team) one beautiful, AI-summarized roundup every Friday.

Cron

You spin up: A weekly trigger that gathers new Drive files / labeled Gmail threads / new sheet rows from the past 7 days, uses AI to summarize each into a sentence, and sends a single formatted 'here's your week' digest email to your chosen recipients.

Why it's great: Nobody wants to write the weekly update. This assembles it from what actually happened — new files, resolved threads, fresh data — and AI-summarizes it into something you'd actually send. It's the invisible chief-of-staff turning scattered activity into one shareable recap, which no single Google product does.

Built on / setup

DriveApp/GmailApp collection, UrlFetchApp to Gemini/Claude for summaries, SpreadsheetApp state, GmailApp send, time-driven trigger; extends the aggregate-Docs, summarize-Sheets, and Gemini-agent samples.Paste an AI API key and pick a folder/label + recipients; then it self-runs every week.

Journal

16/20 Gmail

Every evening it emails you one thoughtful journaling prompt; you just hit reply, and your answer is saved forever as a searchable private journal.

Cron

You spin up: A nightly cron that emails you a rotating reflection prompt ('What drained you today?'); a second cron reads your replies from a Gmail label, files each as a dated journal entry in your sheet, and runs sentiment analysis to plot your mood over time — plus a Sunday 'a year ago you wrote…' throwback email once you have history.

Why it's great: Journaling apps (Day One) charge and require you to open the app — the friction that kills the habit. The wedge is zero-friction capture (reply to an email you're already reading) plus owned mood-sentiment analytics and the 'a year ago today' resurfacing, which no email can do without your historical data.

Built on / setup

GmailApp (send prompt + read replies) + SpreadsheetApp (entries) + Natural Language API (sentiment/mood trend) + time-driven triggers. Directly extends the respond-to-feedback + sentiment-analysis samples and mail-merge send.Zero-setup: prompts start arriving the night it provisions; you never open a UI unless you want to browse the archive page.

Deadline Chaser

15/20 Gmail

You list who owes you what and when; it politely nags them by email on a schedule until they deliver — hands-off follow-up that never forgets.

Cron

You spin up: A Google Sheet of outstanding items (contact, what's owed, due date, cadence) plus a daily trigger that sends escalating personalized reminder emails to the right person and logs each send back to the sheet, auto-stopping when you mark a row 'done'.

Why it's great: It automates the thing everyone hates: the third follow-up. Escalating tone (gentle → firm), auto-stop on completion, and a full send log mean invoices, signed docs, and RSVPs collect themselves. Beats manual snooze-and-forget in Gmail because it's driven by structured due dates and cadence, not your memory.

Built on / setup

SpreadsheetApp as the DB, GmailApp merge-style personalized send, time-driven trigger, PropertiesService; directly extends the Gmail+Sheets mail-merge sample.Paste-and-go: fill in the pre-built sheet columns (contact, item, due date), and the cron does the rest — no code.

Ledger

15/20 Gmail

Forward any receipt email to your own address and it lands as a categorized expense in a running spreadsheet, with a monthly spending recap in your inbox.

Cron

You spin up: A cron that scans a dedicated Gmail label (e.g. 'expenses') every few hours, parses amount/merchant/date from forwarded receipt emails, appends each to your expense sheet, auto-categorizes via a keyword map (and optionally Gemini for messy ones), archives the email, and on the 1st emails you a month recap ('$1,842 across 6 categories, dining up 22%').

Why it's great: Expense apps (Expensify) want a login and a monthly fee and hold your data hostage; here the capture surface is something you already do — forwarding an email — and the whole ledger is a plain sheet you can pivot yourself. The AI categorization + monthly trend recap is the delight that free tools skip.

Built on / setup

GmailApp (read a label) + SpreadsheetApp (the ledger) + UrlFetchApp→Gemini for fuzzy categorization + two time-driven triggers. Extends form/feedback-processing + sentiment/AI samples and CSV-to-Sheets aggregation.Paste a key: works immediately for keyword categories; drop in a Gemini/Claude API key (one field) to unlock AI categorization of messy receipts. Then just forward emails.

InboxTriage

14/20 Gmail

A cron that reads your unread Gmail every 30 minutes, has Claude classify + one-line-summarize each thread, and files them into Urgent / Reply-needed / FYI / Ignore labels so your inbox is pre-sorted before you open it.

Cron

You spin up: A time-driven Apps Script in YOUR account that scans unread threads, sends each subject+snippet to Claude via UrlFetch, applies Gmail labels, and emails you a 5-line 'here's what actually needs you today' digest.

Why it's great: Gmail's native categories are dumb keyword tabs; this reasons about intent ('this is a vendor chasing an invoice = Urgent') on YOUR real threads and hands you a triaged inbox every morning with zero clicks.

Built on / setup

GmailApp (read/label) + UrlFetchApp→Claude + time-driven trigger + MailApp digest — extends the official Gmail form-notification / respond-to-feedback samples into AI triage.Paste one Claude API key at provision, done — no site, no config; it runs forever on its own trigger.

ReplyGhostwriter

14/20 Gmail

Forward (or auto-catch) any email and get a Claude-drafted reply in your own voice waiting as a Gmail draft — trained on a sheet of your past replies so it sounds like you, not a robot.

Cron

You spin up: A trigger that watches a label (e.g. 'draft-me'), pulls the thread, feeds it plus your voice-sample sheet to Claude, and drops a ready-to-edit reply into Gmail Drafts — you just review and hit send.

Why it's great: Gmail Smart Reply gives you three canned stubs; this writes a full, context-aware, on-brand reply grounded in how YOU actually write, and never sends without you — the safe kind of AI email.

Built on / setup

GmailApp (getThreads/createDraft) + SpreadsheetApp (voice samples) + UrlFetchApp→Claude — extends the official respond-to-feedback auto-acknowledge sample with per-thread generative drafting.Paste an API key and drop a few sample emails in the seeded sheet; label a thread and a draft appears — near-zero setup.

Sheet Sentinel

14/20 Gmail

Point it at any spreadsheet and it watches for the conditions you care about — a total crossing a line, a stale cell, a new row — then emails you the moment it happens.

Cron

You spin up: A rules sheet (watched range, condition, message) plus a trigger (runs hourly/daily) that evaluates each rule against your target Sheets, emails on any trip, and keeps a fired-alerts log so you're never double-pinged for the same event.

Why it's great: Sheets has conditional formatting that colors a cell — but it can't email you when you're not looking. This turns any sheet (cash runway, inventory, a signup count, a KPI tab) into a living alarm. The wedge is proactive push on your own data with dedupe, which Google ships nowhere.

Built on / setup

SpreadsheetApp cross-file reads, rule evaluation, GmailApp alerts, PropertiesService dedupe state, time-driven trigger; extends the summarize-multiple-Sheets and stock-alert samples.Connect a sheet and write one plain-language rule row; it starts watching immediately.

Renewal & Expiry Guardian

14/20 Gmail

Track every domain, subscription, warranty, and contract renewal in one sheet and get warned by email well before each one silently auto-charges or lapses.

Cron

You spin up: A sheet of dated obligations (item, renewal date, cost, notice-window) plus a daily trigger that emails tiered warnings (30/7/1 days out), rolls recurring items forward automatically, and sends a monthly 'upcoming spend' forecast.

Why it's great: It kills the two worst money leaks: forgetting to cancel before an auto-renew, and letting a domain/warranty lapse. Calendar reminders don't know your notice window or cost, and don't forecast spend. This owns the data, does the date math, and turns 'oops, they charged me again' into a solved problem forever.

Built on / setup

SpreadsheetApp as DB, date math, GmailApp tiered reminders, recurrence roll-forward via PropertiesService, time-driven trigger; extends the mail-merge and vacation-calendar-automation samples.Fill in the pre-built sheet (item + renewal date + cost); the cron handles all the warning logic with no further setup.

Welcome Kit

12/20 Workspace+

Add a new hire once and a cron auto-shares their onboarding docs, emails them a personalized day-one welcome, and drops their first-week tasks on a shared calendar.

Cron

You spin up: A new-hire row in a Sheet (name, email, role, start date) triggers a scheduled job that, on their start date, shares the role's Drive resource folder with them, sends a mail-merged welcome email, and creates their orientation calendar events — all logged so it never double-runs.

Why it's great: Onboarding tools (Bamboo, Rippling) are heavy and pricey for a 10-person shop. This is the 80% that matters — the right docs get shared, a warm personalized email goes out, and their first meetings are on the calendar — fired automatically on the correct start date so you set it up weeks ahead and forget it. The role→resources mapping is owned glue Google doesn't ship.

Built on / setup

DriveApp + GmailApp + CalendarApp + time-driven triggers, extending the official 'Share resources with new hires' and mail-merge samples.Requires Workspace (Drive folder sharing to org users). Map each role to a Drive folder ID once and set your welcome-email template; after that, adding a hire is the only action needed.

API

API apps (1)

SlotServer

11/20 Gmail

A real-availability booking endpoint backed by your actual Google Calendar — your site shows only genuinely free slots and POSTs create real events.

API

You spin up: A Sheet of bookable windows/rules (days, hours, slot length, buffer) plus endpoints: doGet returns open slots computed against your live Calendar's busy times, and doPost books a chosen slot by creating a Calendar event and confirmation email.

Why it's great: Calendly is $12+/mo and lives on their domain; a static site can't natively expose your real availability. This reads YOUR calendar so slots are never double-booked, books straight into it, and renders inside your own booking UI — owned scheduling glue that beats the paid tool on cost and site ownership.

Built on / setup

CalendarApp free/busy + event creation (the calendar sign-up/offsite samples) + SpreadsheetApp rules + GmailApp confirmation, served as doGet/doPost JSON.Set your hours/slot rules in the Sheet; it reads your existing Calendar automatically. Wire doGet to your slot picker — no external scheduler account.

Build these 12 once → cover most of the directory

Most of the 160 industry apps are one of these engines re-skinned. This is the roadmap.

Deposit-to-Book / No-Show Guard

Page

A branded booking page that captures a Stripe card hold or deposit before it confirms a slot, drops the appointment on the owner's Google Calendar, and auto-charges the late-cancel/no-show fee (or refunds on arrival) via a trigger watching the confirmation window. One booking+payments+calendar engine, reskinned per vertical's service and fee rules.

Re-skinned in: Nearly every appointment industry: therapy (No-Show Guard), dental/medical (implied on intake+treatment), fitness (No-Show Guard, Class booking), salons/spas (No-Show Shield), restaurants (NoShowGuard card hold), home services (Stripe Deposit-to-Book), automotive (Detail Booking Page), vet (NoShowGuard deposit), property mgmt (ShowingBook), childcare, coaching (NoShowGuard), events (deposit locks the date). Example apps: No-Show Guard, No-Show Shield, Deposit-to-Book, Detail Booking Page, ShowingBook.

Waitlist / Gap Filler

Cron

When a slot cancels or opens, a trigger blasts a first-come 'claim-this-slot' magic link to a priority-ordered waitlist, books the first taker, and expires the rest — cascading to the next person on a deadline. One cancel-detect + notify + first-claim-wins engine.

Re-skinned in: therapy (Waitlist Filler), fitness (Class Waitlist Filler), salons (GapFiller), restaurants (GapFiller shift/reservation), dental (NoShowGap Filler), tutoring (WaitlistFiller), e-commerce (Back-in-Stock Waitlist), childcare (WaitlistWaterfall, SubDispatch), restaurants (GapFiller shift), religious (implied). Example apps: GapFiller, Class Waitlist Filler, WaitlistWaterfall, Back-in-Stock Waitlist, SubDispatch.

Intake → Filed PDF / Brief

Page

A branded intake form whose answers merge into a formatted PDF (consent packet, client brief, starter program, patient history) that gets filed to the owner's Drive in a per-client folder and/or emailed before the first appointment. One form + Doc-merge + Drive-file engine, reskinned with the vertical's questions and output template.

Re-skinned in: therapy (Intake Packet Signer), dental (New-Patient Intake Vault), fitness (Intake-to-Program, Waiver Sign-Off), salons (IntakeConcierge, ConsentVault), tutoring (IntakeToTrial), vet (IntakePDF, BoardingPassport), coaching (IntakeToBrief), religious (Life-Event Request Intake), law (IntakeQualify), childcare (IncidentInk). Example apps: Intake Packet Signer, New-Patient Intake Vault, IntakeToBrief, IntakePDF, IntakeConcierge.

E-Sign Doc / Approval Gate

Page

A branded page that merges client+matter data into a contract/estimate/authorization, captures an ESIGN/UETA intent-to-sign click, and drops the signed + countersigned PDF into Drive — often gating a downstream action (retainer paid → date locked, deposit → folder unlocked, approval → work proceeds). One merge + sign + file + gate engine.

Re-skinned in: law (RetainerSign), dental (Treatment-Plan e-Sign), automotive (Repair Approval Link, Deal Doc Packager), vet (EstimateSign), events (ContractSign), coaching (ProposalSign), creative (DepositGate, ScopeLock, ProofRound), property mgmt (TurnoverPunchlist signed report), childcare (IncidentInk ack). Example apps: RetainerSign, Repair Approval Link, ContractSign, EstimateSign, ProofRound.

Dunning / Unpaid-Invoice Chaser

Cron

A scheduled trigger that reads an outstanding-balance sheet (or Stripe subscriptions) and runs an escalating polite→firm reminder sequence with a Stripe pay-now link, stopping automatically the moment payment lands. One aging + escalation-ladder + pay-link engine, reskinned with the vertical's tone and legal timing.

Re-skinned in: accounting (Invoice Nudge), fitness (Membership Dunning), dental (Statement & Balance Collector), home services (Unpaid-Invoice Chaser), creative (DunningLoop), property mgmt (RentLateLadder), childcare (TuitionDunning), nonprofits (PledgeChaser), law (PayRetainer follow-up), tutoring/e-commerce billing. Example apps: Invoice Nudge, Membership Dunning, Unpaid-Invoice Chaser, DunningLoop, RentLateLadder, TuitionDunning.

Deadline / Expiry Watchdog

Cron

A trigger that reads date fields from a Sheet and escalates reminders at fixed intervals (e.g. 180/90/30/7 days) before a legal, compliance, or renewal deadline lapses — routing the nag to the right person. One date-watch + tiered-escalation engine, reskinned with the vertical's deadline types.

Re-skinned in: real estate (DeadlineGuard Escrow), law (SOL Watchdog), home services (COI & License Expiry), nonprofits (GrantClock), property mgmt (LeaseRenewalClock, CertTracker), automotive (State Inspection Recall, We-Owe Tracker), events (FinalCallReminder), childcare (CampFormChaser), religious (Yahrzeit reminder). Example apps: SOL Watchdog, COI & License Expiry Watchdog, GrantClock, CertTracker, State Inspection Recall.

Recall / Rebook / Win-Back Drip

Cron

A trigger that scans a client sheet for people due (by service interval) or lapsed (no activity in N days) and fires a personalized one-click rebook/re-enroll/reactivation email, then stops once they book. One due-date/lapse-detect + personalized-nudge engine, reskinned per service cadence.

Re-skinned in: dental (Recall Reactivation Drip), real estate (SphereTouch), salons (Rebook Nudge, ReviewRush timing), fitness (Win-Back Watcher), tutoring (ReenrollNudge), home services (Maintenance-Season Rebooker), automotive (DeclinedWork Rescue), vet (RecheckReminder), coaching (RenewNudge), e-commerce (implied), nonprofits (LapseGuard). Example apps: Recall Reactivation Drip, SphereTouch, Win-Back Watcher, Maintenance-Season Rebooker, RenewNudge, LapseGuard.

Review Request Router

Cron

A trigger that waits N hours/days after a job is marked done/paid/delivered, then sends one throttled feedback link — routing happy clients to a public Google review and quietly alerting the manager on unhappy ones. One post-completion timer + sentiment-fork + review-link engine.

Re-skinned in: restaurants (ReviewRescue), salons (ReviewRush), home services (Review-Request Auto-Ask), automotive (Review Request Router), e-commerce (Review Request Timer), creative (TestimonialCatch), real estate (ShowingFeedback Loop). Example apps: ReviewRescue, Review Request Router, Review Request Timer, TestimonialCatch, Auto-Ask.

Prepaid Package / Session Meter

Cron

A ledger that sells a prepaid pack (sessions, classes, visits), decrements the balance on each completed booking, exposes the remaining balance on a branded magic-link portal page, and auto-nudges a top-up when they hit the last one or two. One balance-ledger + portal + refill-trigger engine.

Re-skinned in: fitness (Session Pack Ledger), tutoring (PrepaidBundleLedger), salons (PackagePass), coaching (PackageMeter + RenewNudge), vet (implied). Example apps: Session Pack Ledger, PrepaidBundleLedger, PackagePass, PackageMeter.

Retainer / Scope Meter

Cron

A monthly automation that tallies logged hours (or transactions) against a fixed-fee retainer or scope cap and emails a branded owned-domain statement flagging clients who cross 80%/100% before you eat the write-off. One hours-vs-cap tally + threshold-alert engine.

Re-skinned in: accounting (Retainer Meter), law (BillhoursCapture feeds it), coaching (RetainerLedger), creative (RetainerMeter, ScopeLock as the page variant). Example apps: Retainer Meter, RetainerLedger, RetainerMeter, ScopeLock.

Scheduled Statement / Digest Generator

Cron

A recurring trigger that rolls up rows from a Sheet (sessions, donations, sales, rent/expenses) into a branded PDF from a Doc template — a monthly statement, owner report, superbill, giving statement, or daily digest — and emails it in one batch. One rollup + Doc-merge-to-PDF + batch-email engine, reskinned with the vertical's line items.

Re-skinned in: accounting (Statement Snapshot), therapy (Superbill Builder), salons (TipSplit), restaurants (TipSplit), fitness (Trainer Pay Run), nonprofits (TaxLetterGun, Giving Statement Generator), property mgmt (OwnerStatement, DepositEscrowLog), e-commerce (Daily Sales & Payout Digest), religious (Weekly Bulletin Assembler), events (VendorPayout). Example apps: Statement Snapshot, Superbill Builder, Owner Statement, Giving Statement Generator, TipSplit, Daily Sales Digest.

Magic-Link Portal / Self-Service Page

Page

A no-login, magic-link-verified branded page that shows one client their own live data pulled from the owner's Drive/Sheet — status, invoices, gated content, directory info, gallery — or lets them edit their own record. One email-verified identity + per-client live-view/edit engine (delivery gated on payment where needed).

Re-skinned in: law (ClientPortal Lite), coaching (PackageMeter portal, GroupCohortHub), creative (AssetHandoff, GalleryGate), events (GalleryGate, GuestRSVPHub), e-commerce (Return & Exchange Portal, Wholesale Order Form), childcare (PickupPass), religious (Member Directory Self-Service), property mgmt (MaintenanceIntake), automotive/vet (Fleet Service API / RxRefillLine as endpoint variants). Example apps: ClientPortal Lite, GalleryGate, Return & Exchange Portal, Member Directory Self-Service, GroupCohortHub.

By industry — 160 micro-apps across 20 verticals

Real estate agents & brokers · 8

ListingSyndicator PDF

Gmail

One canonical listing sheet in the agent's Drive that auto-renders a branded flyer, a broker's-open one-pager, and an MLS-remarks blurb from the same source of truth.

PageSpreadsheetAppSlidesAppDriveAppDocumentApp

Pain: Agents retype the same listing copy into a Canva flyer, an email blast, and the MLS description, and when the price drops they miss updating one of them.

OpenHouse SignIn

Gmail

A branded open-house sign-in page that captures visitor email/phone, drops them in the agent's Sheet CRM, and fires an instant thank-you-plus-listing-PDF email while the visitor is still in the driveway.

PageSpreadsheetAppGmailAppDriveAppSlidesApp

Pain: Paper sign-in sheets get lost, buyers walk out un-followed-up, and the seller never sees a clean visitor report to prove the agent worked the listing.

SphereTouch

Gmail

A trigger that rotates through the agent's past-client Sheet and drafts one personalized check-in email per day (home-purchase anniversary, seasonal maintenance tip), so their sphere never goes cold.

CronSpreadsheetAppGmailAppPropertiesServiceCalendar

Pain: Agents know 80% of business is repeat and referral, but they never systematically touch their database, so old clients list with someone else.

ShowingFeedback Loop

Gmail

After every showing the buyer's agent gets a one-tap branded feedback page; responses aggregate into a seller-ready Sheet report the listing agent auto-emails weekly.

PageSpreadsheetAppGmailAppSlidesAppDriveApp

Pain: Listing agents chase buyer-side agents by text for showing feedback, then have nothing concrete to show a nervous seller who wants a price justification.

DeadlineGuard Escrow

Gmail

Agent enters contract dates once; a trigger watches inspection, appraisal, financing, and closing deadlines and escalates reminders to agent, client, and TC before anything blows the contract.

CronSpreadsheetAppCalendarAppGmailAppPropertiesService

Pain: A missed inspection-objection or financing-contingency deadline can void a deal or forfeit earnest money, and agents track these in their head or a whiteboard.

CMA Snapshot Sender

Gmail

A branded page where a homeowner enters their address and gets an instant emailed 'what's your home worth' PDF, feeding the agent a warm seller lead with the exact property already known.

PageUrlFetchAppSpreadsheetAppGmailAppSlidesApp

Pain: Agents pay for generic home-valuation lead ads that hand the lead to a portal; they want seller leads landing directly in their own inbox and CRM.

NewListing Blast

Workspace+

When the agent flags a Sheet row 'live', a trigger emails the matched-buyer segment (by price band and area tags) a branded new-listing alert before it hits the portals.

CronSpreadsheetAppGmailAppSlidesAppDriveApp

Pain: Agents sit on active buyer lists but manually cherry-pick who to email about a new listing, so buyers hear about it from Zillow first and the agent loses the coming-soon edge.

ReferralSplit Tracker

Workspace+

An endpoint the brokerage's site embeds where agents log outbound/inbound referrals; a trigger reminds on unpaid referral fees and auto-generates the referral agreement PDF for e-sign.

APISpreadsheetAppDocumentAppGmailAppDriveApp

Pain: Brokers lose track of the 25% referral fees owed to and from other agents, and the paperwork gets done on a napkin then disputed at closing.

Therapy & mental-health private practice · 8

No-Show Guard

Gmail

Auto-texts and emails a client 24h before session, captures a one-tap confirm/cancel, and auto-bills the no-show fee to the card on file.

CronCalendarAppGmailAppUrlFetchAppSpreadsheetApp

Pain: A single no-show is a lost $150-200 hour that can never be resold; therapists forget to enforce their own 24h cancellation policy because chasing the card feels awkward.

Intake Packet Signer

Workspace+

One branded link where a new client reads and e-signs the consent, HIPAA notice, and practice policies, dropping a timestamped signed PDF into the therapist's Drive.

PageDocumentAppDriveAppGmailAppSpreadsheetApp

Pain: New-client onboarding stalls for days emailing back PDFs; consent and financial-policy forms come back unsigned or half-filled, creating legal exposure at the first session.

Superbill Builder

Gmail

Monthly trigger compiles each self-pay client's sessions into a CPT-coded, itemized superbill PDF and emails it so they can claim out-of-network insurance reimbursement.

CronCalendarAppSpreadsheetAppDocumentAppDriveApp

Pain: Self-pay clients constantly ask for superbills to submit to insurance; therapists hand-build them in Word one at a time, and missing/wrong CPT codes get the client's claim denied.

Sliding-Scale Screener

Gmail

A branded page where prospective clients enter income and household size, get an instant fee tier from the therapist's own rules, and book a consult only if a slot matches.

PageSpreadsheetAppCalendarAppGmailAppPropertiesService

Pain: Sliding-scale therapists field dozens of 'what would you charge me?' emails, most from people who can't afford the low tier, burning hours of unpaid back-and-forth before a fit is even known.

Waitlist Filler

Gmail

When a session cancels, the trigger blasts the waitlist in priority order with a claim-this-slot link; first to tap gets it and the rest auto-expire.

CronCalendarAppGmailAppSpreadsheetAppLockService

Pain: A cancellation two days out becomes a permanently empty (unpaid) hour because manually calling down a waitlist never happens fast enough, and the slot goes dark.

Between-Session Check-In

Gmail

Sends clients a short, therapist-configured mood/homework check-in on a schedule and logs responses to a private per-client tab for the next session.

PageSpreadsheetAppGmailAppCalendarAppPropertiesService

Pain: Progress between weekly sessions is invisible; therapists waste the first 15 minutes reconstructing the week, and insurance increasingly wants measurement-based-care data they don't collect.

Referral Loop Closer

Gmail

When a therapist can't take a client, one page routes them to a vetted colleague and auto-emails both sides so the referral actually lands and gets acknowledged.

PageSpreadsheetAppGmailAppPropertiesService

Pain: Full or out-of-scope therapists give 'here are some names' and never know if the client got help; warm referrals die and the referring colleague relationship gets no credit.

Group Roster Manager

Workspace only

Runs enrollment, weekly attendance, and per-session billing for therapy groups from one page, capping seats and dunning missed payments automatically.

PageSpreadsheetAppCalendarAppGmailAppUrlFetchApp

Pain: Running an 8-person DBT or process group means tracking who paid, who showed, and who dropped across a messy spreadsheet, and unpaid group sessions quietly pile up.

Dental & medical practices · 8

NoShowGap Filler

Workspace+

When a patient cancels within 48 hours, it auto-texts your waitlist in priority order and books whoever replies YES first, before the chair sits empty.

CronCalendarAppSpreadsheetAppUrlFetchAppPropertiesService

Pain: A cancelled hygiene or crown appointment inside 48h is a $150-$1,200 hole the front desk almost never fills because manually phoning a waitlist during a busy morning is the first thing that gets dropped.

New-Patient Intake Vault

Workspace only

A branded intake page that captures medical history, insurance card photos, and a signed HIPAA consent into the practice's own Drive before the patient walks in.

PageDriveAppSpreadsheetAppDocumentAppGmailApp

Pain: Patients fill paper forms in the waiting room, staff re-key them into the PMS, and half the medical-history fields are blank or illegible — costing 10-15 min of chair time per new patient and creating liability gaps.

Recall Reactivation Drip

Workspace+

Finds patients overdue for a 6-month cleaning and drips a 3-touch email/SMS sequence until they rebook, then stops automatically.

CronSpreadsheetAppGmailAppUrlFetchAppPropertiesService

Pain: 20-40% of a practice's active patients silently lapse past their recall date; nobody runs the overdue report because it's tedious, and each lost hygiene patient is ~$300/yr in recurring revenue plus downstream restorative work.

Treatment-Plan e-Sign & Estimate

Workspace only

Turns a proposed treatment plan into a branded page showing the procedure, insurance estimate, and patient portion, with an ESIGN consent button that files the signed PDF to Drive.

PageDocumentAppDriveAppSpreadsheetAppGmailApp

Pain: Case acceptance drops when patients leave with a paper estimate to 'think about it'; there's no clean way to send a plan they can review and accept from home, so high-value crown/implant/ortho cases stall.

Insurance Eligibility Chaser

Workspace+

A background job that pings a clearinghouse API for tomorrow's scheduled patients and flags any with inactive coverage or hit deductibles so the front desk fixes it before the visit.

CronCalendarAppSpreadsheetAppUrlFetchAppGmailApp

Pain: Practices discover a patient's plan is inactive AFTER the appointment, then eat the write-off or chase the patient for payment; verifying eligibility by phone for a full day's schedule is hours of hold music nobody has.

Post-Op Recovery Check-In

Workspace+

After an extraction, implant, or procedure, it auto-sends a timed check-in ('how's the pain?') and escalates a same-day call-back to the doctor if the patient reports red-flag symptoms.

CronCalendarAppSpreadsheetAppUrlFetchAppGmailApp

Pain: Post-surgical complications (dry socket, infection, uncontrolled bleeding) get missed because no one follows up; the patient shows up at an ER or leaves a 1-star review, when a 24h text would have caught it.

Statement & Balance Collector

Workspace+

Emails patients with an outstanding balance a branded statement page with a Stripe pay-now button, and re-nudges on a schedule until paid.

PageSpreadsheetAppUrlFetchAppGmailAppDocumentApp

Pain: Patient A/R over 60 days is where practices bleed — mailed paper statements cost postage and get ignored, and staff won't cold-call people for $80 balances, so thousands in small balances quietly age into write-offs.

Referral Loop Closer

Workspace only

When a GP refers a patient to a specialist (or vice versa), it tracks the referral and auto-nags both offices until the consult report comes back and is filed to Drive.

CronSpreadsheetAppGmailAppDriveAppDocumentApp

Pain: Referrals fall into a black hole — the specialist never sends the report, the referring doc never learns the outcome, continuity of care breaks and the referring practice stops sending patients that direction.

Fitness gyms & personal trainers · 8

No-Show Guard

Gmail

A booking page that captures a card at reservation and auto-charges the trainer's late-cancel fee when a client bails inside the window.

PageCalendarAppSpreadsheetAppUrlFetchAppGmailApp

Pain: Personal trainers lose a full session's revenue every time a client cancels 2 hours before or ghosts, and chasing the fee by text feels petty so they eat it.

Session Pack Ledger

Gmail

Tracks each client's remaining pre-paid PT sessions, decrements on every completed booking, and auto-emails a top-up link when they hit their last two.

CronSpreadsheetAppCalendarAppGmailAppUrlFetchApp

Pain: Trainers sell 10-session packs on paper or a notes app, lose count, give away free sessions, and forget to re-sell before the pack runs out — the single biggest silent revenue leak in personal training.

Waiver Sign-Off

Gmail

A branded liability-waiver and PAR-Q signing page that emails a locked PDF to both the client and the gym owner's Drive before the first session.

PageDocumentAppDriveAppGmailAppSpreadsheetApp

Pain: Gyms let new members start training before the paper waiver is signed, then have zero defensible record when someone gets hurt; chasing signatures at the front desk is chaos.

Membership Dunning

Workspace+

Watches the gym's Stripe subscriptions and runs a polite escalating email sequence on failed payments before the membership silently churns.

CronUrlFetchAppSpreadsheetAppGmailAppPropertiesService

Pain: Boutique gyms lose members to expired cards and failed monthly charges they never even notice; Stripe's default dunning is one bland email and the member is gone.

Trainer Pay Run

Workspace+

Reads completed sessions from the gym calendar, applies each trainer's per-session or split rate, and emails every trainer a PDF pay statement each pay period.

CronCalendarAppSpreadsheetAppDocumentAppDriveApp

Pain: Studio owners reconcile trainer commissions by hand in a spreadsheet every two weeks — hours of error-prone math over who trained whom and at what split.

Win-Back Watcher

Gmail

Flags members whose visit frequency has dropped off a cliff and fires a personal check-in email from the owner before they cancel.

CronSpreadsheetAppCalendarAppGmailAppPropertiesService

Pain: The strongest churn signal in a gym is a member who stops showing up two weeks before they cancel — but nobody is watching the attendance log, so the save window is missed every time.

Class Waitlist Filler

Gmail

When a client cancels a full class inside the cutoff, it auto-offers the open spot to the waitlist in order and confirms the first taker.

PageCalendarAppSpreadsheetAppGmailAppUrlFetchApp

Pain: A popular 6am class has a cancellation and an eager waitlist, but the front desk finds out too late to fill it, so the studio runs a class one head short of capacity revenue.

Intake-to-Program

Gmail

A new-client intake form whose answers get merged into a branded starter-program PDF and emailed automatically the moment they submit.

PageFormAppSpreadsheetAppDocumentAppDriveApp

Pain: Every new PT client fills out goals, injuries, and availability, then waits days for the trainer to hand-build a starter plan — the slow first impression that loses onboarding momentum.

Law firms (small / solo) · 8

RetainerSign

Gmail

Send an engagement letter as a branded signing page; the signed PDF and countersigned copy land in the matter's Drive folder automatically.

PageDocumentAppDriveAppGmailAppSpreadsheetApp

Pain: Solo attorneys chase clients for weeks to sign engagement letters, then can't find the executed copy when a fee dispute or bar complaint hits.

IOLTA Ledger Guard

Gmail

A trust-accounting ledger in the firm's own Sheet that logs every client deposit/disbursement and emails the attorney the moment any client sub-balance goes negative.

CronSpreadsheetAppGmailAppPropertiesServiceLockService

Pain: Commingling or overdrawing a client's trust sub-account is the single fastest way to get disbarred, and QuickBooks doesn't enforce per-client trust balances.

SOL Watchdog

Gmail

A statute-of-limitations tracker that reads matter dates from a Sheet and escalates reminders at 180/90/30/7 days before any filing deadline lapses.

CronSpreadsheetAppCalendarAppGmailAppPropertiesService

Pain: A single missed statute-of-limitations date is the #1 cause of legal malpractice claims for solos, and calendar reminders get dismissed and forgotten.

IntakeQualify

Gmail

A headless intake API for the firm's site that captures matter type, conflict-check names, and jurisdiction, then instantly flags conflicts against the firm's existing-client Sheet.

APISpreadsheetAppGmailAppUrlFetchAppPropertiesService

Pain: Solos take on a new client, then discover a conflict of interest days later — after doing work — because there's no fast conflict check at intake.

MatterFolder Provisioner

Gmail

New matter row in a Sheet auto-creates a standardized Drive folder tree (Pleadings/Correspondence/Billing/Evidence) with the naming convention and permissions preset.

CronDriveAppSpreadsheetAppPropertiesService

Pain: Every solo's Drive becomes a swamp of inconsistent folders, so documents get misfiled and discovery/e-filing prep takes hours of hunting.

BillhoursCapture

Workspace+

Attorney emails a one-line time entry ('Smith v Jones, 0.4, drafted motion') to a magic inbox; it parses, appends to the billing Sheet, and generates a branded invoice PDF on demand.

CronGmailAppSpreadsheetAppDocumentAppDriveApp

Pain: Solos lose 20-30% of billable hours because they reconstruct time from memory at month-end instead of capturing it the moment work happens.

ClientPortal Lite

Gmail

A magic-link client portal page showing that client's matter status, invoices, and shared documents pulled live from their Drive folder — no login, no seat.

PageSpreadsheetAppDriveAppGmailAppPropertiesService

Pain: Clients constantly email 'any update on my case?' and 'can you resend the invoice?', burning attorney time on status pings.

PayRetainer Link

Gmail

A branded Stripe payment page for retainers/invoices that records the payment in the trust-vs-operating Sheet and emails a receipt, routing funds to the correct account bucket.

PageUrlFetchAppSpreadsheetAppGmailAppPropertiesService

Pain: Solos either don't take cards (slowing payment) or use generic Stripe links that don't distinguish trust deposits from earned fees — a trust-accounting hazard.

Accounting & bookkeeping · 8

PBC Chaser

Workspace+

A trigger-driven bot that hounds each client for their monthly missing bank statements, receipts, and questions until the folder is complete.

CronDriveAppGmailAppSpreadsheetAppPropertiesService

Pain: Bookkeepers lose days every month-end emailing clients 'still need your March Amex statement' and manually tracking who sent what — the close slips because docs trickle in.

Uncat Q&A

Gmail

A branded page where the client answers 'what was this $412 charge?' for every uncategorized transaction you push to them, writing answers straight back to your sheet.

PageSpreadsheetAppGmailAppPropertiesService

Pain: The 'uncategorized' pile is the single biggest close bottleneck — bookkeepers bury questions in email threads and clients answer three of the twelve, so the reconciliation stalls.

Ledger Watchdog

Gmail

A nightly job that scans the client's transaction sheet for duplicate vendor payments, round-number anomalies, and charges over a threshold, and emails you an exceptions digest.

CronSpreadsheetAppGmailAppPropertiesService

Pain: Duplicate ACH runs and doubled subscriptions leak real money for months because nobody eyeballs the full ledger between quarterly reviews.

Retainer Meter

Gmail

A monthly automation that tallies logged hours against each client's fixed-fee retainer and flags who is 30%+ over scope before you eat the write-off.

CronSpreadsheetAppGmailAppPropertiesService

Pain: Fixed-fee bookkeeping quietly bleeds margin when a client's volume creeps up — firms only notice scope-creep at year-end, long after it's unbillable.

1099 Assembler

Workspace+

A page that collects W-9s from the client's vendors via magic link, files the PDFs in Drive, and hands the bookkeeper a clean, complete 1099 list every January.

PageSpreadsheetAppDriveAppGmailAppFormApp

Pain: Every January is a scramble to chase missing W-9s and vendor TINs before the 1099 deadline, with W-9 PDFs scattered across email and penalties for late filings.

Close Cadence

Workspace+

A driver of the month-end close checklist that auto-advances tasks, escalates blockers to the manager, and won't let a client's books be marked closed with steps undone.

CronSpreadsheetAppGmailAppCalendarAppPropertiesService

Pain: Month-end close relies on a shared spreadsheet nobody updates in real time; the manager can't see which of 20 clients are stuck and books get 'closed' with steps skipped.

Statement Snapshot

Gmail

A monthly automation that renders each client's P&L and cash-position summary into a branded PDF from your Doc template and emails it the day after close.

CronSpreadsheetAppDocumentAppDriveAppGmailApp

Pain: Clients constantly ask 'how'd we do last month?' and bookkeepers hand-build a report or the client never gets one, so they don't feel the value they're paying for.

Invoice Nudge

Workspace+

A schedule-driven dunning bot that emails the client's overdue customers a polite escalating reminder with a Stripe pay link until the invoice clears.

CronSpreadsheetAppGmailAppUrlFetchAppPropertiesService

Pain: SMBs let receivables age because the owner hates chasing customers, and the bookkeeper isn't paid to send reminders — so 60-day-old invoices just sit there.

Tutoring & education · 8

NoShowGuard

Gmail

Auto-charges the late-cancel/no-show fee the moment a student ghosts a session, so tutors stop eating dead hours.

CronCalendarAppUrlFetchAppGmailAppPropertiesService

Pain: Tutors block an hour, a family cancels at 9pm the night before (or just doesn't show), and the tutor is too conflict-averse to chase the $60 cancellation fee written in their policy.

PrepaidBundleLedger

Gmail

Sells 10-session prepaid packs, then silently decrements the balance after each session and nags for a refill at 2 left.

CronCalendarAppSpreadsheetAppGmailAppUrlFetchApp

Pain: Parents buy a 10-pack, the tutor tracks it in their head or a messy spreadsheet, sessions get double-counted or forgotten, and packs expire with un-refunded balances and awkward money conversations.

ParentRecapAuto

Workspace+

Turns the tutor's one-line post-session note into a branded parent-facing progress email with homework and next focus, sent automatically.

CronSpreadsheetAppCalendarAppGmailAppUrlFetchApp

Pain: Parents paying $80/hr want proof of progress but tutors dread writing recaps, so they skip them, and vague value perception drives churn and price resistance.

IntakeToTrial

Gmail

A branded new-student intake form that captures goals, grade, and availability, then auto-books a paid trial and files a Drive folder per student.

PageCalendarAppDriveAppSpreadsheetAppGmailApp

Pain: Leads fill out a generic contact form, the tutor manually emails back-and-forth to schedule the trial, and half go cold in the reply gap; intake data lives nowhere reusable.

SessionReminderChain

Gmail

Sends the parent AND student escalating reminders (48h, morning-of, plus a 'reply Y to confirm') to kill no-shows before they cost anything.

CronCalendarAppGmailAppPropertiesServiceSpreadsheetApp

Pain: Half of no-shows are just forgotten sessions; a single calendar invite reminder to one email address (often the parent, not the teen) doesn't cut through.

MonthlyInvoiceRun

Workspace+

At month-end it tallies every calendar session per family, generates a branded PDF invoice, emails it, and Stripe-charges the card on file.

CronCalendarAppDocumentAppDriveAppGmailApp

Pain: Independent tutors spend the last weekend of every month reverse-engineering how many sessions each family had, hand-building invoices, and chasing payment — pure unpaid admin.

WaitlistFiller

Gmail

When a recurring slot frees up (cancel/drop), it instantly offers that exact time to the waitlisted family who wanted it, first-to-claim wins.

PageCalendarAppSpreadsheetAppGmailAppLockService

Pain: A prime after-school slot opens mid-term, the tutor forgets who was waiting, the slot sits empty for weeks — that's $240/mo of recurring revenue leaking.

ReenrollNudge

Gmail

Spots students who haven't booked in 21 days and fires a personalized 'ready to restart?' re-enrollment offer before they're gone for good.

CronCalendarAppSpreadsheetAppGmailAppUrlFetchApp

Pain: Students drift off after a good report card or a busy season, the tutor doesn't notice until the roster is thin, and win-back never happens because there's no system flagging the gap.

Restaurants & hospitality · 8

NoShowGuard

Gmail

Takes a card hold on large reservations and auto-charges the no-show fee via Stripe when the party ghosts.

PageSpreadsheetAppUrlFetchAppGmailAppPropertiesService

Pain: A ghosted 8-top on a Friday is a $400+ hole; staff have no way to hold a card and no time to chase no-shows manually.

PrepPar

Gmail

Emails the kitchen a daily prep sheet with par levels auto-adjusted for tomorrow's covers, weather, and day-of-week.

CronSpreadsheetAppGmailAppUrlFetchAppCalendarApp

Pain: Chefs over-prep and dump product or under-prep and 86 items mid-service because prep lists are guessed from memory each morning.

TipSplit

Workspace+

Runs the nightly tip pool by hours-worked and role weight, then emails each server their share and logs it for payroll.

CronSpreadsheetAppGmailAppPropertiesServiceDriveApp

Pain: Manual tip-pooling by the closing manager on a paper napkin causes math errors, staff disputes, and wage-theft liability.

GapFiller

Workspace+

When a shift opens up, blasts a first-come claim link to eligible staff and books the first taker automatically.

PageSpreadsheetAppGmailAppCalendarAppLockService

Pain: A sick line cook two hours before service means the manager phone-tree-texts the whole crew; the shift goes uncovered and service suffers.

ReviewRescue

Gmail

Texts/emails a private feedback link after each meal; happy guests get routed to Google, unhappy ones get a quiet manager alert.

PageSpreadsheetAppGmailAppUrlFetchAppPropertiesService

Pain: One angry 1-star Google review tanks the local rank; the manager only finds out publicly, days later, when it's too late to fix.

AllergenLog

Gmail

Generates a printable, always-current allergen matrix from the recipe sheet and PDFs it to front-of-house on every menu change.

CronSpreadsheetAppDocumentAppDriveAppGmailApp

Pain: Servers guess or interrupt the chef about nut/gluten/dairy in a dish; a wrong answer is an anaphylaxis lawsuit and a health-code fail.

CateringQuote

Workspace+

A branded catering-inquiry page that returns an instant itemized quote, takes a deposit via Stripe, and drops the event on the calendar.

PageSpreadsheetAppUrlFetchAppCalendarAppGmailApp

Pain: Catering leads sit in a shared inbox for days; by the time someone quotes them, the client booked the caterer who replied first.

HealthCheckLog

Gmail

Sends line staff a daily temp-log and cleaning-checklist link, timestamps each entry, and files the audit trail for the inspector.

PageSpreadsheetAppGmailAppDocumentAppDriveApp

Pain: Paper temp logs get back-filled in pen right before the health inspector arrives, which is exactly the falsification inspectors write up.

Salons, spas & beauty · 8

No-Show Shield

Gmail

Card-on-file deposit link that auto-charges the fee when a client ghosts their appointment.

PageUrlFetchAppSpreadsheetAppGmailAppPropertiesService

Pain: Last-minute cancels and no-shows blow a hole in the chair's daily revenue and there's no card to charge because the booking tool doesn't hold one.

GapFiller

Workspace+

When a slot cancels, it texts the top of your waitlist and books whoever grabs it first.

CronCalendarAppSpreadsheetAppGmailAppUrlFetchApp

Pain: A same-day cancellation leaves a dead 90-minute hole in a stylist's book that never gets refilled because nobody has time to phone down a waitlist.

Rebook Nudge

Gmail

Color clients get a personalized 'you're due for a touch-up' link timed to their exact service interval.

CronCalendarAppSpreadsheetAppGmailAppPropertiesService

Pain: Clients who don't pre-book drift for 8-10 weeks and never rebook; the salon has no way to nudge them at the right moment per service type.

ConsentVault

Gmail

Branded pre-treatment consent and patch-test forms clients sign on their phone, PDF'd into your Drive.

PageDocumentAppDriveAppSpreadsheetAppGmailApp

Pain: Waxing, lash, chemical peel and color services need signed liability/patch-test consent, but paper forms get lost and there's no timestamped record if a client reacts and complains.

TipSplit

Workspace+

Weekly automated tip and commission statement emailed to each stylist and booth renter.

CronSpreadsheetAppGmailAppDriveAppPropertiesService

Pain: Owners burn Sunday nights hand-tallying each stylist's service revenue, commission split and tips from a messy spreadsheet, and renters dispute the math.

ReviewRush

Gmail

Texts a Google review link 2 hours after checkout, only to clients who tipped or rebooked.

CronCalendarAppSpreadsheetAppGmailAppUrlFetchApp

Pain: New salons live and die by Google Maps rating but forget to ask, and blasting everyone surfaces the one unhappy client's 1-star.

PackagePass

Gmail

Sells and tracks prepaid service packages (10 blowouts, 6 facials) with a redeemable balance link.

APIUrlFetchAppSpreadsheetAppGmailAppPropertiesService

Pain: Front desk tracks 'client bought a 5-facial package' on a sticky note or spreadsheet, loses count, and honors sessions the client already used up.

IntakeConcierge

Gmail

New-client branded intake page (skin type, allergies, meds, hair history) delivered to the stylist before the chair.

PageSpreadsheetAppDocumentAppDriveAppGmailApp

Pain: Stylists meet a new color or facial client cold with no allergy/medication/history info, causing bad reactions and awkward 10-minute consults that eat the appointment.

Home services & trades (plumbing, HVAC, electrical, cleaning, landscaping) · 8

On-My-Way Dispatch Text

Gmail

Fires the customer a branded 'tech is 15 min out' text with a live map link the moment your crew taps a job as en-route.

PageSpreadsheetAppUrlFetchAppPropertiesServiceScriptApp

Pain: The #1 SMB home-services complaint is the 8am-to-5pm window and no-show anxiety; nobody's home, the tech eats a wasted truck roll, and the review goes to one star.

Stripe Deposit-to-Book Page

Gmail

A branded booking page that won't confirm the slot until the customer pays a card deposit, killing tire-kicker no-shows.

PageCalendarAppUrlFetchAppSpreadsheetAppGmailApp

Pain: Trades lose whole days to booked-then-ghosted appointments; a plumber holding a 2-hour slot for a free estimate that evaporates is pure lost revenue with no recourse.

Photo-to-PDF Estimate Sender

Workspace+

Tech snaps job-site photos and picks line items on a phone page; it auto-builds a branded PDF quote and emails it before they leave the driveway.

PageDriveAppDocumentAppSpreadsheetAppGmailApp

Pain: Estimates written on paper or 'I'll email you tonight' die on the drive home; every hour of delay drops close rate, and the job goes to whoever quoted first.

Unpaid-Invoice Chaser

Gmail

A nightly trigger that escalates polite-to-firm payment reminders on aging invoices and stops the second Stripe marks it paid.

CronSpreadsheetAppGmailAppUrlFetchAppScriptApp

Pain: One-person trades are terrible at dunning; a $1,400 completed job sits unpaid for 60 days because nobody wants the awkward follow-up call, and the cash flow gap kills payroll.

Maintenance-Season Rebooker

Workspace+

A trigger that finds last year's spring AC-tune and fall furnace customers and auto-emails a one-click rebook link at exactly the right week.

CronSpreadsheetAppCalendarAppGmailAppScriptApp

Pain: HVAC and landscaping live or die on recurring seasonal service, but the recall list rots in a spreadsheet; every un-rebooked tune-up is a $180 job the competitor's flyer steals.

Review-Request Auto-Ask

Gmail

The evening after a job closes as paid, it texts/emails that one happy customer a direct Google review link, throttled so you never blast.

CronSpreadsheetAppGmailAppUrlFetchAppScriptApp

Pain: Trades know reviews are their #1 lead source but forget to ask in the moment; the ask goes out days later, cold, and the 5-star moment is gone.

After-Hours Emergency Triage Line

Gmail

A branded intake page for burst-pipe / no-heat calls that captures the emergency, texts the on-call tech instantly, and tells the customer their spot in line.

PageSpreadsheetAppUrlFetchAppGmailAppCalendarApp

Pain: Emergency trades bleed high-ticket midnight jobs to whoever answers first; a voicemail nobody checks til 8am is a $900 burst-pipe job handed to a competitor.

COI & License Expiry Watchdog

Gmail

A trigger that tracks every tech license, insurance COI, and vehicle registration and emails the owner 30/14/1 days before each one lapses.

CronSpreadsheetAppGmailAppDriveAppScriptApp

Pain: A lapsed contractor license or expired liability COI means an instant job stoppage or a general-contractor client cutting you off; owners find out only when a permit gets rejected.

Nonprofits & membership associations · 8

LapseGuard

Workspace+

A trigger that watches your membership roster in Sheets and fires the exact renewal-nudge sequence that recovers lapsing members before they churn.

CronSpreadsheetAppGmailAppPropertiesServiceCalendarApp

Pain: Membership orgs lose 20-40% of members annually to silent non-renewal because nobody manually chases the 90/60/30/expired-day emails, and the ED only notices the revenue hole at year-end.

TaxLetterGun

Workspace+

Drop a donation export in a Sheet and it auto-generates each donor's IRS-compliant year-end acknowledgment as a branded PDF and emails it.

CronSpreadsheetAppDocumentAppDriveAppGmailApp

Pain: Small nonprofits spend January mail-merging hundreds of 501(c)(3) tax-substantiation letters by hand, missing the required good-faith-estimate and quid-pro-quo language and risking donor complaints and IRS issues.

PledgeChaser

Gmail

Automated reminders for multi-year and installment pledges that ping donors before each due date and flag broken pledges to staff.

CronSpreadsheetAppGmailAppCalendarAppPropertiesService

Pain: Capital-campaign and recurring pledges routinely go uncollected because the tracking lives in one staffer's spreadsheet and no one systematically reminds donors when their $500/quarter installment is due.

BoardPacket

Workspace+

A branded page that assembles the board meeting packet from your Drive folder into one paginated PDF, logs who opened it, and emails the agenda.

PageDriveAppDocumentAppSlidesAppGmailApp

Pain: Executive assistants burn hours before every board meeting merging financials, minutes, and committee reports into one packet, then have no idea which board members actually read it.

VolunteerHours

Gmail

A branded self-serve page where volunteers log hours, and a trigger totals them, emails monthly statements, and flags grant-reporting milestones.

PageSpreadsheetAppGmailAppCalendarAppPropertiesService

Pain: Orgs must report volunteer hours for grants and insurance but rely on paper sign-in sheets, so hours go uncounted and the annual in-kind value (worth real match dollars) is undercounted at grant time.

GrantClock

Gmail

A background job that tracks every grant's report and renewal deadlines and escalates overdue deliverables to the ED before the funder notices.

CronSpreadsheetAppCalendarAppGmailAppPropertiesService

Pain: Missing a grant report deadline can forfeit the next tranche or blacklist you with a foundation, yet deadlines live scattered across award letters and one program officer's memory.

DuesInvoice

Workspace+

Generates annual membership dues invoices with a Stripe pay-link, then reconciles payments back to the roster and marks members active.

APISpreadsheetAppDocumentAppUrlFetchAppGmailApp

Pain: Associations chasing annual dues manually create invoices, email PDFs, and then hand-match Stripe/bank deposits to the member list, losing days and letting unpaid members keep member benefits.

CommitteeRoster

Workspace only

Syncs your member roster to Google Groups by committee and chapter, so email lists, Drive access, and mailing rights update themselves when members join or lapse.

CronSpreadsheetAppAdminDirectoryGroupsPropertiesService

Pain: In Workspace-based associations, committee and chapter email lists drift from reality: lapsed members keep list access and new members wait weeks to get added, because someone edits Groups by hand.

E-commerce & small retail · 8

Abandoned Checkout Rescue

Workspace+

A trigger fires branded 'you left this behind' emails to shoppers who dropped off at checkout, with a live discount code baked in.

CronSpreadsheetAppGmailAppUrlFetchAppPropertiesService

Pain: SMB stores on Shopify Lite / a bare Stripe checkout lose 60-70% of carts and have no cart-recovery flow because the built-in one is locked behind a pricier plan or a per-message SaaS.

Back-in-Stock Waitlist

Workspace+

A branded page lets shoppers request an out-of-stock item; when your inventory sheet flips to in-stock, everyone waiting gets an instant email.

PageSpreadsheetAppGmailAppPropertiesServiceLockService

Pain: Sold-out product pages leak demand silently — the customer leaves and never learns it came back, so the restock sells slow instead of selling out day one.

Local Pickup & Delivery Slots

Gmail

A branded booking page where local customers reserve a curbside-pickup or same-day-delivery window, capacity-capped per slot, dropped straight onto the shop's calendar.

PageCalendarAppSpreadsheetAppGmailAppLockService

Pain: Small retailers doing local pickup juggle it over DMs and texts, double-book slots, and have no cap — so three people show up at 5pm and the counter is chaos.

Return & Exchange Portal

Workspace+

A magic-link page where a customer pulls up their order, picks items to return, states a reason, and gets a branded RMA + prepaid-label email — logged to a sheet.

PageSpreadsheetAppGmailAppDocumentAppDriveApp

Pain: Returns arrive as vague emails ('want to send back the blue one') with no order match, no reason data, and no tracking, so the owner reconstructs each one by hand.

Wholesale / Net-Terms Order Form

Workspace+

A password-gated branded page where stockists submit reorders at their tier pricing; it generates a Net-30 invoice PDF and logs the order for fulfillment.

PageSpreadsheetAppDocumentAppDriveAppGmailApp

Pain: B2B/wholesale reorders come in as messy emails at retail-priced confusion, and the owner hand-types every stockist invoice at the wrong terms.

Review Request Timer

Workspace+

A trigger waits N days after an order is marked delivered, then sends one branded review-request email with a direct link, and stops nagging once they respond.

CronSpreadsheetAppGmailAppUrlFetchAppPropertiesService

Pain: Owners fire review requests manually and inconsistently — too early (before it arrives), too late, or never — leaving a thin review count that kills conversion.

Low-Stock Reorder Alert

Gmail

A nightly trigger scans the inventory sheet, and when any SKU drops below its reorder point, it emails the owner a consolidated buy-list grouped by supplier.

CronSpreadsheetAppGmailAppPropertiesService

Pain: Bestsellers quietly hit zero because nobody watches the stock column daily; the stockout is discovered only when a customer can't buy it.

Daily Sales & Payout Digest

Gmail

A morning trigger pulls yesterday's Stripe charges, refunds, and fees, then emails a one-glance digest: net revenue, top SKUs, refund rate, and next payout date.

CronUrlFetchAppSpreadsheetAppGmailAppPropertiesService

Pain: The owner logs into Stripe/Shopify separately every morning and still can't quickly see true net-of-fees revenue or catch a refund spike, so problems fester for days.

Automotive (repair, detailing, dealers) · 8

DeclinedWork Rescue

Gmail

Every repair a customer said no to comes back as a scheduled reminder that books the return visit — automatically recovering the work you already diagnosed.

CronSpreadsheetAppGmailAppCalendarAppPropertiesService

Pain: Shops eyeball a $900 brake job, the customer defers it, and it's never followed up on; that deferred/declined work is the single biggest silent revenue leak in independent repair.

State Inspection Recall

Gmail

Auto-emails and texts customers 30/15/3 days before their safety or emissions inspection sticker expires, with a one-click booking link to your bay.

CronSpreadsheetAppGmailAppCalendarAppPropertiesService

Pain: Inspection/emissions stickers expire on a rolling annual cycle and customers forget; the shop that reminds first captures the visit (and the upsells found during it) instead of the competitor down the road.

Detail Booking Page

Gmail

A branded self-serve booking page for detailing packages that blocks the right number of hours per service and drops the job straight on the tech's calendar.

PageCalendarAppSpreadsheetAppGmailAppHtmlService

Pain: Detailers lose an hour a day playing phone/text tag to schedule, and double-book a 5-hour ceramic coat into a 1-hour-window slot because there's no service-aware calendar.

Repair Approval Link

Gmail

Text the customer a branded page showing the diagnosed line items with photos; they tap Approve or Decline each one and you get an e-signed authorization back.

PageSpreadsheetAppDriveAppGmailAppHtmlService

Pain: Techs stop work waiting for verbal approval on added repairs, and disputes over 'I never authorized that' cost shops chargebacks and comebacks with no paper trail.

Deal Doc Packager

Gmail

Merges buyer and vehicle data into your dealership's buyer's-order, disclosure, and we-owe forms as one branded PDF, ready to email for signature.

CronSpreadsheetAppDocumentAppDriveAppGmailApp

Pain: Small used-car lots hand-fill the same buyer's order, odometer, and as-is disclosure for every sale — slow, error-prone, and a compliance risk when a form is missed.

We-Owe Tracker

Gmail

Tracks every 'we owe' promise made at delivery — second key, touch-up, floor mats — and escalates to the manager if it isn't closed within the promised window.

CronSpreadsheetAppGmailAppPropertiesService

Pain: Dealers promise a missing key or a repair 'next week' at delivery, it falls through the cracks, and the customer torches them in a Google review over a $40 item.

Review Request Router

Gmail

After a car is marked picked up, it waits two hours then texts a happy-path Google review link — but routes anyone who flags a problem to the manager privately first.

CronSpreadsheetAppGmailAppPropertiesService

Pain: Shops and lots live and die by star rating but forget to ask at the right moment, and blast review links to unhappy customers who then leave the one-star in public.

Fleet Service API

Workspace+

A JSON endpoint your commercial fleet clients embed in their own portal to see each vehicle's service history, next-due mileage, and open estimates from your shop.

APISpreadsheetAppUrlFetchAppPropertiesServiceLockService

Pain: Shops with fleet accounts field constant 'when's my truck due / what did you do last time' calls and emails, burning service-advisor hours on lookups.

Events, weddings & photography · 8

GalleryGate

Gmail

A branded photo-delivery page that unlocks the full-res download only after the client's final invoice is paid.

PageDriveAppUrlFetchAppSpreadsheetAppPropertiesService

Pain: Photographers hand over full galleries before the balance clears, then chase money for weeks with zero leverage once the client already has the images.

TimelineWeaver

Gmail

Turns a couple's wedding-day questionnaire into a branded PDF run-of-show that auto-syncs to every vendor's calendar.

CronSpreadsheetAppDocumentAppCalendarAppDriveApp

Pain: The day-of timeline lives in one planner's head or a fragile shared doc; the DJ, photographer and caterer all work off stale versions and miss cues like golden-hour or grand-entrance.

FinalCallReminder

Workspace+

A silent trigger that escalates the final-payment-plus-final-details deadline as the event date approaches.

CronSpreadsheetAppCalendarAppGmailAppUrlFetchApp

Pain: Balances and critical detail forms (shot list, guest count, dietary) are due 2 weeks out, but the planner is too slammed with THIS weekend's event to chase NEXT month's — so it slips and blows up on-site.

InquiryTriage

Gmail

A booking-inquiry endpoint that instantly checks date availability against the calendar and auto-sends the right price sheet or a polite 'booked' redirect.

APICalendarAppGmailAppSpreadsheetAppDriveApp

Pain: Every 'are you free June 14th?' email needs a manual calendar check and a copy-pasted reply; slow responses lose the couple to the photographer who answered in 5 minutes.

ContractSign

Gmail

A branded page that merges the couple's details into the shoot contract, captures an intent-to-sign signature, and locks the date only after the retainer is paid.

PageDocumentAppDriveAppUrlFetchAppSpreadsheetApp

Pain: Dates get double-promised because 'pencil me in' emails aren't binding; the studio holds Saturdays for couples who never sign or pay the deposit.

ShotListCollector

Gmail

A magic-link form where couples build their must-have shot list and family-photo groupings, output as a print-ready day-of PDF.

PageFormAppSpreadsheetAppDocumentAppDriveApp

Pain: The 'don't-miss' family combinations (divorced parents, grandma who leaves early) arrive as a chaotic email thread the night before, and the second shooter never sees them.

VendorPayout

Workspace+

An automation that reconciles which sub-vendors (second shooter, DJ, florist) are owed after each event and drafts the payment breakdown.

CronSpreadsheetAppGmailAppDriveAppUrlFetchApp

Pain: After a busy season the lead studio loses track of what it owes freelance second-shooters and referral partners per event, and either overpays or burns the relationship by paying late.

GuestRSVPHub

Gmail

A branded RSVP + meal-choice page for the couple's guest list that keeps a live headcount and dietary tally for the caterer.

PageSpreadsheetAppDocumentAppDriveAppMailApp

Pain: Guest counts and meal selections trickle in across text, email and paper cards; the couple hand the caterer a wrong number and get charged for no-show plates.

Property management & landlords · 8

TurnoverPunchlist

Gmail

The move-out inspection walk becomes a photo-stamped, tenant-signed damage report and a security-deposit deduction letter, generated on the spot.

PageSpreadsheetAppDriveAppDocumentAppGmailApp

Pain: Landlords lose deposit disputes because move-out damage photos, timestamps, and the itemized deduction math live in a phone camera roll and a hand-scrawled sheet, and the 21/30-day statutory deduction letter goes out late or not at all.

RentLateLadder

Gmail

A trigger watches the rent-received sheet and fires the exact late-notice sequence — grace-period reminder, late-fee notice, pay-or-quit — on the legally-timed schedule, each as a dated PDF.

CronSpreadsheetAppGmailAppDriveAppDocumentApp

Pain: On a 15-unit portfolio the landlord forgets which tenants crossed the grace period, applies late fees inconsistently (which voids them in court), and serves the pay-or-quit notice a day early so the eviction gets tossed.

MaintenanceIntake

Gmail

A branded work-order page on the client's own domain takes tenant repair requests with photos, auto-routes to the right vendor by category, and tracks status without a tenant portal login.

APISpreadsheetAppDriveAppGmailAppUrlFetchApp

Pain: Repair requests arrive by text, voicemail, and at 11pm — no photo, no unit number, no paper trail — so vendors get dispatched twice, warranty/habitability deadlines get missed, and the owner can't prove response time.

LeaseRenewalClock

Gmail

Scans lease end-dates and, at the notice window, auto-generates the renewal-or-vacate letter with the new rent (capped to local rent-control limits) and emails it to the tenant.

CronSpreadsheetAppDocumentAppGmailAppDriveApp

Pain: Leases silently roll month-to-month because nobody watched the 60/90-day renewal window, so the owner loses a rent increase for a whole year — or worse, over-raises past a rent-cap and eats a penalty.

ShowingBook

Gmail

A branded self-scheduling page for prospective-tenant unit showings that pre-screens (income, move-in date, pets) before it hands out a slot and drops it on the leasing agent's calendar.

PageCalendarAppSpreadsheetAppGmailAppPropertiesService

Pain: Leasing agents burn hours texting back and forth to book showings, then no-show prospects and unqualified applicants waste the slot, and the vacant unit bleeds another week of rent.

DepositEscrowLog

Gmail

Logs each security deposit, which trust/escrow account it sits in, and auto-produces the annual interest statement and the itemized return ledger some states require.

CronSpreadsheetAppDocumentAppDriveAppGmailApp

Pain: Commingled deposits and missing interest statements are a per-tenant statutory penalty in many states, and at move-out the owner can't cleanly show where the money was held or reconcile it against deductions.

OwnerStatement

Workspace+

For agencies managing units on behalf of owners: each month it rolls rent-in and expenses-out per property into a branded owner statement PDF and emails it with the net payout figure.

CronSpreadsheetAppDocumentAppDriveAppGmailApp

Pain: The property manager spends the first week of every month hand-building owner statements in Excel, and owners chase them for the numbers — a recurring, error-prone, unbillable time sink.

CertTracker

Gmail

Tracks expiry dates for vendor insurance certificates, rental business licenses, smoke/CO and boiler inspections, and pings the owner (and the vendor) before each one lapses.

CronSpreadsheetAppGmailAppDriveAppPropertiesService

Pain: An uninsured contractor or a lapsed rental license turns one slip-and-fall or one inspection into an uncovered liability, and nobody notices the COI expired until a claim gets denied.

Veterinary & pet services · 8

NoShowGuard

Gmail

Deposit-backed booking pages for grooming and surgery slots that auto-refund when the pet shows up.

PageSpreadsheetAppUrlFetchAppGmailAppCalendarApp

Pain: Groomers and clinics lose a full day of chair/table revenue to no-shows on 90-minute grooms and pre-booked spays; front desk has no way to hold a card without a per-transaction SaaS.

RecheckReminder

Workspace+

Trigger-driven recall engine that emails owners the day their pet is due for the next vaccine, heartworm refill, or post-op recheck.

CronSpreadsheetAppGmailAppPropertiesServiceLockService

Pain: Lapsed vaccines and unfilled heartworm/flea prescriptions are the single biggest silent revenue leak; PIMS reminder modules are clunky and staff forget to run them.

IntakePDF

Gmail

A branded new-patient intake page that turns owner+pet history answers into a filed PDF in the clinic's Drive before the first visit.

PageFormAppSpreadsheetAppDocumentAppDriveApp

Pain: New clients fill paper clipboards in the lobby, techs re-key it into the PIMS, and nobody has vaccine history or prior meds until the pet is already on the table.

BoardingPassport

Gmail

A pre-boarding checklist page that verifies vaccine expiry dates and feeding/med instructions, then locks the reservation only when the pet is compliant.

PageSpreadsheetAppDriveAppGmailAppCalendarApp

Pain: Boarding and daycare turn away pets at drop-off because rabies/Bordetella lapsed, or dose a pet wrong because feeding notes were on a sticky note that fell off the kennel.

EstimateSign

Gmail

Emails a treatment estimate as a PDF with a magic-link approve button so the owner authorizes surgery cost before you cut.

PageDocumentAppDriveAppGmailAppSpreadsheetApp

Pain: Vets do a procedure, then fight to collect on an over-budget bill because verbal 'go ahead' from an anxious owner in the lobby isn't documented; declined care isn't logged either.

RxRefillLine

Gmail

A headless refill-request API embedded on the clinic site that logs prescription requests and auto-escalates ones needing a recheck.

APISpreadsheetAppGmailAppUrlFetchAppPropertiesService

Pain: Refill requests come in by phone and voicemail, get lost, and owners buy from Chewy instead — losing the pharmacy margin and the recheck exam revenue.

PostOpCheckin

Workspace+

A scheduled 24h/72h post-surgery check-in that texts-via-email an owner a symptom questionnaire and flags red-answer cases for a callback.

CronSpreadsheetAppGmailAppFormAppPropertiesService

Pain: Nobody follows up after a spay or dental; complications get discovered too late (bad outcome + emergency + bad review), and healthy recoveries never generate a satisfaction touchpoint.

MobileVetRoute

Gmail

A booking page for mobile/house-call vets that clusters same-day appointments by ZIP and blocks impossible drive-time overlaps on the calendar.

PageCalendarAppSpreadsheetAppUrlFetchAppGmailApp

Pain: Mobile vets and mobile groomers waste hours double-booking across town or leaving dead windows because they book into a plain calendar with no travel buffer.

Coaching & consulting · 8

PackageMeter

Gmail

A branded client portal page that shows exactly how many coaching sessions are left in the package the client already paid for.

PageSpreadsheetAppCalendarAppHtmlServicePropertiesService

Pain: Coaches sell 6- or 12-session packages, then lose track of who has how many sessions left; clients dispute the count, and coaches give away free sessions or awkwardly chase for renewal at the wrong moment.

RenewNudge

Workspace+

A trigger that watches each client's remaining sessions and auto-emails a personalized renewal offer when they hit their second-to-last session.

CronSpreadsheetAppCalendarAppGmailAppPropertiesService

Pain: The single biggest revenue leak in coaching is packages that quietly expire without a renewal ask; the coach is too busy delivering to notice a client is one session from churning.

IntakeToBrief

Gmail

A branded intake form whose answers auto-generate a formatted pre-session client brief PDF the coach reads two minutes before the call.

PageFormAppSpreadsheetAppDocumentAppDriveApp

Pain: Coaches walk into discovery/first sessions cold, re-reading a messy Google Form response tab, and consultants waste billable prep time reformatting intake answers into something usable.

NoShowGuard

Workspace+

An automation that detects a booked session the client never confirmed and escalates reminders, then flags a no-show for the coach's cancellation policy.

CronCalendarAppGmailAppSpreadsheetAppPropertiesService

Pain: No-shows and late cancels are pure lost income for solo coaches; free Calendar reminders don't escalate, don't enforce a 24-hour policy, and don't log the pattern of a repeat offender.

ActionRecap

Gmail

After each session the coach fills a 60-second Doc; a trigger emails the client a branded recap with their committed action items and the next session's prep.

CronDocumentAppDriveAppGmailAppSpreadsheetApp

Pain: Clients forget what they committed to between sessions, so momentum dies and results (the coach's whole value prop and referral engine) suffer; hand-writing recap emails after every call doesn't scale.

ProposalSign

Gmail

A branded consulting-engagement page where the prospect reviews scope and pricing and e-signs to accept, triggering a deposit invoice.

PageDocumentAppDriveAppGmailAppSpreadsheetApp

Pain: Consultants lose deals in the gap between 'send proposal' and 'signed'; PandaDoc/DocuSign charge per-seat and per-envelope for what is a one-page scope-and-accept that solo consultants send a few times a month.

RetainerLedger

Workspace+

A monthly automation that tallies a retainer client's logged hours against their cap and emails an owned-domain statement flagging overage before it's billed.

CronSpreadsheetAppGmailAppUrlFetchAppPropertiesService

Pain: Consultants on monthly retainers silently blow past the hour cap and either eat the overage or spring a surprise bill that damages the relationship; nobody wants Harvest+QuickBooks glue for a two-client practice.

GroupCohortHub

Workspace only

One page per group-program cohort that gates weekly module links, tracks who's completed each, and nudges laggards on a schedule.

PageSpreadsheetAppDriveAppGmailAppGroupsApp

Pain: Group coaching and mastermind programs bleed completion (and testimonials/renewals) when members fall behind; a full LMS like Kajabi is overkill and $149/mo for a 12-person cohort.

Childcare, schools & summer camps · 8

RatioGuard

Gmail

A door check-in page that computes your live child-to-staff ratio the moment a parent signs a kid in or out, and screams before you go out of compliance.

PageSpreadsheetAppGmailAppPropertiesServiceLockService

Pain: Licensing fines the moment a room slips out of ratio, but the front desk is tracking heads on a clipboard and finds out you were short-staffed only when the inspector counts.

PickupPass

Gmail

A magic-link authorized-pickup page: parent taps who's grabbing the kid today, teacher sees the approved face and PIN at dismissal, every handoff is logged.

PageSpreadsheetAppDriveAppGmailAppPropertiesService

Pain: Aunt shows up for pickup, the teacher has no idea if she's authorized, calls the front office, holds up the whole dismissal line, and nobody has a record of who actually took the child.

WaitlistWaterfall

Gmail

When a spot opens in a specific age room, it auto-offers the seat to the next waitlisted family, gives them a 24h deadline, and cascades to the next family if they don't claim it.

CronSpreadsheetAppGmailAppPropertiesServiceLockService

Pain: A toddler-room spot frees up, the director emails the top family, waits days for a reply, the seat sits empty burning $1,400/month in tuition while ten families wait.

TuitionDunning

Workspace+

Reads outstanding balances from your tuition sheet and runs the whole late-payment escalation ladder — friendly nudge, firm reminder, late-fee notice — on a schedule, per family.

CronSpreadsheetAppGmailAppUrlFetchAppPropertiesService

Pain: Directors hate chasing parents for late tuition, so they don't, and thousands in receivables age out every month because nobody has time to send the awkward third reminder.

IncidentInk

Gmail

Teacher fills a boo-boo/incident report on a phone page, it generates a branded PDF, files it in Drive, and emails the parent a signature-required acknowledgment same day.

PageDocumentAppDriveAppGmailAppSpreadsheetApp

Pain: State requires documented incident reports with parent sign-off, but teachers scribble on paper that gets lost, and the center can't prove the parent was ever notified when it matters.

CampFormChaser

Workspace+

Tracks which summer-camp forms each enrolled kid still owes — physical, immunization, medication, photo release, swim level — and nags only the parents with missing pieces until the file is complete.

CronSpreadsheetAppDriveAppGmailAppPropertiesService

Pain: Camp starts Monday and 40 kids are missing at least one required form; the registrar is cross-referencing a spreadsheet against a folder by hand and blasting the whole roster instead of just the stragglers.

SubDispatch

Gmail

When a teacher marks out sick before open, it fires a first-come-first-served fill request to your qualified substitute pool and locks the shift to whoever claims it first.

PageSpreadsheetAppCalendarAppGmailAppLockService

Pain: A lead teacher calls in sick at 6am, the director now has a ratio problem in an hour, and is personally texting a dozen subs one at a time hoping someone picks up before parents arrive.

RosterProof

Gmail

Emails each classroom teacher a one-tap daily attendance/allergy roster every morning and, if an enrolled child isn't marked present or absent by mid-morning, alerts the office for a wellness call.

CronSpreadsheetAppGmailAppCalendarAppPropertiesService

Pain: A child is marked enrolled but never actually arrived and nobody notices — the safety and liability nightmare of an unaccounted-for kid — because the paper roster only gets reconciled at pickup.

Religious & community organizations · 8

Pledge & Tithe Tracker

Gmail

A branded giving page that logs every pledge and payment to the congregation's own Sheet and auto-nudges members who fall behind on their annual commitment.

PageSpreadsheetAppGmailAppUrlFetchAppPropertiesService

Pain: Annual pledge campaigns (Kol Nidre appeals, stewardship drives, building funds) leak thousands because nobody chases the half-paid pledges — the treasurer tracks them in a spreadsheet they forget to reconcile, and members simply forget the balance they promised.

Yahrzeit & Memorial Reminder

Gmail

An automation that emails families the day before a loved one's memorial date (Hebrew or Gregorian) and quietly prompts a memorial-fund donation.

CronSpreadsheetAppGmailAppCalendarAppUrlFetchApp

Pain: Yahrzeit/anniversary-of-death observances are a core pastoral duty, but the rabbi or secretary manually cross-references a paper list against the Hebrew calendar every week and misses dates — a missed yahrzeit deeply wounds a grieving family and a relationship.

Volunteer Rota Confirmer

Gmail

A weekly automation that assigns the ushers, greeters, coffee-hour hosts, and lay readers from a Sheet, emails each their slot, and escalates unfilled roles.

CronSpreadsheetAppGmailAppCalendarAppPropertiesService

Pain: Every service needs bodies in specific roles, and the volunteer coordinator spends Saturday nights texting people one-by-one to confirm and scrambling when a greeter no-shows — the gaps are visible to the whole congregation.

Life-Event Request Intake

Gmail

A branded form + triage automation for wedding, funeral, baptism/bris, and pastoral-visit requests that routes each to the right clergy calendar and starts a checklist.

PageSpreadsheetAppGmailAppCalendarAppDocumentApp

Pain: Life-cycle requests arrive by phone, email, and hallway conversation with no single intake, so a funeral request and a wedding inquiry get the same ad-hoc handling, dates get double-booked, and the required steps (permits, deposits, meetings) fall through.

Giving Statement Generator

Workspace+

A year-end automation that turns each donor's giving rows into a branded, IRS-compliant PDF contribution statement and emails it out in one batch.

CronSpreadsheetAppDocumentAppDriveAppGmailApp

Pain: Every January the office must send every donor a tax-deductible giving statement; doing it by hand in Word is days of mail-merge drudgery, and errors trigger angry calls from members who need it for their taxes.

Member Directory Self-Service

Gmail

A magic-link page where members update their own contact info, family details, and photo, writing straight to the congregation's directory Sheet.

PageSpreadsheetAppGmailAppDriveAppPropertiesService

Pain: The congregation directory is perpetually stale — people move, kids age out, phone numbers change — and the office chases updates by hand, so mailings bounce and the printed directory is wrong the day it's published.

Hall & Facility Booking Page

Gmail

A branded page that shows real availability of the social hall, classrooms, and sanctuary, takes a booking with a deposit, and blocks the calendar to prevent double-booking.

PageCalendarAppSpreadsheetAppGmailAppUrlFetchApp

Pain: Renting the fellowship hall for bnai mitzvah parties, quinceaneras, and community meetings is real revenue, but bookings live in a paper calendar or the secretary's head, leading to double-bookings, forgotten deposits, and setup crews with no notice.

Weekly Bulletin Assembler

Workspace+

An automation that pulls this week's events, announcements, and readings from a Sheet into a branded PDF bulletin and emails it to the full list every Thursday.

CronSpreadsheetAppDocumentAppSlidesAppDriveApp

Pain: Producing the weekly bulletin / newsletter is a recurring time sink — someone rebuilds the same layout each week, copies event details by hand, and races the deadline, and late or skipped bulletins mean empty pews and missed events.

Creative freelancers & agencies · 8

ScopeLock

Gmail

A branded change-request page that turns every out-of-scope client ask into a signed, priced add-on before you touch it.

PageDocumentAppDriveAppGmailAppSpreadsheetApp

Pain: Scope creep: clients drip 'one small tweak' requests over email that never get billed, silently eating 20-30% of a project's margin.

DepositGate

Gmail

A signing page that collects the e-signed SOW and the deposit in one flow, then unlocks the shared project folder automatically.

PageDocumentAppDriveAppUrlFetchAppGmailApp

Pain: Agencies start 'kickoff' work on a handshake, then chase the 50% deposit for weeks while the client's already had value delivered.

ProofRound

Gmail

A per-deliverable approval page where clients click Approve or leave one round of comments, and 'approved' locks the version forever.

PageDriveAppDocumentAppGmailAppSpreadsheetApp

Pain: Endless revision rounds: 'final_v7_REALLY_final' with feedback scattered across email, Slack, and text, and no record of who signed off on what.

RetainerMeter

Workspace+

A background job that tallies logged hours against the monthly retainer and emails the client at 80% and 100% before you blow the cap.

CronSpreadsheetAppGmailAppPropertiesService

Pain: Retainer overages: teams burn past the monthly hour cap unnoticed, then either eat the loss or have an awkward month-end invoice fight.

DunningLoop

Workspace+

A trigger that escalates unpaid invoices on a schedule — polite nudge, firm reminder, then pause-work notice — until the Stripe payment lands.

CronSpreadsheetAppUrlFetchAppGmailAppPropertiesService

Pain: Late-paying clients: freelancers hate sending the awkward 'where's my money' email, so invoices sit 60-90 days and cash flow dies.

AssetHandoff

Workspace+

A closeout page that bundles final files, transfers Drive ownership to the client, and issues a dated license-grant PDF in one click.

PageDriveAppDocumentAppGmailAppSpreadsheetApp

Pain: Project offboarding chaos: source files, fonts, and usage rights get emailed as loose links, ownership is ambiguous, and clients come back months later asking for 'the files'.

TestimonialCatch

Gmail

An automation that, N days after project-approved, emails the client a one-question branded review page and files the response as a ready-to-use quote.

PageSpreadsheetAppGmailAppDocumentAppDriveApp

Pain: Nobody collects testimonials at the moment of delight; weeks later the client's moved on and every case study and social proof asset goes unwritten.

LeadTriage

Gmail

A JSON endpoint behind the agency's own site that scores and routes inbound project inquiries, auto-replying with a booking link only to real-budget leads.

APIUrlFetchAppSpreadsheetAppGmailAppCalendarApp

Pain: Tire-kicker inquiries: agencies waste hours on discovery calls for $500 'can you build me a Facebook' leads that were never a fit.

How the hard parts actually work

The reusable mechanisms these apps depend on — including how signatures really work on this stack, and the honest limits.

E-Signature / Signed-Document Capture on Apps Script

A self-hosted signing flow where an Apps Script web app serves an HTML5 canvas signature pad, captures the drawn image plus a bundle of attribution evidence (verified email, server timestamp, typed name, consent, document hash), and stamps it into a DocumentApp template exported as a PDF into the owner's Drive — legally useful as ESIGN/UETA intent-to-sign, but NOT a tamper-proof forensic record.

How it works

1. VERIFY IDENTITY FIRST (magic link). Since the web app request object exposes only e.parameter/e.parameters/e.postData/e.pathInfo/e.queryString and NO client IP, headers, cookies, or user-agent, the only trustworthy identity anchor is a verified email. Flow: signer enters email on a doGet page -> doPost generates a random token (Utilities.getUuid()), stores {email, token, expiry, documentId} in PropertiesService (script properties) or a Sheet row, and emails a link `?token=...` via MailApp.sendEmail (or GmailApp.sendEmail to send-as a domain address on Workspace). Scopes: `script.send_mail` (MailApp) or `gmail.send` (GmailApp). Clicking the link is the email-possession proof. 2. SERVE THE SIGNING PAGE (doGet). doGet validates the token against PropertiesService, then returns HtmlService.createHtmlOutput / createTemplateFromFile with: the rendered document text (or a link to view it), a consent checkbox ("I agree this electronic signature is legally binding"), a typed-full-name text input, and an HTML5 `<canvas>` signature pad (either hand-rolled pointer/touch event capture or a bundled lib like signature_pad.js inlined — no external CDN dependency needed). On submit, JS calls `canvas.toDataURL('image/png')` to get a base64 PNG and POSTs a form (or google.script.run) with: token, typedName, consentBool, pngDataUrl. 3. COMPUTE THE DOCUMENT HASH client-side or server-side. Hash the exact bytes/text of the document being signed with `Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, documentBytes)` and record the hex. This binds the signature to a specific document version so you can later prove "this signature was for THIS content." 4. RECEIVE + PERSIST (doPost). doPost re-validates the token (single-use: mark consumed in PropertiesService to prevent replay), records server-authoritative attribution: `new Date()` server timestamp (the server clock, NOT client-supplied), the verified email from the token record, the typed name, the consent boolean, the document SHA-256, and the PNG. Strip the `data:image/png;base64,` prefix and decode with `Utilities.base64Decode(...)` -> `Utilities.newBlob(bytes, 'image/png', 'signature.png')`. Wrap the whole write in LockService.getScriptLock() to avoid concurrent-write races if the same web app handles many signers. Append an audit row to a Sheet via SpreadsheetApp (append-only log: timestamp, email, name, hash, consent, token). Scope: `spreadsheets`. 5. STAMP INTO A DOCUMENT TEMPLATE. Copy a DocumentApp template with DriveApp: `DriveApp.getFileById(templateId).makeCopy(name, folder)` (scope `drive` or the narrower `drive.file` if the app created the template). Open with `DocumentApp.openById(copyId)`; use `body.replaceText('{{NAME}}', typedName)`, `replaceText('{{DATE}}', ...)`, `replaceText('{{EMAIL}}', email)`, `replaceText('{{HASH}}', hash)`. Insert the signature image at a placeholder: find the paragraph/element and `paragraph.appendInlineImage(signatureBlob)` (or insert at a positioned InlineImage). Call `doc.saveAndClose()`. Scopes: `documents`, `drive`. 6. EXPORT PDF TO DRIVE. Get the finalized PDF blob: `DriveApp.getFileById(doc.getId()).getAs('application/pdf')` (or `doc.getAs(MimeType.PDF)`), then `folder.createFile(pdfBlob)` to persist the signed PDF in the owner's Drive. Optionally email the PDF to the signer and owner as an attachment via MailApp.sendEmail({attachments:[pdfBlob]}). 7. (OPTIONAL) EXTERNAL TIMESTAMP / NOTARY. For stronger non-repudiation you can UrlFetchApp.fetch() an external RFC-3161 timestamp authority or a trusted-timestamp/anchoring API, POSTing the SHA-256 to get back a signed timestamp token you store alongside the record. This is the only way to get a timestamp that isn't just the owner's own server clock. Scope: external_request (UrlFetch).

Gotchas

- NO IP / UA / HEADERS. The single biggest thing people get wrong: you CANNOT capture the signer's IP address, user-agent, or any HTTP header — the Apps Script request object simply doesn't expose them. Any competitor claiming "we log the signer's IP" cannot be doing it in pure Apps Script. Attribution must be reconstructed from verified email + server timestamp + typed name + drawn image + consent + document hash. Be honest in your UI/audit trail about exactly what was and wasn't captured. - CLIENT TIMESTAMP IS UNTRUSTED. Never record the browser's clock as the signing time — a client can lie. Use `new Date()` inside doPost (server-side). Note this is the OWNER's server clock, still not an independent authority (see external timestamp option). - THE FINAL FILE IS OWNER-EDITABLE. The signed PDF/Doc lives in the owner's Drive and the owner has full edit rights — so it is NOT immutable and NOT tamper-proof. Anyone with edit access could regenerate or alter it. Do not market "forensic" or "tamper-proof." The integrity claim you CAN make is: the SHA-256 in the append-only audit log lets you detect whether a presented document matches what was signed. - MAGIC-LINK REPLAY. Tokens must be single-use and expiring, marked consumed atomically (LockService) — otherwise a leaked link lets someone re-sign or replay. - NO NATIVE RATE LIMITING. A public web app has no built-in throttling; a bot can hammer doGet/doPost. Gate everything behind the verified-email token and add your own PropertiesService/CacheService counters if abuse is a concern. - CANVAS QUALITY. `toDataURL('image/png')` on a small/low-DPR canvas yields a jagged signature; set the canvas backing store to devicePixelRatio and a sane resolution or the embedded image looks bad in the PDF. - BASE64 PAYLOAD SIZE. A high-res PNG data URL can be hundreds of KB in the POST body; fine for postData, but watch total execution and don't stuff raw base64 into a Sheet cell (50k char/cell limit) — store the PNG as a Drive file/blob, put only its fileId in the Sheet. - COLD START. First hit after idle adds ~1-2s; the signing page can feel slow. Pre-warm expectations, don't block the canvas render on server work.

Quotas

- EMAIL (magic links + PDF delivery): consumer ~100 recipients/day via MailApp/GmailApp; Workspace ~1,500/day. High signing volume on a consumer account will hit the 100/day wall fast — each signer typically costs 1 magic-link email + optionally 1-2 completion emails. Workspace's 15x headroom matters here. - EXECUTION: 6 min hard cap per doPost execution. DocumentApp replaceText + appendInlineImage + PDF export is usually well under that, but batch/bulk signing loops can blow it. - URLFETCH (external timestamp/notary API): consumer ~20k/day, Workspace 100k/day — non-issue at SMB volume. - TRIGGERS: not needed for the core synchronous sign flow, but if you use a time-driven trigger to send reminder emails for unsigned docs, that draws on the ~90 min/day (consumer) / ~6 hr/day (Workspace) trigger runtime budget and the same email quota. - SHEETS-AS-DB: the audit log is append-only rows; Sheets tops out in the tens of thousands of rows, so extremely high signature volume eventually needs archiving/rotation to a new Sheet. - PROPERTIESSERVICE: token store has size limits (500 KB total, 9 KB/value) — store minimal token records, not payloads.

Legal / trust

- WHAT YOU CAN HONESTLY CLAIM: Under US ESIGN Act and UETA, an electronic signature is valid when there is (a) intent to sign, (b) consent to do business electronically, (c) association of the signature with the record, and (d) attribution/record retention. This stack can satisfy all four: the consent checkbox + typed name + drawn signature show intent and consent; the verified-email magic link provides attribution; the document SHA-256 + append-only audit log + embedded signature associate the signature with a specific document version and retain the record. That is a genuinely legally-useful e-signature for most SMB agreements. - WHAT YOU CANNOT CLAIM: NOT tamper-proof, NOT immutable, NOT forensic — the signed file sits in an owner-editable Drive, and the 'audit trail' is a Sheet the owner can also edit. You are relying on the owner's good faith plus a detectable hash, not cryptographic custody. Do not imply the record is court-hardened evidence on par with a dedicated e-sign vendor with tamper-evident sealing and independent timestamping. - NOT eIDAS QUALIFIED / NOT ADVANCED. This is a simple/basic electronic signature. It is NOT an eIDAS Advanced or Qualified Electronic Signature (those need a qualified certificate and QSCD) — do not sell it into EU contexts that legally require QES. - NO CAPTURED IP/UA in the audit record — say so plainly, because customers coming from DocuSign/Dropbox Sign expect IP logging and it is impossible here. - STRENGTHEN IF NEEDED: adding an external RFC-3161 trusted timestamp via UrlFetch and, on Workspace, sending magic links + final PDFs from the business's own verified domain (deliverability + a stronger identity signal) meaningfully hardens the attribution story without leaving the stack.

Branded PDF Generation from a Template

A server-side Apps Script routine that copies a Google Doc (or Slides) template, swaps {{placeholders}} for row data, exports the result as a PDF, files it in a dated Drive folder, and emails it as an attachment — all running as the sheet owner inside their own Google account.

How it works

1. STORE THE TEMPLATE ONCE. The generated app keeps a Google Doc (or Slides deck) template in the owner's Drive, containing literal tokens like {{customer_name}}, {{invoice_total}}, {{date}}. You keep its fileId in PropertiesService.getScriptProperties(). Scopes touched from here on: https://www.googleapis.com/auth/documents, /auth/drive, and /auth/spreadsheets — the generated appsscript.json must declare these in oauthScopes or the owner's consent screen won't cover them. 2. TRIGGER. Either doPost(e) (a client POSTs form/row data; you read e.parameter / JSON.parse(e.postData.contents)) or a time-driven trigger (ScriptApp.newTrigger('run').timeBased()...) that reads pending rows from the sheet via SpreadsheetApp.openById(id).getSheetByName('Queue').getDataRange().getValues(). 3. COPY THE TEMPLATE PER RECORD. DriveApp.getFileById(TEMPLATE_ID).makeCopy(fileName, destFolder) → returns a new File. You must copy, never mutate the template. Grab the copy's id. 4. OPEN AND REPLACE. For a Doc: DocumentApp.openById(copyId); const body = doc.getBody(); then body.replaceText('\\{\\{customer_name\\}\\}', row.name) for each field — note replaceText takes a REGEX string, so literal braces must be escaped. Call doc.saveAndClose() before exporting or the changes may not be flushed. For images (logo, signature), you can't replaceText an image; you either bake the logo into the template, or use body.findElement / a {{logo}} inline-image placeholder you replace via InlineImage manipulation (fiddlier). Slides equivalent: SlidesApp.openById(id), presentation.replaceAllText('{{customer_name}}', value) then saveAndClose(). 5. EXPORT TO PDF. const pdfBlob = DriveApp.getFileById(copyId).getAs('application/pdf'); (or getAs(MimeType.PDF)). This uses Google's own Doc→PDF renderer, so fonts embedded in the Doc, page size, margins, headers/footers, and page breaks all render exactly as the Doc would print. Set the fileName: pdfBlob.setName(fileName + '.pdf'). 6. FILE IT. Build/find a dated folder: getOrCreate a folder tree like /Invoices/2026-06/ using DriveApp.getFoldersByName + createFolder (there's no mkdir -p; you walk it yourself). Then folder.createFile(pdfBlob) → returns the stored PDF file. Optionally DriveApp.getFileById(copyId).setTrashed(true) to delete the intermediate editable Doc so you don't litter Drive. 7. DELIVER. GmailApp.sendEmail(to, subject, plainBody, { htmlBody, attachments:[pdfBlob], name:'Sender Name' }). On Workspace you can set from: an alias to send-as the business domain. Or share a link instead: pdfFile.getUrl() + set sharing via pdfFile.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW). 8. MARK DONE. Write the PDF's URL/id and a server timestamp (new Date()) back to the queue row so re-runs are idempotent and a trigger batch can resume where it stopped.

Gotchas

- replaceText IS A REGEX. body.replaceText('{{total}}', x) will silently mis-match because { } are regex quantifiers. Escape them: '\\{\\{total\\}\\}'. This is the single most common bug. - FONTS: the PDF renders with whatever fonts the Doc uses. Google Fonts available in the Docs editor render fine. A brand's proprietary/licensed desktop font is NOT available and will substitute — you cannot upload an arbitrary .ttf into the Docs renderer. Pick a close Google-hosted font (or bake headline art into the template as an image). - LOGOS/IMAGES: text replaceText can't insert an image. Bake fixed branding (logo, letterhead) into the template. Per-record images require InlineImage APIs and are slow/finicky. - saveAndClose() BEFORE getAs(). If you export before the edits flush you get the unmodified template. This is intermittent and maddening to debug. - 6-MINUTE EXECUTION LIMIT IS THE REAL CEILING FOR BATCHES. Each record does: makeCopy (~1-2s, a Drive write), openById+replace+saveAndClose (~1-3s), getAs PDF render (~1-3s, the slowest step). Realistically ~3-6s/record, so ~60-120 PDFs per execution before you hit 6 min and Apps Script kills the run mid-flight. For 500 invoices you MUST chunk: process N rows, write progress back to the sheet, and let a self-rescheduling trigger (or a trigger every minute) pick up the next chunk. Do NOT try to loop 1,000 records in one doPost — it will time out and the user gets a truncated batch with no error surfaced to the browser. - makeCopy + getAs are QUOTA-METERED Drive operations, not free CPU — they count toward daily runtime AND can throttle under burst. - NO CLIENT IP/UA. If this PDF is a "signed" document, remember the web app request exposes only e.parameter/e.postData — you cannot capture the signer's IP, headers, or user-agent. Attribution must come from a verified email, the typed name, and the server timestamp. - COLD START ~1-2s per execution adds up across many trigger fires. - Doc→PDF has no fine print-control (no bleed, no CMYK, no crop marks). It's an office-quality PDF, not a print-shop-ready one.

Quotas

- 6 min per execution (hard kill); ~90 min/day total trigger runtime on consumer, ~6 hr/day on Workspace — this caps total PDFs/day for automated batches, not just per-run. - Email: MailApp/GmailApp ~100 recipients/day consumer, ~1,500/day Workspace. If you email each PDF, THIS is often the binding limit long before Drive quotas — a 200-invoice consumer batch that emails each one will hit the wall. Workspace's 15x headroom is the direct unlock. - Drive file creation/copy has daily create quotas and burst throttling; makeCopy on thousands of files/day can trip "too many requests" — add Utilities.sleep backoff and idempotent retries. - Sheets-as-queue tops out in the tens of thousands of rows before getValues() reads get slow/memory-heavy; page the queue if you're at that scale. - Chunk size math: budget ~5s/record, leave headroom, so ~50-60 records/execution is a safe chunk; a per-minute trigger then does ~50/min, i.e. thousands/day within the runtime cap on Workspace, far fewer on consumer.

Legal / trust

- The PDF and all intermediate Docs live in the OWNER's own Drive — genuinely owned, private, and portable. That's a real trust win over a SaaS that holds their invoices. - But an owner-editable Doc/PDF is NOT tamper-proof or forensic. The owner (and any editor) can alter the template, re-run generation, or edit the output. Do NOT market these as "immutable," "certified," or "legally binding signed originals." For a defensible record you'd need an external notarization/hash service via UrlFetchApp, and even then you're attesting to a hash, not preventing edits. - PDFs emailed as attachments leave Google's boundary; if content is PHI, only Workspace Business Standard+ under a signed Google BAA is HIPAA-eligible, and Gmail-to-external-recipient email is generally outside typical BAA-covered configurations — don't imply consumer Gmail is HIPAA-safe. - "ANYONE_WITH_LINK" sharing on a filed PDF is effectively public to anyone who gets the URL — be explicit with users that link-sharing an invoice/statement is not access-controlled.

Taking Stripe Payments from Apps Script

A pattern where the generated Apps Script web app charges buyers by calling Stripe's REST API with UrlFetchApp, redirecting to Stripe-hosted Checkout, and receiving fulfillment confirmation via a Stripe webhook hitting your doPost — with the entire money-critical path fighting Apps Script's lack of native idempotency, request rate-limiting, and its cold-start latency.

How it works

1. SETUP (once, at provisioning): Store the Stripe SECRET KEY and the WEBHOOK SIGNING SECRET (whsec_...) in PropertiesService.getScriptProperties().setProperty(). These live in the owner's own script project, not in your infrastructure. The script declares the OAuth scope https://www.googleapis.com/auth/script.external_request (UrlFetchApp) — that is the only scope payments strictly needs; SpreadsheetApp (auth/spreadsheets) is needed for the ledger, and LockService/CacheService need no scope. NOTE: Stripe has no official Apps Script SDK — you hand-roll the REST calls. 2. CREATE THE CHARGE (doGet or a client fetch): The buyer hits your web app. Server-side you call UrlFetchApp.fetch('https://api.stripe.com/v1/checkout/sessions', {method:'post', headers:{Authorization:'Bearer '+secret}, payload:{...}}). CRITICAL: Stripe expects application/x-www-form-urlencoded with bracket notation for nested fields (e.g. 'line_items[0][price]', 'line_items[0][quantity]'), NOT JSON — UrlFetchApp form-encodes a flat payload object for you, but you must flatten the nesting into bracketed keys yourself. Pass your own 'idempotency_key' in the headers (a UUID you generate and store) so a retried CREATE doesn't double-create a session. Parse the JSON response, read session.url. 3. REDIRECT THE BUYER: A web app cannot send a real 302 redirect (ContentService/HtmlService can't set Location headers). You return HtmlService.createHtmlOutput() with a <script>window.top.location.href='<stripe url>'</script> or a meta-refresh, OR return the URL to your front-end and redirect there. This is a genuine limitation — the redirect is client-side JS, not an HTTP redirect. 4. STRIPE PROCESSES PAYMENT on its own hosted page. Your code is not involved. 5. WEBHOOK ARRIVES at doPost(e): Configure the Stripe dashboard webhook endpoint to your deployed /exec URL for events like checkout.session.completed. Stripe POSTs the raw JSON body plus a Stripe-Signature header. 6. VERIFY THE SIGNATURE — this is where most people fail. Apps Script's e object does NOT expose request headers. e.postData.contents gives you the raw body, but there is NO way to read the Stripe-Signature header inside doPost. Therefore native HMAC signature verification of Stripe's header is IMPOSSIBLE on a standard web app. Honest workarounds: (a) put a shared secret in the webhook URL query string (?token=...) and check e.parameter.token — weak, since the URL is a bearer secret; or (b) treat the webhook as untrusted and, on receipt, call BACK to Stripe with UrlFetchApp — GET https://api.stripe.com/v1/checkout/sessions/{id} or /v1/events/{id} using your secret key — and trust only Stripe's authenticated response, not the POST body. Option (b) is the correct, secure pattern: the incoming POST is merely a 'ping', and you re-fetch the truth from Stripe over an authenticated TLS call. Do this before fulfilling. 7. DEDUPE on the Stripe EVENT ID: Cold starts and Stripe's at-least-once delivery mean doPost can run twice concurrently or hours apart for the same event. Acquire LockService.getScriptLock().waitLock(30000). Inside the lock, check whether event.id already exists in your Sheet ledger (a sheet with columns: event_id, session_id, amount, status, server_timestamp). Use CacheService.getScriptCache() as a fast first-pass dedupe (get(event.id)) but NEVER trust cache alone — it expires (max 6h) and can evict; the Sheet is the durable ledger. If event.id is already present and processed, releaseLock and return 200 immediately (idempotent no-op). 8. FULFILL + RECORD atomically-ish: Still holding the lock, append the row (SpreadsheetApp appendRow or Sheets values.append), THEN perform fulfillment (grant access, send receipt via MailApp/GmailApp, etc.), THEN releaseLock(). Order matters: write the ledger row first so a crash mid-fulfillment still leaves a record you can reconcile. Return a 200-ish ContentService.createTextOutput() so Stripe stops retrying. 9. RECONCILE (trigger): Because you can't fully trust webhook delivery, install a time-driven trigger (ScriptApp.newTrigger, e.g. every 6h) that lists recent Stripe sessions/PaymentIntents via UrlFetchApp and back-fills any paid-but-unfulfilled rows. This is your safety net for dropped webhooks.

Gotchas

- SIGNATURE VERIFICATION IS THE #1 TRAP: Because doPost cannot read the Stripe-Signature header (Apps Script exposes only e.parameter/e.parameters/e.postData/e.pathInfo/e.queryString — no headers, no IP, no cookies), you CANNOT do Stripe's documented HMAC signature check. Anyone who learns your /exec URL can POST a fake 'payment succeeded' body. The ONLY safe fix is to re-fetch the event/session from Stripe with your secret key and trust that response — never trust the webhook body. People who skip this ship a free-money exploit. - NO NATIVE IDEMPOTENCY: Apps Script gives you nothing. You must build dedup yourself with LockService + a durable Sheet ledger keyed on event.id. CacheService alone is unsafe (it evicts and expires). - CONCURRENCY + COLD STARTS: A public web app has NO native rate-limiting and can spin up multiple concurrent executions for simultaneous webhook retries. Without LockService.waitLock you get double-fulfillment (two access grants, two receipts, or a double refund). waitLock can itself time out — handle the timeout and let Stripe retry rather than proceeding unlocked. - 6-MINUTE EXECUTION LIMIT: doPost must finish in under 6 minutes. If fulfillment is slow (many emails, external API calls), do the minimal durable write inside the request and offload heavy work to a trigger, or Stripe will see a timeout and retry — compounding the dedup problem. - THE REDIRECT ISN'T A REAL REDIRECT: No Location header is possible; it's client-side JS/meta-refresh. Pop-up blockers and window.top sandboxing (if your form is iframed) can silently break it. - FORM ENCODING, NOT JSON: Stripe's API rejects JSON. You must form-encode with bracket notation. A JSON.stringify payload is a common silent 400. - SECRET IN THE URL (webhook): Since you can't verify the signature, the fallback token lives in the webhook URL. That URL is now a bearer secret — it leaks in logs and Stripe's dashboard. The re-fetch pattern (option b) is safer because even a leaked URL only lets an attacker trigger a re-fetch, not forge a payment. - SHEET AS LEDGER IS OWNER-EDITABLE: The buyer/owner can hand-edit the ledger sheet. It is a reconciliation record, NOT a tamper-proof book of record. Stripe is always the source of truth; the Sheet is a cache of Stripe state. - REFUNDS/DISPUTES: Same webhook fragility applies to charge.refunded and charge.dispute.created — re-fetch and dedupe those too, or your fulfillment state drifts from Stripe.

Quotas

- UrlFetchApp: ~20,000 calls/day on consumer, 100,000/day on Workspace. Every charge creation, every webhook re-fetch, and every reconciliation-trigger list call counts. The re-fetch-on-webhook pattern roughly DOUBLES your UrlFetch usage per payment (create + verify), plus reconciliation sweeps — budget accordingly; a high-volume seller can hit the consumer cap. - Trigger runtime: ~90 min/day total on consumer, ~6 hr on Workspace, with a 6-min-per-execution ceiling. The reconciliation sweep lives in this budget; keep each run short and paginated. - Email receipts via MailApp/GmailApp: ~100 recipients/day consumer, ~1,500 Workspace. High-volume checkout can exhaust the consumer email quota fast — send receipts via Stripe's own email or defer to a trigger, and note Workspace's 15x headroom. - Sheets-as-DB: tens of thousands of rows before it degrades. A busy payment ledger will outgrow a Sheet within months; plan to archive/rotate or move the ledger to a real DB via UrlFetchApp. - No native web-app rate limiting: a malicious actor can hammer doPost; each hit that triggers a re-fetch burns UrlFetch quota and can DoS your payment path. Add a cheap CacheService-based throttle.

Legal / trust

- PCI scope is minimized by design: with Stripe Checkout, card data never touches Apps Script — the buyer enters it on Stripe's hosted page, so you stay in the lightest PCI SAQ tier. Do NOT collect raw card numbers into a Sheet; that would balloon PCI obligations and is a hard no on this stack. Using raw PaymentIntents with a custom card form (Stripe Elements) is fine because the card still tokenizes in the browser, never in your script. - The Stripe secret key and webhook secret sit in the OWNER's PropertiesService, inside their own Google account. Your agency never holds the client's Stripe credentials — good for trust and liability, but it also means YOU cannot centrally rotate or monitor them; document key rotation for the client. - The Sheet ledger is owner-editable and therefore NOT a legal book of record or audit-grade financial log — never market it as 'immutable' or 'forensic'. Stripe's dashboard is the authoritative financial record for accounting, taxes, and disputes. - Money-critical correctness is a real liability surface: a signature/dedup bug means real dollars double-charged, double-refunded, or fulfilled for free. Treat the reconciliation trigger and the Stripe-as-source-of-truth re-fetch as non-negotiable, and reconcile against Stripe payouts before trusting any Sheet-derived number. - Consider Stripe's own idempotency and its dashboard-side fraud tooling (Radar) rather than reimplementing fraud checks in Apps Script, which lacks IP/UA/header signals to score risk.

Calendar Booking on Apps Script (free/busy read → locked write, signed reschedule tokens)

A self-hosted booking page that provisions into the owner's Google account, where a public Apps Script web app reads the owner's real Calendar free/busy to render open slots and writes the confirmed event directly onto their calendar — the honest replacement for a SaaS booking tool when you want the backend owned by the client, not rented.

How it works

1. PROVISION (front-end, one-time): the static front-end creates a Sheet + bound Apps Script in the owner's Drive, and the generated script's appsscript.json declares oauthScopes: https://www.googleapis.com/auth/calendar (or the narrower calendar.events) plus script.external_request (UrlFetch for email/webhooks) and script.scriptapp (triggers). The owner authorizes on first run; the web app is deployed "Execute as: Me (owner), Access: Anyone". Config (calendar id, slot length, buffers, business hours per weekday, timezone, max days out) is written to the Sheet or PropertiesService. 2. doGet — RENDER AVAILABILITY. e.parameter carries the requested date-range and, critically, the VISITOR's IANA timezone + UTC offset, which the client page must send (from Intl.DateTimeFormat().resolvedOptions().timeZone) because the request object exposes no headers/locale — e.parameter/e.parameters/e.postData/e.pathInfo/e.queryString are all you get, no IP, no UA. The script calls CalendarApp.getCalendarById(id) then getEvents(start, end) — or better getEvents and inspect, but for pure busy blocks use the Calendar Advanced Service (Calendar.Freebusy.query) which returns busy intervals without reading event details/titles. Generate candidate slots by walking the owner's availability rules in the OWNER's timezone (Session.getScriptTimeZone() or a stored config tz), convert each slot boundary to absolute UTC instants, subtract any busy interval that overlaps (accounting for pre/post BUFFER minutes), then serialize slots as UTC ISO strings. The HtmlService/JSON payload renders them in the VISITOR's tz client-side. Store and compare everything as absolute UTC Date instants; never compare wall-clock strings. 3. doPost — BOOK WITH A LOCK. Visitor posts {slotStartUtc, name, email, tz}. Server: a. LockService.getScriptLock().waitLock(20000) — a script-wide mutex. This is the ONLY thing that prevents a double-booking race across two concurrent cold-start executions, because each request may spin up a fresh instance with no shared memory. b. Inside the lock, RE-QUERY Calendar.Freebusy.query (or getEvents) for that exact slot to confirm it is still free — the availability the visitor saw may be stale. Also re-validate the slot against the rules server-side (never trust the client's slot; recompute that slotStartUtc is a legal boundary, within business hours, within max-days-out, buffers respected). c. If free: CalendarApp.getCalendarById(id).createEvent(title, startUtcDate, endUtcDate, {guests: visitorEmail, sendInvites: true, description}) — this puts the event on the owner's calendar and (with guests) sends a native Google Calendar invite. Capture event.getId(). d. Persist a booking row to the Sheet: eventId, slotUtc, name, email, visitor tz, server timestamp (new Date() on the server), and a status. Use SpreadsheetApp with a header-mapped append. For idempotency, generate a booking token now. e. releaseLock() in a finally block. 4. SIGNED TOKENS for reschedule/cancel. There are no cookies/sessions. Mint an HMAC token = base64url(payload={eventId, exp}) + "." + base64url(Utilities.computeHmacSha256Signature(payload, SECRET)) where SECRET lives in PropertiesService (getScriptProperties). Email confirmation + "Reschedule"/"Cancel" links pointing back at the same web app URL with ?action=cancel&t=TOKEN. On that doGet, recompute the HMAC and constant-time compare, check exp, then look up eventId and CalendarApp...getEventById(eventId).deleteEvent() for cancel, or render a new slot picker for reschedule (delete + recreate under the same lock). This is the honest attribution model: a verified action authenticated by a signed token, not a captured IP. 5. REMINDERS via time-driven trigger. ScriptApp.newTrigger("sendReminders").timeBased().everyHours(1).create() at provision time. The handler scans the Sheet (or Calendar) for bookings ~24h/1h out that haven't been reminded, and sends via MailApp.sendEmail() / GmailApp; mark reminded=true to avoid resends. Note Google's own native event reminders also fire from the invite, so this is mainly for branded/SMS-webhook reminders. UrlFetchApp.fetch() can hit Twilio/an SMS provider or a CRM webhook. 6. CONFIRMATION email: MailApp.sendEmail({to, subject, htmlBody}) with the ICS-style details and the signed reschedule/cancel links. On Workspace with send-as configured, GmailApp can send from the business domain for deliverability.

Gotchas

- TIMEZONE is where naive builds die three ways: (a) don't store wall-clock strings — store absolute UTC instants and convert at render; (b) the OWNER's availability rules ("9–5 Mon–Fri") are in the owner's tz, so generate slots in owner-tz then convert to UTC, while the VISITOR sees them in visitor-tz — three timezones in play; (c) DST: a rule crossing a DST boundary can produce a missing or doubled wall-clock hour. Use the Calendar Advanced Service / real Date arithmetic, not hand-rolled offset math (a fixed +N offset breaks twice a year). Utilities.formatDate(date, tz, fmt) is the correct converter. - DOUBLE-BOOKING is NOT prevented by "check then create" — that's a TOCTOU race. Two visitors hitting the same slot land in two separate execution instances with no shared state; only LockService (a real cross-execution mutex) plus a RE-CHECK inside the lock is correct. Skipping the re-check inside the lock still double-books if the free/busy read happened before the lock. - COLD START ~1–2s means waitLock timeouts must be generous (15–20s); a slot re-query + createEvent + Sheet append can eat a couple seconds. LockService.waitLock throws if it can't acquire — handle it and return "please retry", don't 500. - NEVER TRUST CLIENT SLOTS: the client sends slotStartUtc; recompute legality server-side. Otherwise anyone can POST an off-hours or already-passed slot. - e.parameter is the ENTIRE input surface — no IP, no headers, no user-agent, no cookies. You cannot rate-limit by IP natively and cannot capture the booker's IP. A public web app has NO built-in rate limiting; a bot can spam doPost. Mitigate with a CAPTCHA/turnstile token you verify via UrlFetchApp, a PropertiesService/CacheService counter keyed by email, and the LockService serialization (which at least prevents corruption, not abuse). - Email verification: a typed email is unverified. If you need the booker's email confirmed, add a magic-link step (email a signed token, confirm on click) before writing — otherwise "attribution" is just self-asserted. - The owner's calendar/Sheet is OWNER-EDITABLE — the booking log is real data but NOT tamper-proof or forensic; don't market it as an immutable audit trail. - getEvents leaks event titles/details; prefer Calendar.Freebusy.query so the public page never touches private event content, and so an availability bug can't render someone's meeting titles. - Reschedule race: reschedule = delete old + create new, and the create must run under the same lock + re-check, or you can lose the slot between delete and create. - HTMLService iframe sandbox: the booking UI runs sandboxed; the visitor-tz must be captured in client JS and posted explicitly, and cross-origin embedding on the agency's marketing site needs the web app's XFrameOptions set to ALLOWALL.

Quotas

- Email: consumer MailApp/GmailApp caps ~100 recipients/day; Workspace ~1,500/day. A busy booking page sending confirmation + 24h + 1h reminders burns 3 emails per booking — ~33 bookings/day exhausts a consumer account, ~500/day on Workspace. This alone is a real reason to steer higher-volume clients to Workspace. - Triggers: ~90 min/day total trigger runtime on consumer (~6 hr Workspace), max 6 min per execution, and only ~20 triggers per script. An hourly reminder-sweep is cheap; don't scan a huge Sheet every minute. - UrlFetch (SMS/webhook/CAPTCHA verify): ~20k/day consumer, 100k Workspace. - CalendarApp/Freebusy reads count against Calendar API quotas but are generous for this scale; the practical ceiling is the Sheet-as-DB — bookings in the tens of thousands of rows is fine, but a single global bookings Sheet degrades well before a real DB would. - No per-request rate limit means a traffic spike or bot can pile up executions and hit the concurrent-execution limit (~30 simultaneous), causing waitLock timeouts — size the lock timeout and surface ret#ries gracefully.

Legal / trust

- Data residency/ownership is genuinely strong: the calendar, the event, and the booking Sheet all live in the CLIENT's own Google Drive — no third-party SaaS holds the customer PII. That's the real selling point over Calendly/Cal.com for privacy-sensitive SMBs. - But "owned" is not "compliant by default": the booking log is owner-editable, so make no immutability/forensic/audit-proof claims. For HIPAA-adjacent booking (clinics), the client must be on a Workspace edition with a signed Google BAA (Business Standard+), and you must avoid putting PHI in event titles/descriptions the public page or invited guests could see. - Consent/records: since you cannot capture IP/UA, any "consent record" is a server timestamp + typed name + (ideally) a magic-link-verified email — describe it as exactly that, not as a signed/witnessed legal record. - Sending confirmation email as the client's own domain (Workspace send-as) improves deliverability and trust vs. a generic no-reply, and keeps the whole flow first-party.

Passwordless / Magic-Link Auth for an Apps Script Client Portal

Email-only login for a client portal where the web app mints an HMAC-signed, expiring URL token, mails it as a magic link, and on click verifies the signature and issues a short-lived server-stored session — because an Apps Script web app can't read cookies, headers, or IPs, so all auth state has to ride in the URL and live in server-side storage keyed by a token.

How it works

1. STORE A SECRET AT PROVISION TIME. When the front-end provisions the script, generate a random 32-byte secret and write it with `PropertiesService.getScriptProperties().setProperty('HMAC_SECRET', ...)`. Script Properties are per-script and not exposed to the public request object, so this is your signing key. (Scope: `script.storage`, implicit — no OAuth prompt for PropertiesService.) 2. REQUEST LINK (doPost). The static front-end POSTs `{email}` to the web app URL. In `doPost(e)` you read `e.parameter.email` (or parse `e.postData.contents` if you sent JSON). Normalize/lowercase it. Optionally check it against an allowlist Sheet (`SpreadsheetApp.openById(...).getSheetByName('Clients')`) so only known clients get links. 3. MINT A SIGNED TOKEN. Build a payload string, e.g. `email + '|' + exp` where `exp = Date.now() + 15*60*1000` (server clock via `new Date()` / `Date.now()`). Compute `var sig = Utilities.computeHmacSha256Signature(payload, secret)` — this returns a `Byte[]`. Base64url-encode both: `Utilities.base64EncodeWebSafe(payload)` and `Utilities.base64EncodeWebSafe(sig)`. The token is `payloadB64 + '.' + sigB64`. Nothing secret is in the token; the signature is what makes it unforgeable. 4. EMAIL THE MAGIC LINK. Send `WEBAPP_URL + '?token=' + token` (or `?p=...&s=...`) via `MailApp.sendEmail(email, subject, body)` (scope `script.send_mail`, no From control) or `GmailApp.sendEmail(...)` if you want the owner's Gmail identity / send-as. On Workspace you can send-as the business domain for deliverability. This is the ONLY delivery channel that proves the person controls the mailbox — that mailbox possession is your entire identity proof. 5. VERIFY (doGet). The link hits `doGet(e)`. Read `e.parameter.token`. Split on `.`, base64url-decode the payload, recompute HMAC over the payload with the same secret, and compare to the provided signature byte-by-byte (constant-time-ish loop — don't `==` the strings lazily; length-check then XOR-accumulate). Reject if mismatch. Then parse `exp` and reject if `Date.now() > exp`. This is stateless verification: no DB lookup needed to know the token is authentic and unexpired. 6. ISSUE A SESSION. On success, generate a fresh random session id (`Utilities.getUuid()`), and store a session record server-side keyed by that id: either `CacheService.getScriptCache().put(sid, JSON.stringify({email, exp}), 21600)` (max 6 h TTL) for speed, and/or append a row to a `Sessions` sheet (`sheet.appendRow([sid, email, created, expires])`) for durability/audit. Return the sid to the browser. Because you have no cookies, the browser must carry the sid itself — store it in `localStorage` (the web app is served in an iframe under `script.googleusercontent.com`, so localStorage works) and send it as a `?sid=` parameter or POST field on every subsequent call. 7. AUTHENTICATE SUBSEQUENT REQUESTS. Each later `doGet`/`doPost` reads `e.parameter.sid`, looks it up in Cache (fast path) then Sheet (fallback), checks expiry, and serves data. Single-use hardening: mark the magic-link token consumed by recording a `jti` (a nonce you put in the payload) in Cache/Sheet on first use and rejecting replays.

Gotchas

- NO COOKIES, NO HEADERS, NO SET-COOKIE. The request object only exposes `e.parameter(s)`, `e.postData`, `e.pathInfo`, `e.queryString`. You cannot set a real HTTP cookie, cannot read `Authorization`, cannot read Referer. So "session cookie" is impossible — the session id MUST travel in the URL/body and be held in browser localStorage. Treat any URL containing a live token/sid as a bearer credential (browser history, proxies, shoulder-surfing all leak it). - NO IP / NO USER-AGENT. You cannot rate-limit by IP or fingerprint the device natively. Someone can hammer `doPost` to blast magic-link emails at arbitrary addresses (email-bomb / your-quota-drain). Mitigate with a Sheet/Cache counter per email+minute and an allowlist — but there is no true per-IP throttle on a public web app. - `computeHmacSha256Signature` RETURNS SIGNED BYTES (`Byte[]`, values -128..127). Don't compare raw byte arrays as strings — base64url-encode before comparing, and do a length check plus constant-time comparison to avoid timing leaks. Naive early-return string equality is the classic mistake here. - CLOCK IS SERVER-SIDE ONLY. `exp` uses `Date.now()` on the owner's script runtime — fine and trustworthy. Never trust an expiry the client sends; it's inside the signed payload precisely so the client can't move it. - "RUNS AS OWNER" ≠ IDENTITY. Deploy the web app as "execute as me (owner), access: anyone". Then `Session.getActiveUser().getEmail()` is EMPTY for anonymous visitors — you get no Google identity for free. Your magic link IS the identity; don't expect Apps Script to tell you who's calling. (If you instead deploy "execute as user accessing", every visitor must have a Google account and OAuth-consent — wrong model for a public client portal.) - OWNER CAN READ EVERYTHING. Sessions sheet, the HMAC secret in Script Properties, and all portal data live in the client-owner's own Drive. This is fine for a single-tenant portal the owner runs, but it means the backend is NOT tamper-proof or zero-knowledge — the owner can forge a session row or read the secret. Don't market this as "we can't see your data." - STATELESS TOKEN vs STATEFUL SESSION. The magic-link token is stateless (verifiable by signature alone) — good, because Cache can evict. The session sid should be stateful (looked up), so you can revoke it. If you make the session itself a bare HMAC token with no server record, you lose revocation and single-use. - CACHE IS NOT DURABLE. `CacheService` entries can be evicted before TTL and are capped (100 KB/value, 6 h max TTL). Use it as a fast layer but back sessions with the Sheet if you need them to survive; otherwise a user gets logged out unpredictably. - CONCURRENCY ON THE SHEET. Two requests appending/reading the Sessions sheet can race. Wrap writes in `LockService.getScriptLock()` with a short `waitLock`. The Sheet-as-DB also degrades past tens of thousands of rows — prune expired sessions on a time-driven trigger. - COLD START ~1-2s. First hit after idle is slow; the magic-link click may feel laggy. Not a correctness issue, but set expectations.

Quotas

- EMAIL IS THE BINDING QUOTA. `MailApp`/`GmailApp` sends are capped at ~100 recipients/day on consumer Gmail, ~1,500/day on Workspace. Every magic-link request = one email, so a consumer-account portal tops out around ~100 logins/day and is trivially exhaustible by an email-bomb attacker — hard-gate with an allowlist and per-email cooldown. Workspace's 15x headroom materially changes what's viable. - TRIGGER RUNTIME: pruning expired sessions/tokens via a time-driven trigger draws from ~90 min/day total trigger runtime (consumer) vs ~6 h (Workspace); 6 min per execution. Keep the prune job small/batched. - No UrlFetch needed for the core flow (all crypto is local via `Utilities`), so the ~20k/day UrlFetch quota is irrelevant unless you add an external email provider or captcha. - CacheService and PropertiesService reads/writes are effectively free and fast; prefer Cache for the hot session-check path to avoid Sheet read latency.

Legal / trust

- ATTRIBUTION HONESTY: you can prove "someone who controls this mailbox clicked this link at this server timestamp" — that's it. You CANNOT capture the signer's IP or user-agent on this stack, so don't claim IP-based audit trails. Attribution = verified email + server `Date.now()` + whatever the user typed. Adequate for portal access; NOT sufficient for high-assurance e-signature or KYC claims. - MAGIC LINKS ARE BEARER TOKENS: anyone who obtains the link (forwarded email, shared screen, corporate email scanner that pre-fetches URLs — a real problem, some scanners GET the link and can burn a single-use token) is authenticated. Single-use + short expiry mitigate but don't eliminate this. Warn users not to forward the link. - DATA RESIDENCY / PRIVACY: all session and client data sit in the OWNER's Google Drive under their Google account, not a third-party server — a genuine privacy selling point for agencies delivering owned backends. On Workspace Business Standard+ this can sit under a BAA (HIPAA-eligible), unlike consumer Gmail. But reiterate: owner-editable ≠ immutable; do not represent the session log as a forensic/tamper-proof record.

Recurring Triggers & Quota-Safe Batch Sending

A time-driven Apps Script cron that wakes every few minutes, processes a bounded chunk of work before the 6-minute execution wall, persists a cursor so the next run resumes where it left off, and throttles email sends against the account's live remaining daily quota so a large batch drains out over hours/days without ever tripping a limit or dropping rows.

How it works

1. PROVISION THE CRON (one-time, at setup or from doGet/doPost admin action). In the generated script, call `ScriptApp.newTrigger('drainQueue').timeBased().everyMinutes(5).create()` (choices are everyMinutes(1|5|10|15|30), everyHours(n), everyDays(n), or atHour/nearMinute). This requires the `https://www.googleapis.com/auth/script.scriptapp` scope, which the owner authorizes at install. The trigger runs AS the owner — same identity/quotas as the web app. IMPORTANT: there is no cron-string syntax; you get the fixed intervals above. A trigger can also be created dynamically at the end of a run (`ScriptApp.newTrigger(...).timeBased().after(60*1000).create()`) to self-reschedule for finer control — but you must delete stale triggers or you hit the ~20-trigger-per-script/per-user cap. 2. HOLD A DURABLE QUEUE + CURSOR. The work list (e.g. recipients to email) lives in a Sheet tab via `SpreadsheetApp` (scope `.../auth/spreadsheets`) or serialized in `PropertiesService.getScriptProperties()` (scope `.../auth/script.storage`, 500KB total / 9KB per value cap — so Sheet is the real store for anything sizable). Track a cursor: last processed row index, plus a per-row status column (`QUEUED`/`SENT`/`FAILED`/`timestamp`). PropertiesService holds the small scalar cursor (`lastRow`); the Sheet holds per-row state so a crash mid-batch is recoverable and idempotent. 3. TAKE THE LOCK. First line of `drainQueue`: `const lock = LockService.getScriptLock(); if (!lock.tryLock(1000)) return;` (scope `.../auth/script.locks`). Because a 5-min trigger can fire while the previous run is still grinding through a slow chunk, overlapping executions would double-send. The lock makes runs mutually exclusive; a run that can't grab it exits immediately and waits for the next tick. 4. READ LIVE EMAIL QUOTA. `const budget = MailApp.getRemainingDailyQuota();` returns the integer recipients still allowed today for THIS account (consumer ~100, Workspace ~1,500; it counts recipients, not messages). Scope: `.../auth/script.send_mail` (MailApp) or the broader Gmail scope if using `GmailApp`. If `budget <= 0`, release the lock and return — the queue simply resumes tomorrow when Google resets the counter. 5. CHUNK AGAINST BOTH THE TIME WALL AND THE QUOTA. Record `const start = Date.now()`. Loop over queued rows, and STOP the loop when ANY of: (a) `Date.now() - start > 4.5*60*1000` (leave a safety margin below the hard 6-min `Exception: Exceeded maximum execution time` kill — that exception is uncatchable-for-cleanup in practice, so you must bail early to flush your cursor), (b) `sentThisRun >= budget` (never exceed live remaining quota), or (c) an optional self-imposed per-run cap (e.g. 50) to stagger deliverability and keep runs short. 6. SEND + MARK ATOMICALLY-ISH. For each row: `MailApp.sendEmail({to, subject, htmlBody})` (or `GmailApp.sendEmail` if you need threading/labels/`from` send-as on Workspace). Immediately write the row's status to `SENT` + `new Date()` in the Sheet BEFORE moving on, and advance the PropertiesService cursor. Wrap each send in try/catch; on failure mark `FAILED` with the error string and continue — one bad address must not stall the whole queue. Because you commit status per-row, a mid-chunk timeout only re-attempts unsent rows. 7. FLUSH + RELEASE. After the loop: persist final cursor to PropertiesService, `SpreadsheetApp.flush()` to force pending Sheet writes to disk (Apps Script batches writes — an execution killed at 6 min can otherwise lose un-flushed changes), then `lock.releaseLock()`. The standing 5-min trigger fires again and step 3 onward repeats until no `QUEUED` rows remain. 8. (OPTIONAL) SELF-DISABLE / CLEAN UP. When the queue is empty you can leave the cron running idle (it just takes the lock, sees 0 rows, exits — cheap) or delete it via `ScriptApp.getProjectTriggers().forEach(t => { if (t.getHandlerFunction()==='drainQueue') ScriptApp.deleteTrigger(t); })` to reclaim the trigger slot. EXTERNAL APIs: if sending via a third party instead of Gmail (SendGrid/Postmark/Resend) you swap step 6 for `UrlFetchApp.fetch(apiUrl, {headers:{Authorization:'Bearer '+key}, ...})` (scope `.../auth/script.external_request`, key in PropertiesService). That escapes the Gmail 100/1500 recipient cap entirely but is bounded instead by the UrlFetch quota (~20k/day consumer, 100k Workspace) and your ESP's own rate limits — and you lose `getRemainingDailyQuota()` as your throttle signal, so you must track sends yourself in PropertiesService.

Gotchas

- THE 6-MINUTE WALL IS A HARD KILL, NOT A WARNING. When execution hits ~6 min (Workspace paid tiers historically got 30 min but Google has been normalizing this toward 6 — do not design assuming 30), the runtime throws `Exceeded maximum execution time` and stops. You cannot reliably run cleanup after it. The ONLY safe pattern is: measure elapsed time in the loop and voluntarily exit at ~4.5 min with your cursor flushed. People who "just loop over 5,000 rows" get a partial send and a lost cursor. - FLUSH OR LOSE WRITES. `Range.setValue`/`setValues` are buffered. If the run dies before an implicit or explicit `SpreadsheetApp.flush()`, the last writes vanish and rows get re-sent next tick → duplicate emails. Flush after each chunk (or per-row for max safety, at a throughput cost). - WITHOUT A LOCK YOU WILL DOUBLE-SEND. Triggers can overlap: a 5-min interval firing while the prior run is still inside its 4.5-min window means two executions read the same `QUEUED` rows. `LockService` is mandatory, not optional. - `getRemainingDailyQuota()` COUNTS RECIPIENTS, AND IT'S SHARED. The ~100/1,500 cap is per-account across ALL Apps Script + Gmail-API sending for that user that day, including CC/BCC as separate recipients. It resets on a rolling ~24h Google clock (roughly midnight Pacific), NOT at the user's local midnight. Don't assume a fresh 100 at 12:01am local. - QUOTA EXHAUSTION THROWS, it doesn't return false. If you call `sendEmail` past the limit you get `Exception: Service invoked too many times for one day: email`. Always gate on `getRemainingDailyQuota()` BEFORE sending, and treat the throw as a hard stop that ends the run gracefully. - THE ~90-MIN/DAY (consumer) / 6-HR (Workspace) TOTAL TRIGGER RUNTIME CEILING IS SEPARATE from the per-execution 6 min and from the email cap. Many short runs still accrue against it. A 5-min cron doing near-zero work barely touches it; a cron that legitimately grinds 4.5 min every 5 min will exhaust 90 min/day in ~20 runs (~100 min of wall clock) and then Google silently stops firing your triggers for the rest of the day. Size chunks so total daily compute stays well under the ceiling. - TRIGGER COUNT IS CAPPED (~20 per script per user). The self-rescheduling `.after()` pattern MUST delete the trigger that just fired or you leak slots and hit "This script has too many triggers." - NO SUB-MINUTE CRON, NO GUARANTEED PUNCTUALITY. Minimum interval is 1 minute and Google may fire a "1-minute" trigger anywhere in a fuzzy window, or skip/coalesce under load. Never build anything needing exact-time execution. - YOU CANNOT ATTRIBUTE A SEND TO A REQUEST IP/HEADER. Unrelated but often assumed: the web-app request object exposes only `e.parameter/parameters/postData/pathInfo/queryString` — no IP, headers, cookies, or UA. Any per-recipient audit trail must be built from server `new Date()` timestamps and the row data itself. - TRIGGERS DIE IF THE OWNER LOSES ACCESS / REVOKES OAUTH, and Google auto-disables triggers that error repeatedly, emailing the owner a failure summary. A queue can silently stall; build a "last successful run" heartbeat timestamp the front-end can surface.

Quotas

Email recipients/day: consumer ~100, Workspace ~1,500 (per account, all Apps Script sending combined, counts every recipient incl. CC/BCC; check live via MailApp.getRemainingDailyQuota(); resets ~midnight Pacific). Per-execution: 6 min hard kill — design to exit ~4.5 min. Total trigger runtime/day: consumer ~90 min, Workspace ~6 hr (accrues across all runs; exhaustion silently halts triggers till reset). Triggers: min 1-min interval, fuzzy timing, ~20/script/user. PropertiesService: 500KB total, 9KB/value — use a Sheet for real queues. UrlFetch (external ESP path): ~20k/day consumer, 100k Workspace, plus the ESP's own limits. Cold start ~1-2s on first run after idle. Sheets-as-queue realistically tops out in the tens of thousands of rows before scan latency and the 6-min wall bite — keep a numeric cursor, don't re-scan.

Legal / trust

Sends originate from the OWNER's own Google account and count against the owner's quota and reputation — so a batch blast can consume the client's entire day of business email and get their domain flagged; stagger and cap conservatively, and never let one tenant's queue starve the account. On consumer Gmail, all mail sends from the user's personal address (no send-as the business domain, weaker deliverability); Workspace unlocks `GmailApp` send-as an authorized domain alias plus the 15x cap, which is the honest reason to push agency clients to Workspace for any real volume. Bulk/marketing sends are subject to CAN-SPAM / CASL / GDPR obligations (accurate from-line, physical address, working unsubscribe, consent) — the platform can provide an unsubscribe link + suppression list in the Sheet, but compliance is the sending client's legal responsibility, not something Apps Script enforces. Finally: the queue Sheet lives in the owner's Drive and is owner-editable — it is a real audit log but NOT tamper-proof or immutable, so do not market it as forensic-grade proof of what was sent.

Gmail Read / Parse / Triage

A trigger-driven Apps Script routine that searches a mailbox with GmailApp, reads matching message bodies, extracts structured fields (via regex or a Claude call over UrlFetchApp) into a Sheet, and either files labels or stages Gmail drafts for human review — all running as the mailbox owner in their own Google account.

How it works

1) SCOPE DECLARATION & CONSENT. The generated script's OAuth manifest (appsscript.json oauthScopes, or auto-inferred from the methods it calls) declares https://www.googleapis.com/auth/gmail.readonly OR the broader https://mail.google.com/ / gmail.modify. GmailApp is coarse: reading a body needs at least gmail.readonly, and gmail.readonly grants read of the ENTIRE mailbox — there is no folder/label-scoped grant. Creating drafts or applying labels needs gmail.modify (or the full mailbox scope). On the OAuth consent screen the user sees 'Read, compose, send, and permanently delete all your email from Gmail' (or 'Read all your email') — these are RESTRICTED scopes: an unverified app is capped at ~100 users and shows a scary warning; going past that requires Google's annual CASA/security assessment (paid, thousands of dollars). This is the single biggest wedge and cost of this primitive. 2) TRIGGER. A time-driven trigger (ScriptApp.newTrigger('triageInbox').timeBased().everyMinutes(5).create(), free cron) fires the run; there is NO native 'on new email' push trigger in Apps Script for arbitrary scripts, so you POLL. (Gmail push/watch via the Advanced Gmail Service + Pub/Sub exists but is heavy and rarely worth it here.) 3) SEARCH. GmailApp.search('label:inbox is:unread newer_than:1d subject:invoice', 0, 50) returns GmailThread[] using standard Gmail query syntax. Page with the start/max args; cap max at ~50-100 threads per run to stay under the 6-min execution limit. Persist a cursor (last processed internalDate or a processed-label) in PropertiesService so each run only touches new mail and you don't reprocess. 4) READ BODIES. For each GmailThread call .getMessages(); per GmailMessage use .getPlainBody() (prefer over .getBody() which is raw HTML), .getFrom(), .getSubject(), .getDate() (server-side Date), .getId(), and .getAttachments() -> Blob for PDFs/images. Note getPlainBody can be empty on HTML-only mail; fall back to stripping .getBody(). 5) EXTRACT. Two paths: (a) REGEX/string parse in-script for cheap, deterministic fields (order numbers, amounts, dates) — no external call, no cost, no data leaves Google; (b) LLM parse via UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', {method:'post', headers:{'x-api-key': PropertiesService.getScriptProperties().getProperty('ANTHROPIC_KEY'), 'anthropic-version':'2023-06-01'}, contentType:'application/json', payload: JSON.stringify({model, max_tokens, messages:[{role:'user', content: emailBody + extraction-instructions}]})}) — send the body, ask for JSON, JSON.parse the returned text. The API key lives in PropertiesService (Script Properties), never in code. This ships raw email content to a third-party API — a real trust/compliance event (see legal notes). 6) WRITE TO SHEET. Open the bound/owned sheet with SpreadsheetApp, collect rows in an array, and do ONE sheet.getRange(...).setValues(rows) batch write per run (never setValue in a loop — it's orders of magnitude slower and burns the time budget). Store the Gmail message getId() so you can dedupe and deep-link back (https://mail.google.com/mail/u/0/#all/<id>). 7) ACT — DRAFT vs SEND vs LABEL. To triage without touching the send cap: GmailApp.createDraft(recipient, subject, body) or message.createDraftReply(body) / thread.createDraftReply(body) — DRAFTS DO NOT COUNT against the daily send quota and land in the owner's Drafts for human review/one-click send. To file: thread.addLabel(GmailApp.getUserLabelByName('Triaged') || GmailApp.createLabel('Triaged')), thread.markRead(), thread.moveToArchive(). Only GmailApp.sendEmail / message.reply / draft.send actually SEND and consume the send quota.

Gotchas

- NO LABEL-SCOPED READ: gmail.readonly / gmail.modify grant the WHOLE mailbox. You cannot restrict the grant to one label or one sender; the app is technically able to read everything the moment it's authorized. Do not claim otherwise to a client. - RESTRICTED-SCOPE VERIFICATION WALL: these are the highest-sensitivity Gmail scopes. Unverified = ~100-user cap + interstitial warning; scaling requires Google verification AND an annual CASA Tier-2 security assessment (real money, real time). Because THIS stack provisions into the END USER's OWN account and the script runs AS them, each user is authorizing their own copy — that sidesteps the aggregate-user verification wall in the common self-provisioned case, but the user still sees the full scary consent screen and any shared/central OAuth client is still subject to it. Be precise about which deployment model you're in. - NO PUSH TRIGGER: you poll on a timer; there's inherent latency (whatever your trigger interval is) and you MUST track a cursor or you'll reprocess/duplicate. 'onNewEmail' does not exist for plain Apps Script. - getPlainBody() can be empty for HTML-only senders; .getBody() is HTML you must strip. Quoted-reply text and signatures pollute regex and LLM extraction — trim before parsing. - 6-MINUTE EXECUTION LIMIT (consumer): a large backlog + per-email UrlFetch to Claude will blow the limit. Batch small (e.g. 20-50 threads/run), let the timer catch up over successive runs, and make the run idempotent (label-as-processed) so a mid-run timeout doesn't corrupt state. - Sheet writes: batch setValues once; per-cell writes will time you out. - The mailbox is the owner's live inbox — markRead/archive/label mutate their real mail. Test on a label subset; a bad query is destructive to their workflow. - 'Capture the sender's IP/headers' is impossible here: this is READING existing Gmail messages, and even the raw RFC822 (message.getRawContent()) only gives you the headers the SENDER's path already wrote — you cannot observe anything Gmail didn't record. And the web-app request object never exposes client IP/headers regardless. - LLM extraction is non-deterministic: pin the model, force JSON output, and validate/JSON.parse defensively — a malformed response should skip the row, not crash the whole run.

Quotas

- Trigger runtime: ~90 min/day total across all triggers (consumer) vs ~6 hr (Workspace); 6 min per single execution (both). Polling every 5 min = 288 runs/day, so keep each run well under a minute of work. - UrlFetch to Claude: ~20,000 calls/day consumer, 100,000 Workspace. One call per email is the natural unit — a busy inbox can approach the consumer ceiling; batch multiple emails into one Claude call to conserve. - SEND cap only matters if you actually send: ~100 recipients/day consumer, ~1,500 Workspace. DRAFTS created via createDraft/createDraftReply are FREE — they never touch the send quota, which is exactly why draft-and-review is the safe default for this primitive. - Sheet as DB: fine for tens of thousands of extracted rows; roll to a new sheet/tab or archive before that. - Anthropic API is your own metered cost (tokens), entirely separate from Google quotas.

Legal / trust

- Trust cost is the headline: authorizing this app means granting an app the ability to read (and with modify, alter) the person's ENTIRE Gmail. For an SMB client that's their whole business correspondence. The honest framing to a client is 'this runs inside YOUR Google account, as YOU, and the code is yours' — true and reassuring — but the consent screen still says 'read all your email'. - Sending email bodies to the Claude API (step 5b) exfiltrates potentially sensitive/PII/PHI content to a third party. On consumer Gmail that may violate the client's own obligations. On Workspace with HIPAA in scope, you need a Google BAA (Business Standard+) AND a BAA with Anthropic before any PHI touches the API — otherwise keep extraction to in-script regex only, where nothing leaves Google. - The Sheet of extracted data lives in the owner's Drive and is owner-editable — do NOT market it as immutable, forensic, or tamper-proof. - Google's Limited Use / restricted-scope policy governs Gmail data: you generally cannot use the mailbox content to train models or for unrelated purposes, and human review of Gmail data is tightly restricted — relevant if any support/debugging path could expose a client's email to your team.

Workspace Admin SDK automation via Apps Script

A Workspace-only Apps Script backend that calls the Admin SDK Directory and Reports APIs (as advanced services) to programmatically create/suspend users, manage group membership, reclaim licenses, and pull audit logs — running as an authorizing super-admin, which is why it is categorically impossible in any consumer (@gmail.com) Google account.

How it works

1. PROVISION AS AN ADMIN, NOT A REGULAR USER. The generated Apps Script project must be created/owned by (or the web app authorized by) a Google Workspace user who holds a super-admin role (or a custom admin role scoped to the specific privileges: User Management, Groups, Reports, etc.). Consumer Google accounts have no Admin Console, no org units, no domain — so the Admin SDK simply does not exist for them. This is not a quota difference; the API returns no domain to operate on. 2. ENABLE THE ADVANCED SERVICE + UNDERLYING API. In the Apps Script project, enable the "Admin SDK API" advanced service (this exposes the `AdminDirectory` and `AdminReports` globals). Because advanced services are thin wrappers over the REST APIs, the corresponding APIs must also be enabled in the linked Google Cloud project (Admin SDK API). For a standalone-provisioned script this means the script is bound to a real GCP project, not the default hidden one. 3. DECLARE THE EXACT OAUTH SCOPES in appsscript.json `oauthScopes`. Directory writes: `https://www.googleapis.com/auth/admin.directory.user` (create/suspend/delete users, reset passwords), `https://www.googleapis.com/auth/admin.directory.group` and `.../admin.directory.group.member` (group + membership management), `https://www.googleapis.com/auth/admin.directory.orgunit` (move users between OUs), `https://www.googleapis.com/auth/admin.directory.user.security` (revoke tokens/app passwords). License reclaim needs the separate Enterprise License Manager API scope `https://www.googleapis.com/auth/apps.licensing` via `AdminLicenseManager` (or UrlFetch to the Licensing REST endpoint). Audit pull: `https://www.googleapis.com/auth/admin.reports.audit.readonly` and `.../admin.reports.usage.readonly`. 4. ADMIN CONSENTS. When the admin first runs the script (or the web app on their behalf), the OAuth consent screen lists these high-privilege scopes; the admin must authorize. The token that results carries admin authority — everything the script does thereafter runs AS that admin. On restricted/sensitive scopes, the Cloud project usually needs to be verified or the app marked Internal (published to the same Workspace) to avoid the "unverified app" block. 5. DIRECTORY WRITES. Create a user: `AdminDirectory.Users.insert({primaryEmail, name:{givenName,familyName}, password, changePasswordAtNextLogin:true})`. Suspend/offboard: `AdminDirectory.Users.update({suspended:true}, email)`. Move OU: same `update` with `orgUnitPath`. Add to group: `AdminDirectory.Members.insert({email, role:'MEMBER'}, groupKey)`. All are synchronous REST round-trips wrapped in `UrlFetch`-style calls under the hood; each counts against Directory quotas. 6. LICENSE RECLAIM. On offboarding, after suspend, call `AdminLicenseManager.LicenseAssignments.remove(productId, skuId, userId)` to free the paid seat (or the equivalent Licensing REST DELETE via `UrlFetchApp`). Suspending a user does NOT auto-free the SKU — you must explicitly delete the license assignment. 7. AUDIT / REPORTS PULL. `AdminReports.Activities.list(userKey='all', applicationName='login'|'drive'|'admin'|'token', {startTime, maxResults})` returns paginated activity events; `AdminReports.CustomerUsageReports.get(date)` returns aggregate usage. Reports are eventually consistent — Google documents ingestion lag (login events ~hours, some apps up to ~2-3 days), so "real-time audit" is a lie on this API. 8. TRIGGER THE WORK. Wire it as either (a) a public web app `doPost` that an internal admin dashboard (your static front-end) calls with a shared secret in PropertiesService — but note the web app exposes no client IP/headers, so authenticate the caller by a secret token in `e.parameter`/`e.postData`, not by network identity — or (b) a time-driven trigger (`ScriptApp.newTrigger('nightlyReclaim').timeBased().everyHours(6)`) that runs offboarding/reclaim/audit-sync on Google's free cron. Store the admin's authorization by having the admin be the trigger owner. Secrets (shared API token, Cloud project creds) live in `PropertiesService.getScriptProperties()`.

Gotchas

- IMPOSSIBLE ON CONSUMER, FULL STOP. No Admin Console, no domain, no OU, no Admin SDK. There is no partial/degraded version — a @gmail.com account cannot create users or read a directory of anyone but itself. Do not design a "works on consumer, better on Workspace" story here; it is Workspace-exclusive. - THE SCRIPT RUNS AS A SUPER-ADMIN — THIS IS THE WHOLE RISK. A public `doPost` web app that wraps Directory.Users.insert is, functionally, a user-creation and account-takeover endpoint exposed to the internet. Apps Script gives you NO native rate limiting and NO IP allowlist on a public web app. If the shared secret leaks (it will end up in your static front-end's network calls / browser), an attacker can create admin users or suspend the CEO. You MUST: keep the endpoint access-controlled at the token layer, prefer time-driven triggers over public web endpoints for destructive ops, and scope the authorizing admin to a CUSTOM admin role with only the needed privileges rather than full super-admin. - CUSTOM ADMIN ROLE, NOT SUPER-ADMIN. Naive builders authorize with a super-admin. Correct design: create a custom admin role granting only User Management / Groups / Reports as needed, assign it to a dedicated service-owner account, and have THAT account own the script. Blast radius shrinks accordingly. - "RUN AS OWNER" MEANS ONE HARDCODED IDENTITY. A web-app-deployed script executes as its owner, not the caller. So multi-admin delegation, per-admin audit trails, and "who actually clicked suspend" are NOT captured by Google's admin audit as distinct humans — the Admin console will show the script-owner account doing everything. You lose per-operator attribution unless you log the calling operator yourself (and that self-log is owner-editable, so not tamper-proof). - DOMAIN-WIDE DELEGATION IS A DIFFERENT, HEAVIER MECHANISM. If you need to impersonate arbitrary users (act as them, not just administer them), that requires a service account with domain-wide delegation configured in the Admin Console — which Apps Script advanced services do NOT give you by default; you'd hand-roll JWT auth via `UrlFetchApp`. Admin SDK administration ≠ user impersonation. - CONSISTENCY/PROPAGATION LAG. Directory writes are not instantly globally consistent; a user just created may 404 on immediate read. Reports API lags hours-to-days. Build for eventual consistency; don't assert real-time. - 6-MINUTE EXECUTION CEILING. Bulk operations (onboard 500 users, page through months of audit logs) will blow the 6-min/execution limit. You must checkpoint progress in PropertiesService and continue on the next trigger fire; a single run cannot process a large directory. - ENABLING THE API IS TWO STEPS. Turning on the advanced service in the editor is not enough on a standalone-provisioned project — the bound GCP project must also have the Admin SDK / Licensing APIs enabled, or calls fail with a permission error that looks like a scope problem but isn't.

Quotas

- Admin SDK Directory API default quota is on the order of ~2,400 queries/min/account (and a daily cap) — generous for SMB directories but a bulk backfill paging thousands of users can still throttle; implement exponential backoff on 403 rateLimitExceeded / 429. - Reports API has its own lower per-project/per-user query limits and, more importantly, DATA LATENCY (login events surface in hours; some app activity up to ~2-3 days) — this is a freshness constraint, not just a rate one. - Apps Script platform limits still apply on top: 6 min per execution, ~90 min/day total trigger runtime on consumer but this is Workspace so ~6 hr/day, and UrlFetch ~100k/day on Workspace if you bypass advanced services and hit REST directly. - License reclaim (Licensing API) has modest write quotas; batch offboarding of many seats should be spread across trigger runs.

Legal / trust

- This primitive hands your generated code the keys to the client's entire org: creating users, suspending accounts, reading who-did-what audit logs. That is a materially different trust posture than a Sheet-backed form. Sell it as such and get explicit admin sign-off on the exact privileges. - Admin Console audit will attribute all script actions to the single script-owner identity, so YOUR change log is the only per-operator record — and because it lives in the owner's own Drive/Sheet it is owner-editable and therefore NOT forensic/immutable. Do not market it as a tamper-proof audit trail. - On Business Standard+ this can fall under a signed BAA if it touches PHI in Reports/Directory data; confirm the specific SKU is HIPAA-eligible and that Admin SDK usage is in scope before making any compliance claim. - Least-privilege is a legal/trust obligation here, not a nicety: authorize via a scoped custom admin role, store the shared secret only in PropertiesService, and never let the destructive endpoint be reachable with a secret that ships to the browser. A leaked token on this primitive is an org-compromise incident.