Lead tracking on the reps' phones, backed by a spreadsheet.

Dax Distro bought leads: five to ten smoke shops a day, each arriving as an email in a shared inbox. A few reps worked them by phone. Nobody could say who had called which shop, how many times, or what came of it, because the answer lived in the reps' text messages. I built a phone app on a Google Sheet where each rep sees only their own leads, one tap logs a call and dials it, and the office hands out new leads from a spreadsheet tab with a menu click.

Dax Distro · Feb–Apr 2026 · AppSheet · Google Sheets · Apps Script · retired with the company · ← all work

The problem

Leads came in as emails and went out as forwards. A rep's notes were in their phone, a rep's call count was a guess, and a lead that got no call for a week was invisible until somebody asked. Sharing one spreadsheet with everyone would have shown every rep every other rep's shops. Keeping a spreadsheet per rep meant the office had no single picture.

What I built

A phone app built with AppSheet over one Google Sheet. A rep signs in with their company Google account and sees only the leads assigned to them. Each lead has three buttons: Call, Email, Text. Each writes a timestamped activity row and then opens the phone's dialer, mail, or messages. A note is three taps. Status moves itself on the first contact. New leads go in on a staging tab in the sheet and a menu click pushes them to the reps with an email each.

What changed

"Who's called this shop" became a number on the card. The office saw calls, emails, and texts per rep on a chart without asking anyone. A new rep was one row in a roster tab and a share; the app didn't need editing. And after a stray column insert broke the app for everyone once, a guard in the sheet started raising the alarm the moment a tab's shape changed.

The rep's phone

The app opens on a deck of the rep's leads, newest on top, each card carrying the shop, its status, and how many times it has been contacted. Tap one and the buttons are the first thing on the screen. The drawing below is reconstructed from the app's configuration: the app lived in the company's Google Workspace, which was closed with the company, so there is no screenshot to show.

Leads Sam · 4 Northside Smoke & Vape ATTEMPTING CONTACT (512) 555-0134 · Austin 2 calls · 0 emails · 1 text Cedar Creek Wellness NEW (713) 555-0188 · Houston no contact yet Lone Star Glass Co. SAMPLES SENT (214) 555-0102 · Dallas 3 calls · 1 email · 0 texts Bayou Botanicals ATTEMPTING CONTACT (409) 555-0166 · Beaumont 1 call · 0 emails · 0 texts Leads Map Table Activity ‹ Leads Northside Smoke & Vape ATTEMPTING CONTACT 📞 Call & Log ✉ Email & Log 💬 Text & Log + Note SHOP PHONE (512) 555-0134 ADDRESS 2200 Guadalupe St, Austin MANAGER · CURRENTLY SELLING? Marisol Vega · Yes · 2 lbs / order ACTIVITY (3) 📞 Call · Mar 12, 10:41 · Sam 💬 Text · Mar 12, 10:44 · Sam 📞 Call · Mar 14, 09:15 · Sam NOTES (1) Mar 14 · owner in Tue/Thu, wants two samples first Leads Map Table Activity what the sheet decides the rep sees only rows where Sales Rep is theirs. The roster tab says who is a rep and who is an admin. one tap does two things, in order: write a row to the Activity Log tab (lead, rep, Call, timestamp), then open the phone's dialer on the number. the counts on each card are formulas over that log. The first contact moves status from NEW to Attempting Contact by itself; Samples Sent, Won, Lost are a tap on the chip. a note is three taps: + Note, type, save. Each one is its own row, so the office can read who said what, when. drawn from the app's configuration; not a screenshot

Left: the deck a rep opens to. Right: one lead. Every number and chip on the left is computed from rows the buttons on the right wrote.

Rules the sheet enforced

How a lead moved

Two versions, one sheet. The first, in February, was a parser that read the vendor's lead emails into a master sheet every fifteen minutes and kept a spreadsheet per rep in sync both ways, with the sheet owning the lead's details and the rep owning status and notes. Then the reps asked for it on their phones, and the second version replaced the per-rep spreadsheets with the app and gave the office a staging tab and a button.

Lead vendor 5–10 emails a day into a shared inbox shop · phone · address manager · what they sell FIRST VERSION · FEB 2026 Python parser Gmail API, every 15 min regex on the vendor's format Master sheet owns the lead's details and who it's assigned to A spreadsheet per rep rep owns status, contacts, notes; synced both ways replaced by the phone app below SECOND VERSION · MAR 2026 ONWARD New Lead Entry a staging tab the office types leads in, picks the rep from a dropdown of assignable reps Push Leads ▸ (a menu) Apps Script in the sheet: assign IDs, fill the dates, append to Leads, email each rep a "View in App" link, clear the staging tab The Google Sheet Leads · Reps Activity Log · Notes RepStats · New Lead Entry one source of truth, the office can open it The app, on a phone AppSheet, company sign-in rows filtered by the rep's email Call · Email · Text, each logged notes · status · map of shops reads and writes the sheet directly Activity Log · Notes · Status a row per tap, with who and when RepStats → a chart calls, emails, texts per rep; admins see it what was tried and dropped: the AppSheet API (needs a pricier plan), push notifications (only fire for taps in the app), deep links to one record (unreliable on phones), and a browser robot that edited the app to add a rep (made unnecessary by the roster lookup)

The sheet in the middle is the same one throughout. The first version put the reps in spreadsheets of their own; the second put the sheet on their phones and the office on a button.

What didn't work, and what replaced it

Most of the second version's design came from things that failed on the way. AppSheet's own API would have let the script hand leads to the app directly, but it needs a plan tier the company's Google Workspace policy blocked, so the script writes the sheet and sends its own email instead. AppSheet's push notifications fire only for rows added inside the app, never for rows a script writes, so the email with a link into the app became the notification. Deep links to a single record opened the wrong screen on phones often enough to be dropped for a link to the deck sorted newest first. And an early tool I wrote to add a rep, a browser robot that clicked through the AppSheet editor and rewrote six expressions, was deleted the day the security filters were rewritten to look the rep up in the roster tab, because from then on adding a rep needed no edit at all.

Where it stands

Dax Distro wound down in July 2026 and the app, its sheet, and the Workspace it lived in went with it. The scripts, the setup guide, and the app's full configuration are archived, which is what the drawings on this page are built from. The shops, reps, and numbers in them are invented.

The reps' other tool from the same months: the order portal, where a won lead became an order.

Stack

AppSheet Google Sheets Google Apps Script Gmail (MailApp) Python · Gmail API · Sheets API (v1) Playwright (retired)

No-code was the right tool because the users were on phones, the data was already a spreadsheet, and the hard part was never the screens. It was deciding who sees what, and putting that decision in one place the office could edit.

Built in conversation

Same method as everything on this site. The security filters, the grouped actions that log and then dial, the staging-tab script, the schema guard, and the decision to delete the browser robot all came out of dialogue with Claude, with the reps trying each change on their phones between rounds.

The philosophy, or what I'm building right now.