← Back to Portfolio

Case Study — Automated Lead Generation System

The code worked. The platform said no.

A pipeline that ran correctly end to end, blocked not by a bug, but by a platform's own policy, and what it took to notice the difference.

This pipeline automates cold outreach for the supervised visitation business. Instead of manually emailing individual social workers, attorneys, and family law professionals one at a time, it reads a CSV export of prospects, deduplicates against everyone already contacted, and pushes the new batch into an email platform on a weekly schedule via GitHub Actions. The problem it solves: turning a slow, easy-to-forget manual outreach task into something that runs itself, without duplicating contacts or losing track of who's been reached.

Before

What happened at send time

mailchimp.com/campaigns/review
Campaign Rejected This audience contains contacts who have not opted in. Mailchimp's policies prohibit sending campaigns to non-permission-based contact lists.

What the code had already done correctly

pipeline run — GitHub Actions
// contacts synced ✓ addMember(audience, contact) // success // draft campaign created ✓ createCampaignDraft(audience) // success setCampaignContent(campaign, template) // success // submitted for send — // blocked by platform policy, not by an error

The pipeline was built and working end to end on Mailchimp, contacts synced, draft campaign created. Then Mailchimp rejected the campaign outright with a compliance warning: their platform prohibits sending to non-opted-in contacts, which is exactly what cold outreach is. The tool worked technically but was structurally the wrong fit for the use case.

The insight

This wasn't caught by analytics, and not something I noticed while working on something else. Mailchimp itself flagged it, an active enforcement action from the platform, triggered the moment the campaign was submitted for review. That's a distinct failure mode from a bug: the code executed correctly, but the platform's own policy blocked the actual goal.

The root cause wasn't a broken integration. It was verifying that Mailchimp's stated policy prohibits cold outreach to non-opted-in contacts, full stop, against a platform like Instantly.ai, which is built specifically for cold outreach. No amount of debugging the Mailchimp integration would have fixed this, because there was nothing broken to fix. The pipeline needed a different tool, not a better version of the same one.

After

The migration

mailchimp.js → instantly.js
// Mailchimp: audience + draft-campaign model addMember / createCampaignDraft / setCampaignContent // Instantly: single call against an existing campaign addLead(campaignId, lead) // index.js rewritten to match: leads added directly // to a live campaign, no audience/draft step

Verified, not assumed

Test Send Result 100/100 deliverability score
SPF/DKIM authenticated
Confirmed landing in inbox, not spam

A second-order bug, caught mid-migration

Initially pointed the integration at an Instantly "AI Sales Agent" object instead of a plain "Campaign" object, same platform, different API shape, silent success responses that added nothing. Diagnosed by querying the API directly rather than trusting the UI.

End state

That last decision, capping sends on a brand-new account instead of pushing volume the moment it worked, wasn't the fastest configuration available. It was the one that protected deliverability long enough for the account to earn trust with the platform, a decision made for a reason, not just the first thing that shipped.