Build in Public

What We Learned Automating a Complex Small‑Business Sales Funnel in Public

We built a public automation for a small business with a 5‑stage funnel; the biggest lessons were: keep the data model simple, instrument early, make every step idempotent, respect API rate limits, and document everything for the community.

Overview

The project was a public GitHub repo that wired together a lead capture form, email nurturing, payment processing, and post‑sale support. The funnel looked like:

  1. Lead capture (Web form → Airtable)
  2. Lead qualification (score → Slack notification)
  3. Email sequence (HubSpot → Gmail)
  4. Payment (Stripe checkout)
  5. Onboarding (Twilio SMS + Google Drive folder)

We chose a low‑code orchestration layer (Zapier, Make, or n8n) and exposed the workflow as a series of reusable sub‑automations. The public nature forced us to write clear READMEs, version the flow, and treat the code as a product.

Lesson 1: Define a Minimal Viable Data Model

A complex funnel tempts you to store every event in a separate table. In practice that creates mapping bugs and makes debugging a nightmare. We reduced the model to three core entities:

  • Contact – email, name, source, score
  • Deal – contact ID, stage, amount, timestamps
  • Activity – type, payload, linked deal ID

All downstream tools (Airtable, HubSpot, Stripe) referenced these IDs. When a new field was needed we added it to the Contact record, not a new table. This kept joins to a single lookup and made the data flow traceable.

Lesson 2: Choose the Right Orchestration Tool

FeatureZapier (Paid)Make (Free tier)n8n (Self‑hosted)
UI clarityVery polishedModerateMinimal, node‑based
Task limit (free)100 tasks/mo1 000 operations/moUnlimited (self‑host)
Webhooks support✅✅✅
Code stepsLimited JSFull JSFull JS/TS
Cost for 5 k runs/mo$20$0 (within limits)$0 (hosting)

Zapier is reliable but pricey; Make’s free tier covers most small‑business volumes; n8n gives unlimited runs if you can host a Docker container. We started with Make for cost reasons, switched to n8n when we needed custom JS for idempotency, and kept Zapier as a fallback for client‑specific integrations.

Lesson 3: Idempotency and Error Handling

Public automations are hit by duplicate webhooks, network glitches, and manual retries. Every step must be safe to run twice.

  • Deduplicate inbound webhooks: store a hash of the payload and skip processing if the hash exists.
  • Use upserts instead of blind inserts (Airtable’s Update record if exists, else create).
  • Wrap external API calls in a retry block with exponential back‑off.
  • Emit a clear error record to an “Automation Errors” Airtable view and notify Slack.

Without idempotency we saw contacts duplicated, Stripe charges retried, and onboarding emails sent twice.

Lesson 4: Monitoring and Alerting

A public repo can’t rely on a single developer’s console. We built a lightweight monitoring layer:

  • Heartbeat webhook every 5 minutes to a Google Cloud Function that logs “alive”.
  • Error Slack channel fed by the Automation Errors view.
  • Dashboard in Google Data Studio pulling from Airtable to show funnel conversion rates in real time.

The cost of a Cloud Function was under $1/month, and the visibility saved hours of manual log digging.

Lesson 5: Rate Limits and API Costs

Stripe, HubSpot, and Twilio all enforce per‑minute limits. When the funnel spikes (e.g., a marketing blast) we hit those limits and lose data.

  • Batch non‑critical updates: instead of sending a HubSpot contact update per lead, queue them in a Google Sheet and process 100 at a time.
  • Respect back‑off headers: Make and n8n can read Retry-After and pause the flow automatically.
  • Cost tracking: log each API call with its cost (e.g., Stripe fee) to a “Cost Log” table. This gave us a clear picture that the automation added $0.12 per qualified lead, which the client could budget.

Lesson 6: Documentation and Community Feedback

Because the repo was public, every change required a clear commit message and a versioned README entry. We added:

  • Architecture diagram (draw.io) linked from the repo description.
  • Step‑by‑step “Run locally” guide using Docker Compose for n8n.
  • FAQ section in the repo Wiki (the same questions we answer here). Community contributors filed pull requests to improve error handling and added a new Slack integration. Treating the automation as a product attracted useful external audits.

Lesson 7: Scaling and Future‑Proofing

The client later added a referral program that inserted a new stage into the funnel. Because we had isolated each stage into its own sub‑automation, we only needed to add a new “Referral” module and update the main orchestrator’s routing map. Key practices that made this easy:

  • Modular sub‑flows stored in separate Make scenarios or n8n workflows.
  • Configuration table in Airtable that maps stage names to workflow IDs.
  • Version tags in Git to roll back a single stage without affecting the whole pipeline.

Conclusion

Building a public automation for a small business taught us that simplicity, observability, and defensive coding outweigh any fancy UI. Keep the data model lean, choose a tool that matches your budget, make every step idempotent, monitor early, and document as if the next developer is a stranger. Those habits turn a fragile script into a maintainable product.

On pricing: plans and rates change, and this post was written on a specific date. Check the vendor's current pricing page before you commit to anything.

FAQ

How do I prevent duplicate webhook events in a public automation?
Store a hash of each incoming payload and skip processing if the hash already exists; most platforms let you add a lookup step before the main logic.
What free tool can replace Zapier for a complex funnel?
n8n is free and self‑hosted, offering unlimited runs and full JavaScript support; it works well for most small‑business automations.
Which step should I monitor first when an automation fails?
Start with the error‑record view (e.g., an Airtable table) that captures failed payloads and the associated error message; it gives immediate context.
How can I keep API costs visible in my automation?
Log each external call with its cost to a dedicated “Cost Log” table and aggregate it in a dashboard; this makes per‑lead expenses transparent.
What’s the simplest way to make a workflow idempotent?
Design each action as an upsert (update‑or‑insert) and guard external calls with deduplication checks based on unique identifiers.

More in Build in Public

Questions & answers

Ask anything about this post — you'll get a reply here.

  • No questions yet — be the first.

One concrete AI-automation tip a week

No hype. Unsubscribe anytime.