Growth · Paid Acquisition & Internal Tooling
Caracas, Venezuela · remote, US EST hours
michelcunill1@gmail.com · +58 424 152 0466
I'm a paid search marketer who writes software because the job stopped being doable without it. Running around 100 client accounts, the bottleneck wasn't strategy. Nobody can look at 100 accounts every morning. So I built the things that look for me: a reporting platform that syncs the whole book every 20 minutes, and an automation suite that catches broken ad destinations before a client does. Different stacks, one documented interface between them. Other managers work out of both.
Both in production, both solving a problem I had personally before anyone asked for a solution, and both now used by managers other than me. That last part changed how I build: once a colleague's morning depends on it, "works on my machine" stops being a finish line.
Designed and built, 2025
Performance data for hundreds of client accounts lives behind one manager account, and the native interface makes you look at accounts one at a time. Answering "which accounts are pacing badly this week" meant opening them one by one.
It syncs every account under the agency's manager accounts into a database every 20 minutes and keeps around 400 days of per-campaign-per-day history, then serves it through a web app: cross-account comparison, spend pacing at a glance, asset search across the whole book, and a map view of campaign location targeting. The team uses it daily and some of our reporting workflows depend on it now.
| Decision | Why |
|---|---|
| Read-only | It holds credentials for every client account in the book. Write access would mean a bug could change live ad data, so no write path is exposed at all. The blast radius of anything going wrong is a bad chart. |
| Resumable sync | The scheduled job runs in phases and keeps its progress in the database, so a timeout or a failure mid-sync picks up where it stopped instead of starting the whole book again. |
| History, not snapshots | Storing every campaign-day rather than the current state is what makes "is this worse than last month" answerable at all. The API won't tell you later. |
| 2FA and domain-locked sign-up | It aggregates real client spend across an entire agency book. Sign-up is restricted to the company domain and needs admin approval, and accounts carry a second factor. |
Stack
TypeScript on serverless functions / Postgres / React and Vite / Leaflet for the map /
JWT and TOTP auth
Started as a Python and SQLite prototype. I rewrote it onto this stack once it was
clear the team was going to depend on it.
Built 2026, still in development
Ads point at client websites that change without telling you. A page gets renamed during a redesign and the ad keeps running, keeps charging, and lands people on a 404. Across 100 accounts, nobody has the hours to check every destination URL by hand. Same story for the other recurring chores: drafting a campaign for a new client, or finding the search terms naming towns a client can't serve.
Five services behind one dashboard. A checker that sweeps live ads and probes every destination URL and sitelink. An extractor that reads a client site for products, images, and landing pages. A generator that drafts campaign structures from what it found. A geo tool that works in both directions, finding searches naming places outside the service area and existing negative keywords that are blocking traffic inside it. And an orchestrator with a job queue, an audit log, and an approval gate, fronted by a dashboard other managers work out of.
| Decision | Why |
|---|---|
| Two strikes | A URL is only flagged after it fails twice in a row. Sites blip. An alerting system that cries wolf gets ignored within a week, and then it's worse than nothing. |
| Proposes, doesn't act | It computes a suggested fix and hands it to a person rather than editing the account. Automated repair of something it misdiagnosed is a worse outcome than the original broken link. Every write path is dry-run by default. |
| Split trust zones | The host that scrapes client websites never holds Google Ads credentials. The host that could write to Ads never fetches client sites. Compromising one doesn't hand over the other. |
| Swap, never edit in place | A sitelink shared across many ads gets a new asset and a swap. Editing the shared one would silently change every other ad using it. |
| Probes aren't clicks | Checks are plain HTTP fetches with per-host pacing and a tagged user agent, never the ad's own click URL. Monitoring a client's ads should never spend their budget or look like fraud. |
Stack
Python / FastAPI / Postgres / htmx dashboard behind company SSO / queue workers, one
per trust zone
276 tests, all running offline with no network and no credentials.
How they fit together
The automation suite reads its performance data from the reporting platform rather than querying the ads API again. That connection is a documented read-only contract, written so it could be lifted into a different codebase without reading any of the code that consumes it. Two systems, two languages, one interface, and neither one reaches into the other's internals.
Both built LLM-assisted. That's the honest version of "AI-native": it changed what I could ship on my own, not just how fast I write.
I've described these by what they do rather than naming the repos, since they run against live client accounts. Happy to walk through the code and the architecture in an interview.
Tooling that reports on conversion data is only worth as much as the conversion data. I own tracking implementation across client sites, which is where I learned not to trust a number just because a dashboard rendered it.
The recurring lesson: Smart Bidding optimizes toward whatever you tell it a conversion is. A tag firing on the wrong event doesn't produce an obviously broken report. It produces a confident one that's wrong, and the algorithm spends real money against it.
I didn't build these from a spec someone handed me. I built them because I was the one doing the work badly by hand.
My book
The full agency book the platform covers
Day to day that was Google Ads across Search, Display, and Performance Max for fintech, SaaS, and events clients: pacing, bidding strategy, keyword and audience work, ad copy testing, and the client meetings where you explain what changed and why. My buying experience is Google-side; I haven't run campaigns in Meta Ads Manager.
The book-wide figures are the whole agency, not my personal results. They're here because they describe the surface area the platform covers. My own book was about 100 accounts and $465K in spend over the engagement.
The book turned over constantly. I picked up new accounts most months and handed others off, so comparing my numbers in June against my numbers a year later measures churn rather than work. New accounts arrive expensive and reset the average.
So I measured it a different way. I pulled the assignment history out of the CRM, joined it to per-day campaign metrics, and indexed every account against the agency's own cost per conversion for that same calendar month. Then I grouped by how long I'd held the account. That controls for the churn and for the seasonal swing at once. An index of 100 is the agency average, and lower is better.
| Months held | Accounts | My CPA | Agency, same month | Index |
|---|---|---|---|---|
| 0, at takeover | 227 | $14.93 | $13.32 | 112 |
| 1 | 192 | $13.95 | $12.81 | 109 |
| 2 | 163 | $12.99 | $12.58 | 103 |
| 3 | 132 | $12.38 | $12.13 | 102 |
| 4 | 89 | $10.65 | $12.85 | 83 |
| 5 | 60 | $10.53 | $13.29 | 79 |
| 6 | 48 | $13.96 | $16.06 | 87 |
| 7 | 41 | $18.58 | $17.14 | 108 |
| 8 | 38 | $16.23 | $17.16 | 95 |
Accounts came to me running about 12% worse than the agency average and were running 17 to 21% better by month four or five. That's across 227 accounts at takeover.
The honest limit: past month six the sample thins and the gap closes again. Months seven and eight sit on 38 to 41 accounts and month seven is back above the benchmark. The part I'd defend is the first six months.
Everything above.
Consumer fintech app. Ran App Store and Google Play presence: listings, creatives, and metadata optimized for install conversion. Ran A/B tests and fed results into UX and copy. Built go-to-market and acquisition strategy including in-app brand and character development.
88%+ quality accuracy inside the first month, among the highest in my cohort, and no complaints across the engagement. Live interpreting under time pressure, which is why I don't hedge about working in English.
Outbound lead generation for a SaaS platform: prospecting, demos, negotiation, closing.
Completed 9 of 10 semesters before going full-time into marketing.