Building Cheap, Rule-Driven Gmail Alerts with IMAP and OpenClaw

I wanted a simple system: watch Gmail for messages that actually matter—especially job application replies—and send a WhatsApp alert when something actionable shows up.

The first version worked. It was also unnecessarily complicated.

What I really needed was something predictable, cheap to run, and easy to extend—not a clever system I’d have to debug at 2am.


What I Actually Wanted

Instead of chasing real-time perfection, the real requirements were:

  • Predictable behavior
  • Cheap filtering before calling AI
  • Simple, readable rules
  • No raw email stored in git
  • No full mailbox sync
  • Something other people could copy

The final system delivers exactly that.


The Original Architecture (Too Clever)

The first version used Gmail push events:

Gmail push event
  → local watcher
  → OpenClaw gateway
  → session artifact
  → systemd path trigger
  → scanner
  → classifier
  → WhatsApp alert

It worked—but it raised too many operational questions:

  • Who owns the Gmail watcher?
  • What actually sends the alert?
  • Where do failures happen?
  • Why didn’t something trigger?

For something that can wait 12 hours, this was overkill.


The First Pivot: Local Sync (Also Wrong)

Next idea: sync Gmail locally.

Gmail IMAP
  → mbsync
  → local Maildir
  → scanner
  → rules + classifier
  → alert

This looked clean on paper.

Reality check:

  • Inbox had ~125,000 emails
  • Syncing that just to read “last day” messages is wasteful
  • Even partial syncs still pay the cost of a huge mailbox

Lesson: “not AI” doesn’t mean “cheap.”


The Real Solution: Query Gmail Directly

The final design skips syncing entirely:

systemd timer
  → mail-alert script
  → Gmail IMAP SEARCH SINCE <date>
  → fetch recent messages only
  → YAML rules
  → deterministic prefilter
  → optional classifier
  → WhatsApp alert
  → metadata-only state

This gives you:

  • Bounded cost
  • Simpler debugging
  • No local mailbox

Yes, latency is worse—but clarity is much better.


How It Runs in Practice

  • Runs twice a day (~08:00 and 20:00)
  • Looks back 24 hours
  • Each message is processed at most twice

Simple, predictable, boring—and that’s a good thing.


Rule Design: One YAML File Per Alert

Instead of a giant config, each rule lives in its own file:

config/mail-alert-rules/
  google-security-alert.yaml
  job-application-response.yaml

This makes it easy to:

  • Add a rule
  • Disable a rule
  • Copy and tweak

Example: Deterministic Rule

id: google-security-alert
enabled: true
window: 1d
folders:
  - INBOX
match:
  all:
    from_contains:
      - google
    subject_contains:
      - Security alert
alert:
  channel: whatsapp
  title: Google security alert

Example: Rule + AI Classification

id: job-application-response
enabled: true
window: 1d
folders:
  - INBOX
match:
  any:
    subject_contains:
      - application
      - interview
      - recruiter
      - hiring
    body_contains:
      - thank you for applying
      - schedule
classify:
  prompt: job_application_response
  positive_key: is_job_application_response
alert:
  channel: whatsapp
  title: Possible job application reply

Key Design Principle

  1. Use cheap string matching first
  2. Only call AI when necessary
  3. Keep everything human-readable

If your prefilter is too strict, AI never runs—and accuracy doesn’t matter.


Duplicate Prevention (Without Storing Emails)

Polling means overlap.

Instead of storing full emails:

  • Store metadata only
  • Track what’s been evaluated
  • Allow manual bypass for testing

Clean and privacy-friendly.


The Final Operational Model

systemd timer
  → mail-alert service
  → Gmail IMAP
  → YAML rules
  → optional classification
  → WhatsApp alert

That’s it.

No sync. No push events. No hidden state.


What I’d Copy If You Build This

Use direct IMAP if:

  • Latency isn’t critical
  • Mailbox is large
  • You only need recent messages
  • You want predictable cost

Use local sync if:

  • You truly need offline mail
  • Mailbox is small
  • You want full local access

For this use case, IMAP search wins.


The Core Pattern

This is the part worth reusing:

  1. Poll on a schedule that matches urgency
  2. Search before fetching
  3. Keep rules human-readable
  4. Run deterministic checks before AI
  5. Store metadata-only state
  6. Hash rule behavior for re-evaluation
  7. Make duplicate bypass explicit
  8. Keep secrets out of git

What Went Wrong (And Why It Helped)

Mistakes along the way:

  • Built a push system when polling was enough
  • Assumed sync would be cheap
  • Tried syncing a huge inbox
  • Made prefilter rules too narrow
  • Ignored “no-match” evaluations

None of these were catastrophic—they were useful.


The End Result

The system is now:

  • Simple
  • Cheap
  • Understandable
  • Easy to extend

Adding a new alert is a small, human-scale task:

scripts/mail-alert-rule new bank-security-alert
$EDITOR config/mail-alert-rules/bank-security-alert.yaml
scripts/mail-alert-rule validate bank-security-alert
scripts/mail-alert --dry-run --rule bank-security-alert

That’s the goal:

A system you actually use.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *