AV outreach bot — end to end, real PII, two-eye gate — Donovan Cadelina

AV outreach bot — end to end, real PII, two-eye gate

The problem: I’m a freelance AV tech in Vegas. My income depends on whether enough production companies know I’m available next week. Manual outreach scaled to maybe 5 cold applications per week. The goal was 100 a week without burning my reputation domain or sending to dead addresses.

The pipeline

Nightly discovery (cron 3 AM)
    → scrape trade-pub people-moves (PLSN), classify, upsert into LIFE OS
    → dedup via persistent seen-companies store

Morning drafts (cron 9 AM PST)
    → pull due cold + due follow-ups from LIFE OS by stage + date
    → render templates locally (zero LLM cost)
    → post each draft as one message to a Discord thread

Discord react-to-approve (cron every 5 min)
    → 👍 from my user_id → send via Resend, update LIFE OS to 'sent',
       bump stage to follow_1 (or follow_2) with next_action_at += 7d/30d
    → 🗑️ → mark skipped, no send

Inbox poller (cron every 5 min)
    → IMAP poll donovan@donovancadelina.com
    → classify each new message (reply / interview / rejection / out_of_scope)
    → PATCH LIFE OS so the outreach row's stage matches reality
    → checkpoint-on-failure retry queue so a transient outage doesn't drop replies

Bounce webhook (Resend → LIFE OS)
    → on email.bounced / email.complained, flip the outreach_send row
       to bounced/complained, NULL the address, log to history
    → idempotent on retry

The hard rules that didn’t move

What was harder than expected

The deliverability layer. Sending raw SMTP from a DigitalOcean droplet is impossible — outbound port 25 is blocked at the platform level (verified by socket test). The first design assumed a verify-by-RCPT step that the droplet couldn’t actually run. Pivoted to MX + syntax + role-prefix detection over DNS (port 53, open) for the cheap catches, then Resend’s bounce webhook for the real protection against live-domain-dead-mailbox addresses.

Every constraint shaped the architecture. The bot looks the way it does because of what the droplet couldn’t do, not what the cleanest design would have wanted.

What it ships

Real applications to real production companies, with real PII, through a two-eye gate. The same pipeline now handles cold sends, follow-ups, inbox classification, and bounce suppression — all autonomous between two human taps per day.

The architecture rule

A real production pipeline isn’t the one that handles the happy path. It’s the one that handles the boring failure modes — bounces, soft 4xx retries, IMAP outages, character-limit overflows on platform pushes — without dropping work or sending wrong-thing-twice.

I built the bounce suppression and the parser idempotency before I built any of the “smart” parts. The smart parts only matter if the boring parts hold.