Case Study · AI Platform · Marketing Automation

MSM Marketing Engine

A custom internal marketing platform for a regional youth baseball tournament operator. Two LLMs in parallel draft, score, and route every campaign — replacing a SaaS stack and a Google Drive of CSVs with a single tool that ships campaigns in 30 seconds instead of 30 minutes.

Python · FastAPI SQLModel · SQLite Anthropic Claude Google Gemini MailerLite API Twilio Tailwind
1,500+Contacts unified
121Campaigns shipped
100,306Emails sent
56.6%Avg open rate
3 weeksConcept to live
MSM Marketing Engine — Email Performance dashboard with AI scoring per campaign
The challenge

Marketing run on intuition, spreadsheets, and a hard deadline.

Middle School Matchup is a regional youth baseball tournament operator running a tight registration window — every season, dozens of schools sign up players against a hard deadline. The marketing reality going in:

  • 1,500+ contacts scattered everywhere — MailerLite subscribers, Google Forms, registration CSVs, and a "new player leads" form owned by a separate franchisor.
  • Every campaign drafted by hand in MailerLite's editor with no historical performance lookup. No way to know what worked last time, what didn't, or what to send next.
  • "What should I send this week?" answered by gut feel. The data existed — it just wasn't accessible at the moment of the decision.
  • Audience segmentation done in Excel — export CSV, filter, paste back. Slow, error-prone, and impossible to reuse.
  • No view of which past campaigns moved the needle. Campaign #45 might have driven half the registrations. Nobody could tell.

Off-the-shelf marketing automation could solve some of it — at a per-seat SaaS contract, a dozen clicks per action, and a learning curve longer than the registration window itself. The operator needed a tool that worked the way the season works — fast, prescriptive, and aware of its own history.

The solution

A self-hosted marketing engine with two LLMs at the core.

A FastAPI application with a no-build-step Tailwind frontend, designed to run on the operator's laptop or a small VPS. Four modules, one operator, one tournament cycle.

  • Unified contact database — idempotent imports from registration CSVs, MailerLite dumps, and lead lists. School-name normalization (104 raw values → 71 canonical). Live audience sizing for every preset segment.
  • Email module (the centerpiece) — Performance, Insights, and Compose tabs that turn campaign history into prescriptive guidance. Every email goes through a dual-LLM ensemble before it's pushed.
  • SMS module (Twilio) — bulk-send pipeline with delivery-status reporting; awaiting 10DLC carrier clearance for full parity with email.
  • Scorecard — live KPIs across email subscribers, SMS subscribers, registered players, and social channels in a single scroll. Day-over-day and week-over-week trends without the operator opening four other tabs.

The whole thing runs on Python 3.9, SQLite, and httpx. No Kubernetes, no SaaS dependencies, no monthly contract. The operator owns the code, the database, every contact, every API key.

Key features

What the operator actually uses every day.

Composite AI Score per campaign

Every past campaign gets a 1–100 score blending open rate (40%), click rate (20%), hook strength (20%), and audience fit (20%) — percentile-ranked across the full history. Both Anthropic and Gemini analyses surfaced side by side so disagreements are visible, not hidden.

Dual-LLM ensemble drafting

Every email goes to Claude Sonnet and Gemini Flash in parallel, scored against a heuristic rubric (subject length, emoji density, urgency-word penalty, single-CTA bonus). The higher-scoring draft wins; the alternate is one click away. The operator sees both rationales and picks.

"What should I send this week?"

The Suggested Composer reviews current registration state, top historical winners, and known pitfalls — then proposes 3 prescriptive campaigns with priority, audience, subject, body outline, and rationale. The decision the operator was making by gut, made by the system that has the data.

One-shot drafting

Free-form: "Tomorrow morning, last call for 6th graders who haven't registered." The system picks the right audience preset, writes the topic and goal, drafts the email with both LLMs, and reveals the MailerLite push panel. Idea to sent: ~30 seconds.

Day-of-week and hour-of-day analysis

Send-time performance derived from actual campaign history — not "best practices" or vendor recommendations. The bars show what's worked for this audience, not what worked for someone else's mailing list two years ago.

Background sync, never blocked

MailerLite syncs run as background jobs with an in-memory ledger and polled progress UI. The operator keeps working while a 1,000-contact upload finishes — never frozen, never wondering if it's still running.

Technical edge

Where it gets clever.

  • Audience-aware drafting. The audience description is woven into the system prompt so the draft cites realistic logistics for that segment. A hot-leads draft and a registered-refer-friends draft come out genuinely different in tone and structure — not just swapped subject lines.
  • LLM output sanitization. All filter outputs from the routing model are intersected against a tag allowlist and known schema keys. Hallucinated tag names ("rising_7th") get dropped at the boundary instead of silently producing zero-match audiences downstream.
  • Two-layer caching. Settled data (MailerLite campaign analytics) caches to disk with TTL; AI suggestions cache to sessionStorage for instant reload. Cold start 3.98s → hot path 0.025s.
  • Rate-limit hardening. Exponential backoff with Retry-After honoring on every MailerLite endpoint. Semaphore-bounded concurrency. Graceful fallback when the API rejects HTML body submission on lower plan tiers — detect, retry without body, instruct the user to paste in the editor.
  • No build step, ever. Vanilla HTML, Tailwind via CDN, ES modules. No webpack, no node_modules, no devops drama. One operator, one uvicorn command, done.
  • Honest about what's mocked. When franchisor permissions blocked Meta/Facebook integration, those scorecard cards were marked clearly and tracked in a public roadmap doc — not faked.
Before / after

What it replaced.

BeforeAfter
CSV exports + spreadsheet filteringLive audience presets with one-click selection
"I think Tuesday at 7pm works"Day-of-week + hour-block bars on actual past performance
Drafting in MailerLite's editor with no contextAudience-aware ensemble draft with historical winner citations
Manual MailerLite group creation + subscriber uploadOne-click sync + draft campaign creation
No view of which past campaigns workedComposite AI Score per campaign, ranked and explained
30 minutes of clicks per send~30 seconds, idea to push
The result

One tool, one tournament cycle, full ownership.

Built in three weeks across one tournament cycle. 121 campaigns shipped, 100,306 emails sent, 56.6% average open rate — every send drafted, scored, and routed through the engine. The operator owns the code, the database, every contact, every API key. Adding a new audience segment is a config edit, not a contract renegotiation.

What used to be a Google Drive of CSVs and a tab full of MailerLite is now one URL, one operator, one source of truth.

In the wild

The platform, in use.

UI from the live product — Email Performance, Insights, Sync Segment, SMS, Weekly Scorecard, Marketing Activities.

Got a workflow that should be a tool?

If you're running operations through spreadsheets, SaaS subscriptions, and gut feel — there's probably an internal tool that fixes it. Tell us about it.

Start a Project  or call (682) 999-9240