Google Sheets + Apps Script automation kit

SheetFlow turns a spreadsheet into a small-business back office.

A lead pipeline with reminders, one-click invoice PDFs with Gmail drafts, weekly KPIs and form routing, all inside the Google Sheet the team already uses. The business logic is in typed, tested TypeScript.

Apps Script V8TypeScript (strict)esbuild + claspVitestSheets / Docs / Drive / Gmail
40leads in the pipeline
18follow-ups due today
4invoices generated from 9 orders
2duplicate leads caught

What it automates

Four workflows that come up in most small businesses. Each one can be used on its own.

Lead CRM

Status dropdowns with an enforced pipeline, automatic timestamps, duplicate detection (email, phone, name + company) and business-day follow-up reminders.

Invoice generator

Groups orders per customer, numbers invoices (INV-YYYY-NNNN), handles discounts and tax, renders a PDF from a Docs template and creates a Gmail draft.

Weekly KPIs

Live formulas (not pasted values) for new, won and lost leads, invoiced revenue and win rate, plus a chart that refreshes every Monday.

Form intake

Google Form answers are routed to the right tab by category and cleaned up; sales enquiries become leads automatically.

The Leads sheet and the SheetFlow menu

Coloured status chips, overdue follow-ups in red and flagged duplicates. Everything in the menu is plain TypeScript compiled to Apps Script.

Copperleaf Landscaping - OperationsGoogle Sheets
Leads sheet, most recent 12 rows
ANameBCompanyCEmailDSourceEStatusFLast ContactGNext Follow-upHDuplicate Of
2Jon BergPine Street Bakeryjon.berg@pinestreetbakery.exampleWalk-inLost2026-11-04
3Quinn TanakaOak & Iron Gymquinn.tanaka@oakirongym.exampleInstagramQualified2026-11-042026-11-06
4Frank ReyesPine Street Bakeryfrank.reyes@pinestreetbakery.exampleInstagramQualified2026-11-062026-11-10
5Frank MoreauCedar Booksfrank.moreau@cedarbooks.exampleReferralProposal2026-11-012026-11-05
6Marco TanakaCedar Booksmarco.tanaka@cedarbooks.exampleWebsiteProposal2026-11-042026-11-10
7Omar PatelRiverside HOAomar.patel@riversidehoa.exampleWebsiteQualified2026-11-072026-11-10
8Omar TanakaSummit Realtyomar.tanaka@summitrealty.exampleWebsiteNew2026-11-052026-11-06
9Grace BergSummit Realtygrace.berg@summitrealty.exampleWebsiteNew2026-11-062026-11-09
10Marco CostaMaple Cafemarco.costa@maplecafe.exampleGoogle AdsContacted2026-11-112026-11-16
11Frank PatelOak & Iron Gymfrank.patel@oakirongym.exampleReferralContacted2026-11-112026-11-16
12Omar PatelRiverside HOAomar.patel+web@riversidehoa.exampleFormNew2026-11-112026-11-12Row 35 (email)
13Jon WalshBirch Dental-InstagramNew2026-11-112026-11-12Row 29 (phone)

Pipeline rules

Invalid moves (for example New to Won) are reverted by onEdit, with a toast that explains why. Follow-ups skip weekends and holidays.

StatusCan move toFollow-up after (business days)
NewContacted Lost1
ContactedQualified Lost3
QualifiedProposal Lost2
ProposalWon Lost Qualified4
Wonfinal-
LostContacted-

From orders to invoice PDF and Gmail draft

"Invoices > Generate from Orders" groups un-invoiced rows by customer, numbers them, renders the PDF and writes the invoice number back to each order. Emails are created as drafts, so nothing is sent without a human check.

Orders11 rows · tax 8.25%
Orders sheet with invoice numbers written back
AOrder IDBDateCCustomerDItemEQtyFUnit PriceGDiscountHTaxableIInvoice #
2ORD-1012026-10-30Maple CafeWeekly lawn mowing (4 visits)1$180.00YesINV-2026-0041
3ORD-1022026-10-30Maple CafeHedge trimming1$95.00YesINV-2026-0041
4ORD-1032026-11-02Birch DentalSpring cleanup package1$320.00YesINV-2026-0042
5ORD-1042026-11-02Birch DentalMulch, premium (cubic yard)6$48.50YesINV-2026-0042
6ORD-1052026-11-03Birch DentalTree pruning (per tree)2$135.00YesINV-2026-0042
7ORD-1062026-11-04Riverside HOAWeekly lawn mowing (4 visits)3$180.0010%YesINV-2026-0043
8ORD-1072026-11-05Riverside HOAFall leaf removal2$240.0010%YesINV-2026-0043
9ORD-1082026-11-05Riverside HOAIrrigation repair, labor (hours)3.5$72.00NoINV-2026-0043
10ORD-1092026-11-07Summit RealtyHedge trimming2$95.00YesINV-2026-0044
11ORD-1102026-11-07Summit RealtyMulch, premium (cubic yard)4$48.50YesINV-2026-0044
12ORD-1112026-11-09Harbor YogaFall leaf removal1$240.00YesINV-2026-0045
Copperleaf LandscapingFictional company
INVOICE
Bill toRiverside HOAboard@riversidehoa.example
Invoice #
INV-2026-0043
Issued
2026-11-11
Due
2026-11-25
DescriptionQtyUnitDisc.Amount
Weekly lawn mowing (4 visits)3$180.0010%$486.00
Fall leaf removal2$240.0010%$432.00
Irrigation repair, labor (hours) *3.5$72.00-$252.00
Subtotal
$1,272.00
Discount
-$102.00
Tax 8.25% on $918.00
$75.74
Total due
$1,245.74

* Labor is not taxable. Line amounts are rounded per line; tax is computed once on the taxable subtotal (half-up), so the document always adds up.

Why the totals always reconcile

  • Money is integer cents end to end; floating-point dollars never reach the totals.
  • Line amount = quantity x unit price - discount, rounded half-up per line.
  • Tax is computed once, on taxable lines only.
  • Numbering continues the year's sequence and ignores hand-typed junk; it restarts every January.
  • A document lock prevents double invoices if someone clicks twice.

Open the generated PDF

Weekly KPI summary

The KPI tab holds formulas such as =COUNTIFS(Leads!F:F,"Won",Leads!I:I,">="&A2, ...), so it stays correct when someone edits data by hand. A time-driven trigger rebuilds the chart every Monday.

New leadsWonLost
Leads per week: new, won and lost over the last 8 weeks0246Week of 2026-09-21: 6 new leadsWeek of 2026-09-21: 1 wonWeek of 2026-09-21: 0 lost09-21Week of 2026-09-28: 4 new leadsWeek of 2026-09-28: 3 wonWeek of 2026-09-28: 1 lost09-28Week of 2026-10-05: 5 new leadsWeek of 2026-10-05: 1 wonWeek of 2026-10-05: 3 lost10-05Week of 2026-10-12: 4 new leadsWeek of 2026-10-12: 1 wonWeek of 2026-10-12: 2 lost10-12Week of 2026-10-19: 6 new leadsWeek of 2026-10-19: 3 wonWeek of 2026-10-19: 0 lost10-19Week of 2026-10-26: 6 new leadsWeek of 2026-10-26: 1 wonWeek of 2026-10-26: 1 lost10-26Week of 2026-11-02: 4 new leadsWeek of 2026-11-02: 0 wonWeek of 2026-11-02: 1 lost11-02Week of 2026-11-09: 3 new leadsWeek of 2026-11-09: 0 wonWeek of 2026-11-09: 0 lost11-09
Weekly KPIs table
AWeek ofBNewCWonDLostEInvoicedFWin rate
22026-09-21610$0.00100%
32026-09-28431$412.8075%
42026-10-05513$0.0025%
52026-10-12412$259.8033.3%
62026-10-19630$0.00100%
72026-10-26611$0.0050%
82026-11-02401$297.690%
92026-11-09300$2,874.900%

Pipeline today: New 4 · Contacted 2 · Qualified 6 · Proposal 10 · Won 10 · Lost 8

Form intake routing

An installable onFormSubmit trigger sends each response to a tab by the answer to "What do you need help with?". Answers are trimmed, capped, and protected against formula injection.

Category answerDestination tab
New project, Quote request, EstimateIntake - Sales
Existing project, Support, WarrantyIntake - Support
Billing, Invoice question, PaymentIntake - Billing
anything elseIntake - Other

Architecture

Business rules never touch Google globals (a lint rule enforces this), so they run and are unit-tested in Node. Thin adapters do the Sheets, Docs, Drive and Gmail calls.

Google Workspace

  • Sheets (data, menu, toasts)
  • Forms (intake)
  • Docs template + Drive (PDF)
  • Gmail (drafts only)
  • Triggers (onEdit, daily, weekly)

src/gas: adapters

  • setup.ts: tabs, validation, formats
  • crm.ts: onEdit, duplicates, digest
  • invoices.ts: template, PDF, drafts
  • automation.ts: KPIs, form routing, triggers

src/core: pure TypeScript

  • money, invoice (totals, numbering)
  • leads (pipeline, duplicates, reminders)
  • kpi, intake, rows/CSV, schema, menu
  • Tested with Vitest
src/**/*.tstsc --noEmitesbuild (IIFE + global wrappers)dist/Code.js + appsscript.jsonclasp push

Triggers and permissions

TriggerRunsDoes
onOpen (simple)When the sheet opensAdds the SheetFlow menu
onEdit (simple)On every editPipeline rules and timestamps on the Leads tab
dailyFollowUpDigestDaily, 8amUpdates follow-up dates and drafts a digest email
refreshKpisMondays, 7amRebuilds KPI formulas and the chart
onFormSubmitRouteOn form submitRoutes answers and creates leads

Scopes are kept narrow: spreadsheets.currentonly, documents, drive.file (only files SheetFlow creates), gmail.compose (drafts, not send), script.scriptapp and script.container.ui.

How I'd set this up for you

  1. 30-minute call

    We map your current sheet, statuses, tax rules, invoice look and who needs reminders.

  2. Adapt the schema

    Columns, dropdowns and pipeline rules live in one schema file, so I change them there and the tests confirm nothing else broke.

  3. Install in your Google account

    I push the script to a copy of your sheet with clasp. You approve the permissions; I never need your password.

  4. Your branded invoice template

    Logo, fonts and payment details go in the Docs template. We generate a test batch together and check every number.

  5. Hand-off

    A short video walkthrough, a README for your team, and triggers switched on. Typical turnaround: 3 to 5 days.

A taste of the code

// src/core/invoice.ts: pure, tested, no Google APIs
export function computeTotals(lines: InvoiceLine[], taxRatePct: number): InvoiceTotals {
  let subtotal = 0, net = 0, taxableBase = 0;
  for (const line of lines) {
    const { gross, amount } = lineAmount(line.quantity, line.unitPrice, line.discountPct);
    subtotal += gross;
    net += amount;
    if (line.taxable) taxableBase += amount;
  }
  const tax = roundHalfUp((taxableBase * taxRatePct) / 100);
  return { subtotal, discount: subtotal - net, taxableBase, tax, total: net + tax };
}