Inspiration

I run a mobile app. After launch I watched thousands of users download it, try it once, and never come back. The conversion numbers were brutal. That is when I learned what most indie developers eventually discover: retention and re-engagement marketing is not optional. It is the whole game.

But I am a builder at heart. Writing code gives immediate feedback. Marketing means chasing the 95-99% of downloads that never convert. You craft a campaign, send it out, and wait. Maybe nothing happens. So I kept building features instead of doing marketing, and I suspect many solo developers and small teams fall into the same trap.

MailPidge started from that frustration. What if an AI agent could handle the marketing side entirely? Not a template generator with a send button, but an autonomous operator that strategizes campaigns, designs the emails, sends them, reads its own results, and gets better every week. So builders like me can stay focused on building.

What it does

MailPidge connects to the Google Sheet where your contacts already live, plans a multi-wave campaign toward a goal you describe in one sentence, designs email templates with AI-generated hero images and animated banners, sends through your own Gmail account, tracks opens and clicks into a BigQuery warehouse, and proposes the next wave based on what the last wave taught it.

Why AI, and why these models

Email marketing involves too many interacting decisions for one person to handle well. Audience segmentation, send timing, template design, performance analysis, re-engagement strategy. Each one demands attention and iteration. Most indie developers skip it entirely because the effort-to-result ratio feels terrible. MailPidge uses Gemini because Google ADK gives us multi-agent orchestration without writing glue code, and the model family covers every capability in one ecosystem:

  • Gemini 3.5 Flash: orchestration, planning, HTML generation, and tool calling across all agents
  • Gemini 3.1 Flash Image: hero image generation for email templates via the Gemini multimodal API
  • Veo 3.1: animated GIF/video generation for email banners
  • Gemma 4 26B: template quality scoring as an independent gate — the judge is never the generator
  • Imagen 4 (wired, disabled for cost): fully integrated in the codebase and activates with a single env var (IMAGEN_MODEL=imagen-4.0-generate-001), kept off during the hackathon to avoid per-image generation costs

The agent decides at runtime which models to call. A plain-text newsletter uses only Flash. A visual campaign pulls all four active models. No wasted API calls.

The learning loop is real. After each wave, the Campaign Analyst writes structured insights to Firestore with evidence queries and confidence scores. Before the next campaign, the Strategist and Template Designer recall those insights automatically. Stale learnings self-evict when the data no longer supports them. The agent genuinely improves over time, not through retraining, but through its own memory.

It runs in two modes: approval-required (you review every wave before it sends) or auto-mode (the agent proposes and executes within safety rails you set). The agent never knows which mode it is in. That safety boundary lives in the infrastructure, not in the LLM's reasoning.

The whole experience lives inside a Google Sheets sidebar. You chat with the agent, approve plans, watch waves send, and see delivery status written back into your rows. No CRM migration, no new contact database, no stored contacts at all.

PWA Dashboard Google Sheets Sidebar

How we built it

The system is two layers with a hard wall between them. The AI layer (Google ADK 2.7.0 on Cloud Run) does all the thinking: planning, designing, analyzing, proposing. The infrastructure layer (a Cloudflare Worker with Durable Objects) does all the doing: sending, tracking, suppressing, pacing. The agent never touches D1, Gmail, or Google Sheets directly. Every action flows through the Worker's REST API, so a misbehaving model has no path around the compliance gates.

Agent system

One root agent, four specialists. Each specialist owns one phase of the campaign lifecycle:

┌────────────────────────────────────────────────────────────────────────┐
│                              PIDGE (root)                              │
│                      ADK 2.7.0 · Gemini 3.5 Flash                      │
│                        46 tools · 4 sub_agents                         │
│                                                                        │
│          ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│          │ before_model │  │ before_tool  │  │  after_tool  │          │
│          │inject context│  │approval gate │  │  audit log   │          │
│          └──────────────┘  └──────────────┘  └──────────────┘          │
└───────┬──────────────────┬──────────────────┬──────────────────┬───────┘
        │                  │                  │                  │
        ▼                  ▼                  ▼                  ▼
┌───────────────┐  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐
│   STRATEGIST  │  │    DESIGNER   │  │    ANALYST    │  │    REPORTER   │
│ Gemini Flash  │  │  4 AI models  │  │  Gemini Flash │  │  Gemini Flash │
│  DEFINE phase │  │ IMPROVE phase │  │ ANALYZE phase │  │  REPORT phase │
└───────────────┘  └───────┬───────┘  └───────────┬───┘  └───────────────┘
                           │                      │  saves insights
      ┌─────────────┬──────┴──────┐               │
      ▼             ▼             ▼               ▼
┌───────────┐ ┌───────────┐ ┌───────────┐  ┌─────────────┐
│ Imagen 4* │ │  Veo 3.1  │ │  Gemma 4  │  │  Firestore  │
│   images  │ │    GIFs   │ │  scoring  │  │  learnings/ │
└───────────┘ └───────────┘ └───────────┘  └──────┬──────┘
                                                  │  recalled
                                                  ▼
               ┌────────────────────────────────────────┐
               │ STRATEGIST + DESIGNER recall learnings │
               │ before every new campaign              │
               └────────────────────────────────────────┘

*Imagen 4 is fully integrated but disabled by default to avoid per-image generation costs during the hackathon. The codebase uses Gemini 3.1 Flash Image as the default. To enable Imagen 4, set the env var IMAGEN_MODEL=imagen-4.0-generate-001 on the Cloud Run agent — no code changes needed.

Here is how they wire together in code:

specialist_sub_agents = create_specialist_sub_agents()  # 4 Agents

pidge = Agent(
    name="pidge",
    model=GEMINI_MODEL,
    instruction=as_literal_instruction(PIDGE_SYSTEM_PROMPT),
    tools=ALL_TOOLS,
    sub_agents=specialist_sub_agents,
    before_model_callback=inject_context_before_model,
    before_tool_callback=[start_tool_call_timer, enforce_approval_gate_before_tool],
    after_tool_callback=record_tool_call_to_audit_log,
)

ADK callbacks wire the cross-cutting concerns. before_model_callback injects fresh context (campaign history, send defaults, recalled learnings) before every LLM call. before_tool_callback enforces the approval gate so the LLM cannot bypass it regardless of what it is prompted to try. after_tool_callback writes every tool call to an audit trail. Safety and observability are structural, not something we rely on the model to remember.

DMAIC quality cycle

The whole loop is structured around DMAIC (Define, Measure, Analyze, Improve, Control). Each phase with human-grade judgment gets its own specialist. The purely mechanical phase gets pure infrastructure.

                ┌───────────────────────────────────────────────┐
                │                DMAIC CYCLE                    │
                ▼                                               │
     ┌─────────────────────┐                                    │
     │       DEFINE        │  Campaign Strategist · Gemini Flash  │
     │   goal → plan       │  round 1: human sets the goal      │
     │                     │  round 2+: agent proposes the wave │
     └──────────┬──────────┘  Firestore plans/                  │
                ▼                                               │
     ┌─────────────────────┐                                    │
     │       MEASURE       │  No LLM. Worker → BigQuery direct  │
     │  opens · clicks ·   │  sub-ms pixel ACK · HMAC row_id    │
     │  unsubscribes       │  append-only Storage Write API     │
     └──────────┬──────────┘                                    │
                ▼                                               │
     ┌─────────────────────┐                                    │
     │       ANALYZE       │  Campaign Analyst · Gemini Flash   │
     │  agent-composed SQL │  compose → execute → refine ×3     │
     │                     │  48h post-send + weekly digest     │
     └──────────┬──────────┘                                    │
                ▼                                               │
     ┌─────────────────────┐                                    │
     │       IMPROVE       │  Template Designer · 4 models      │
     │  learnings → new    │  RECALL learnings → generate →     │
     │  templates          │  score → repair                    │
     └──────────┬──────────┘                                    │
                ▼                                               │
     ┌─────────────────────┐                                    │
     │       CONTROL       │  Report Designer + safety rails    │
     │  reports · gates ·  │  approval gate · suppressions ·    │
     │  suppressions       │  weekly report emailed to owner    │
     └──────────┬──────────┘                                    │
                │                                               │
                └───────────────────────────────────────────────┘
       the loop closes: wave 2 is defined by what wave 1 measured
Phase Owner Where it physically lives
Define Campaign Strategist + human Firestore plans/, approved via UI click
Measure Worker (no LLM) BigQuery events table, written from the edge
Analyze Campaign Analyst Agent-composed SQL over BigQuery
Improve Template Designer Firestore learnings/ + R2 template versions
Control Report Designer + callbacks Approval gate, suppression checks, weekly report

Data lives in four stores, each chosen for one physical reason: D1 for anything the Worker checks during execution, BigQuery for anything appended or aggregated, Firestore for anything read whole by key (plans, learnings, agent sessions), and R2 for large blobs like template HTML. Every attempt to use fewer stores broke on a real constraint.

Orchestration follows one principle: code owns the order, LLM owns the judgment. Transfer mode lets the LLM route between specialists for open-ended conversations. Workflow DAGs guarantee sequencing when the order matters. Single-turn mode handles self-contained tasks like campaign analysis.

Multi-Wave Campaign

Challenges we ran into

LLMs produce valid HTML but terrible email HTML. Early on, we prompted Gemini to design an email. The HTML passed W3C validation but was unusable in real inboxes. No MSO conditionals for Outlook. No VML fallbacks for background images. Layouts that collapsed on mobile. Email rendering has decades of accumulated quirks that barely exist in training data. We tried stuffing design guidelines into the system prompt, but 5K+ tokens of email craft on every call is expensive and wasteful when the agent is just answering "how many people opened wave 2?" This led to the Skills Architecture, where the agent loads domain knowledge on demand (~$0.0003 per template) and most requests never touch the expensive deep references at all.

The fix was a 4-step template pipeline where different models handle different concerns:

  STEP 1 · PARALLEL GENERATION
  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
  │  Gemini Flash    │  │ Gemini 3.1 Flash │  │     Veo 3.1      │
  │  React Email JSX │  │ Image (Imagen4*) │  │  animated GIF    │
  │  15-20 lines     │  │                  │  │   (optional)     │
  └────────┬─────────┘  └────────┬─────────┘  └────────┬─────────┘
           │                     │                     │
           └──────────┬──────────┴─────────────────────┘
                      ▼
  STEP 2 · COMPOSE
  ┌────────────────────────────────────────┐
  │  merge images into rendered HTML       │
  └──────────────────┬─────────────────────┘
                     ▼
  STEP 3 · SCORE + REPAIR (cross-model gate)
  ┌────────────────────────────────────────┐
  │  Gemma 4 scores: spam risk · tone ·    │
  │  accessibility · mobile · compliance   │
  │  score < 6/10 → Gemini repairs ──┐     │
  │        ▲                         │     │
  │        └───── ×2 max ────────────┘     │
  └──────────────────┬─────────────────────┘
                     ▼
  STEP 4 · DETERMINISTIC REVIEW
  ┌────────────────────────────────────────┐
  │  regex CAN-SPAM / GDPR checks          │
  └────────────────────────────────────────┘

The judge is never the generator. Gemini writes the template, Gemma 4 scores it. A model grading its own output grades generously. A different model family judging it is an architectural separation of concerns.

The agent autonomy boundary had to be structural, not behavioral. Our first instinct was if autonomous then send(). That puts the safety boundary inside the LLM's reasoning, which means it is one creative prompt away from doing something you did not approve. We ripped that out and built wave-append architecture instead. The agent always submits a proposal. A D1 toggle downstream decides whether it auto-executes or waits. The approval gate is an ADK before_tool_callback, not a prompt instruction. The LLM literally cannot skip it.

Choosing the right ADK orchestration mode cost us days. Transfer mode, single-turn, and Workflow DAGs each solve different problems, and mixing them wrong causes failures that are genuinely hard to debug. We initially wrapped specialists in AgentTool, which swallowed all sub-agent events. Tool calls became invisible in server logs. Test assertions turned flaky with no clear cause. Migrating to sub_agents= with Transfer made every specialist action observable again.

Zero-PII architecture is more discipline than code. Every record is keyed by HMAC-SHA-256 tracking IDs derived from the recipient's address with a Worker-held secret. That part is straightforward. The hard part is making sure no email address leaks through any side channel. Gmail error bodies echo recipient addresses. Error messages get logged. Log entries get persisted. Every path that stores an error now scrubs addresses before persistence. GDPR erasure fans out across all four stores in one operation. Getting this right in every code path took longer than building most features.

Accomplishments that we're proud of

  • $0/month email delivery through the Gmail API. The entire stack runs on Cloudflare and Google Cloud free tiers
  • Zero email addresses stored, anywhere. A fully compromised database leaks suppression flags and open rates, but no contact data, because there is none to leak
  • 5 agents, 46 tools, 4 active Google AI models (Imagen 4 wired but disabled for cost) working as one coherent system with 3 workflow DAGs
  • Skills architecture that turns domain expertise into a cost question (~$0.0003/template) instead of a capability question
  • Firebase Auth + App Check + reCAPTCHA Enterprise with independent token verification on both the Python agent and TypeScript Worker
  • Self-improving learning loop where the Analyst saves insights with confidence scores and stale findings self-evict when the evidence no longer holds

What's novel

Most email marketing tools are WYSIWYG editors with a send button. MailPidge is fundamentally different in a few ways:

  • Skills Architecture with progressive disclosure. The agent loads domain knowledge on-demand (L1 metadata, L2 instructions, L3 deep references), not stuffed into every prompt. Most requests never touch L3.
  • Wave-append autonomy. The agent never knows if it is in auto or semi-auto mode. The safety boundary lives in infrastructure, not in LLM reasoning.
  • 4-model template pipeline with graceful degradation. Gemini Flash + Gemini 3.1 Flash Image + Veo 3.1 + Gemma 4 in one flow, with Imagen 4 wired as an env-var upgrade. Image generation failure produces a text-only template. Gemma unavailable falls back to deterministic heuristics.
  • Code owns the order, LLM owns the judgment. Workflow DAGs for guaranteed sequences, Transfer for open-ended reasoning. Three orchestration modes, each used where it fits.
  • Full DMAIC cycle automated. Define through Control, with the agent reading its own reports to self-improve. Wave 2 is always informed by wave 1.

Deep Dive Analysis Template Editor

What we learned

LLMs know HTML but they do not know email design. The fix is not longer prompts. It is smarter knowledge architecture. Progressive skill loading means the agent pays for craft only when the task demands it. Most requests never touch the expensive references. Make the architecture smarter, not the model.

Put the safety boundary in infrastructure, not in the prompt. Telling the agent "only send if approved" is fragile. Making the approval gate a before_tool_callback that the LLM cannot see or skip is not. The same code path runs in both auto and semi-auto mode, which cuts the test surface in half and makes the safety argument trivial to audit.

ADK orchestration is powerful, but the mode you pick matters more than you expect. Transfer for routing, Workflow DAG for guaranteed order, single-turn for contained tasks. We lost days debugging invisible event loss from picking the wrong mode. The principle we landed on: code owns the order, LLM owns the judgment.

Multiple specialized models beat one powerful model. Gemini Flash generates HTML. Gemini 3.1 Flash Image creates hero images. Veo 3.1 makes animated banners. Gemma 4 scores quality as a gate, not a creator. Imagen 4 is fully wired as a drop-in upgrade. The agent picks at runtime. A plain-text newsletter skips image generation entirely. You only pay for what the campaign actually needs.

What's next for MailPidge

MailPidge is designed as a real product, not a hackathon project we will abandon on September 1st. The multi-tenant data layer, billing scaffolding, and open-core licensing (AGPLv3 CE + proprietary Pro) are already in place.

Distribution first: Google Workspace Marketplace publishing for the Sheets add-on, and a Tauri desktop app for users who want a standalone experience outside the browser.

Then the features that separate a capable agent from a competitive one: A/B testing execution, advanced audience segmentation, send-time optimization, and tenant-scoped brand knowledge RAG.

Longer-term: SES orchestration for volume beyond Gmail's 1,500/day ceiling, following the Resend model. Eventually, KumoMTA (Apache 2.0) for full sending independence. The queue seam in the current architecture is designed but not yet built. One call site changes, nothing downstream breaks.

The goal is an email marketing operator that gets better every week, running inside the tools people already use.

Built With

Share this project:

Updates

Submission history