Case Study 06 · Marketing Operations
Three linked automation systems that route, screen, and track free trial class leads end to end. Built entirely from Meta Lead Ads, Activepieces, Airtable, and Discord, with no new tools, no database, and no code steps outside what those four platforms already offer.
The Problem
Free trial classes are a tutoring company's highest-volume acquisition offer: a parent sees a Meta ad, fills an instant form, and a sales caller phones to confirm the booking. Before automation, a human had to notice the lead, decide who should call it, check whether the family had already been through a trial before, and log everything by hand. Four specific failure modes kept recurring.
Before
After
The Lead Journey
The constraint on any solution was strict: the team runs on Activepieces, Airtable, and Discord, with no appetite for new tools, new subscriptions, or a database. Everything below is built from flow triggers, router branches, and small JavaScript code steps calling the Airtable and Discord APIs directly.
01
Capture
02
Screen
03
Assign
04
Notify
05
Record
06
Complete
System 1
Leads should alternate between callers automatically, so each new signup is owned within seconds, workload splits fairly, and every CRM record carries an accountable owner. The hard part: automation platforms run each flow execution in isolation. A code step has no memory of previous runs, so it cannot know whose turn it is. The textbook answer is external state, a database or key-value store, exactly the kind of extra infrastructure the team wanted to avoid.
The solution uses Airtable itself as the shared state. A small config table holds one row per lead funnel, and each row has a plain Number field called Counter. The rotation step reads the counter, picks the caller by modulo arithmetic, and increments the counter for the next run.
// pick the caller by modulo, then PATCH Counter = currentCounter + 1
const callers = ["Caller A", "Caller B"];
const s1Caller = callers[currentCounter % callers.length];
Result: strict alternation with zero human triage, verified end to end through both branches. Because the counter only ever increments, it doubles as a free lifetime tally per funnel. Adding a third caller is one entry in the callers array, one entry in the Discord ID map, and a publish. Known trade-off: two leads arriving in the same instant could draw the same caller. At current volumes this is rare and harmless, so the simplicity was kept over adding locking.
System 2
A free class is a real cost: a coach's time, a seat, admin work creating logins. Some families sign up repeatedly to collect multiple free classes, while others signed up months ago and never attended. Both facts change how a caller should handle the conversation, and neither was visible at the moment of dialling.
The hard part was not finding an old record, it was deciding what an old record means. A previous signup is not proof a family received a class: plenty sign up and never attend, and some are mid-cycle, added to a class group with the class not yet run. Flag those as repeat testers and the system accuses innocent families, and callers stop trusting the warnings.
A screening step sits on the flow's trunk, immediately after the lead's record is created. It searches for earlier records matching the lead's contact details and classifies history against one strict rule: a previous trial only counts as delivered when that record's Remove from group box is ticked, meaning the full cycle demonstrably happened. Mid-cycle records and signups that never reached a class deliberately do not qualify.
| History found | What the caller sees | How it is used |
|---|---|---|
| No earlier record | Message posts clean, nothing added | Normal first-contact call |
| Earlier signup, never delivered | Context line: signed up before but never got the class | Caller frames the call as a follow-through, not a cold pitch |
| Full cycle completed | Red warning: repeat tester, already received a free class | Caller steers to paid enrolment instead of a second freebie |
No history found: clean assignment, normal first-contact call
Earlier signup, never delivered: context line frames the follow-through
Full cycle completed: red warning steers the caller to paid enrolment
Every assignment message is now also a briefing, at the cost of one lookup per lead and zero effort to consult. The strict delivered-only rule keeps the red flag trustworthy: when it appears, the class genuinely ran.
System 3
When a caller finishes with a lead and ticks Remove from group, the team needs one notification saying the lead is complete, and the master contact base needs to learn this family has used their free trial. Without that write-back, the repeat screening in System 2 has no history to read.
Two separate difficulties made this hard. First, Airtable's automation trigger watches Last Modified, which updates on any field edit, and cannot watch for a specific field changing to a specific value. A naive flow fires the completion alert every time anyone fixes a typo on an already-completed record. Second, matching the lead back to the master base by phone number fails constantly in naive form, because the same number gets stored in different formats across bases.
// dedupe gate: proceed only when both are true Remove from group = TRUE AND Discord Notified = FALSE // cross-base match on normalised phone, last 8 digits FIND(last8, SUBSTITUTE({Number} & "", " ", ""))
Dedupe is handled with a second, bot-managed checkbox. The flow proceeds only when both conditions above are true, and its final step ticks Discord Notified on the record so a fresh completion is never re-announced. Cross-base matching strips spaces and compares on the last eight digits, which survives country-code and formatting differences entirely.
One alert per completion, no matter how many times the record is edited afterwards, and a master base that stays truthful about who has used their free class. This is the system that closes the loop: completion sync writes the history, repeat screening reads it, rotation decides who acts on it. Each system alone is small. Chained, they run the funnel.
End to End
Beyond the three systems, several practices run through every code step in the flow, keeping the whole pipeline trustworthy at volume rather than merely functional on a good day.
Guarded inputs
Required inputs are checked at the top of every code step, so a missing mapping returns a plain-English error naming the input, instead of a cryptic API failure buried in a log.
Safe auto-create writes
Airtable writes use typecast so a new caller or a new form option auto-creates its select option instead of rejecting the record. Safe here because every value originates from a controlled source.
One canonical version per step
Each code step exists as one canonical version pasted identically into every branch of the flow, so a fix made in one place is a fix made everywhere it runs.
Design Principles
A counter field on a config row gives isolated automation runs a memory, no database required, and doubles as a lead tally per funnel.
A history lookup costs one query per lead and turns every assignment message into a briefing, catching repeat freebie seekers at zero marginal effort.
Received a class means the remove-from-group cycle completed, nothing less. A loose definition would flag no-shows as repeat testers and destroy trust in the warning.
The warning rides on the message the caller was going to read anyway. A separate repeat-tester report would have been technically identical and behaviourally useless.
A bot-managed checkbox is the difference between an alert that fires once and one that fires on every future edit of the record.
Comparing the last eight digits with spaces stripped means formatting and country-code variants never split one family into two records.
Technical Stack
| Component | Technology | Purpose | Cost |
|---|---|---|---|
| Lead capture | Meta Lead Ads instant form | Native conversion event: name, phone, email, year level, requested class | Existing |
| Automation engine | Activepieces flows, router branches, JS code steps | Screening, rotation, record creation, notification, completion sync | Existing |
| Operational CRM | Airtable REST API | Free Trial Class working table: contact details, caller, group status | Existing |
| Master contact base | Airtable REST API, separate base | Lifetime contact history across every funnel, source of truth | Existing |
| Team notification layer | Discord Bot API | Lead assignment pings, history warnings, completion alerts | Existing |
Result
Rotation is live with two callers, verified end to end through both branches, expandable with a two-line edit. Every lead posts with its history classified before the caller dials, with the red warning reserved for genuinely delivered trials. Completion alerts fire exactly once, and the master base stays current on who has used their free class, which in turn powers the screening for the next signup from that family.
The one-line version: a parent taps a Meta ad, and seconds later the right caller is tagged in Discord with the lead's details and its full history, the CRM already knows who owns it, and when the class is delivered the system remembers, so the next signup from that family arrives pre-screened.