Overview
sheetdesk is what a small marketing agency could run out of one Google Sheet. A custom menu and a sidebar sit in the Sheet, each client gets a monthly summary tab, a report PDF is built from a Docs template and emailed, leads arrive through a web app webhook, and a cleanup job works through 50,000 rows without hitting Google's six minute limit. The agency is a sample one, with a Sheet of sample clients, campaigns and leads.
Key Features
- A menu and sidebar. Open desk, Close a month, Rebuild summaries and Set up from a custom menu. The sidebar picks a client and a month and has a Send to me first tick, on by default, so a report goes to the person running it before it goes to the client.
- Monthly summaries and report PDFs. Spend, clicks, conversions, cost per click and cost per conversion by channel, built per client, then a one page PDF from a Docs template sent through MailApp.
- A lead webhook.
doPostchecks a shared secret, refuses bad JSON and unknown fields, keeps text that looks like a formula as text and writes one row per lead id under a script lock, so a retry never makes a second row. - A big job that continues itself. A 50,000 row clean, convert and roll up job works in slices and sets a time trigger to carry on, with the trigger cleaned up at the end.
- Intake and rates. A form intake trigger adds clients, and exchange rates come from a documented API shape.
Technical Achievements
The repo has 228 tests that run against a stand-in of Google's services written from Google's reference pages, and a mutation script that breaks one guard at a time and requires a test to fail: 143 of 143 were caught. A review round found real faults, each reproduced before it was fixed. Gmail's send method is documented as needing the whole mailbox scope, so both send sites use MailApp with a narrower one. Month text such as 2026-10 was read by Sheets as a date, which broke the report log check. And the web app could return an HTML error page where the docs promised JSON.
The code was then deployed with clasp to a real Google account and driven by hand with the sample data. The menu appeared, Set up asked for permission and built the tabs, Rebuild summaries built a tab for each client, the sidebar rendered in Google's own iframe, and Run report produced a PDF that arrived by email with figures that add up. The deployed web app answered a good lead, a duplicate, a wrong secret, a formula typed as a name, an unknown field and a dashboard request. The run is written up in the repo with pictures.
What I Learned
A stand-in for someone else's service is only as good as its sources, and the real run found what the stand-in could not. Loading phone numbers that start with a plus sign through the Sheets API turned them into error cells, so the import step now says to switch value conversion off.
Limits
The menu, summaries, emailed report and web app ran on a real Google account. Month close, the form trigger and the 50,000 row job ran in the test suite against the stand-in. The ad platform is a sample one, and a Looker Studio (Data Studio) report on the same Sheet, built by hand in the editor, shows spend over time and a table of conversions, clicks and spend by client: https://datastudio.google.com/reporting/e3ad4a80-d1d2-47cb-bdfd-20df33d35874
Tech Stack
Google Apps Script, clasp, Google Sheets, Docs, Drive, Gmail and Forms, JavaScript, Node, ESLint, GitHub Actions.