lala ✉︎

A Cloudflare Worker template that catches inbound mail and gives your agent a clean queue to drain.

What it is

Point any email address — kirby@postflight.co, support@example.com, whatever — at a lala Worker. Inbound mail gets parsed, stored as a row in D1, and archived (raw .eml + attachments) to R2. Your code drains the queue, marks each row processed, and moves on.

sender ─→ Cloudflare Email Routing ─→ lala Worker
                                         │
                                         ├─ INSERT row in D1      subject, from, body, attachments
                                         ├─ PUT raw.eml in R2     inbox/<id>/raw.eml
                                         └─ PUT attachments in R2 inbox/<id>/<n>-<filename>

Quick install

For consumers (e.g. an agent that needs to read an existing inbox):

curl -fsSL https://lala.postflight.co/install | bash

Installs the lala Python client + CLI, and drops a Claude-Code-style skill into ~/.claude/skills/lala/.

Or just pip directly:

pip install "git+https://github.com/postflightco/lala.git#subdirectory=py"

Provision a new inbox

One-time setup of an inbox (provisioner side). Run on a host that has wrangler:

npx wrangler login                                    # one-time
npx github:postflightco/lala init my-inbox             # walks you through

Interactive prompts cover Cloudflare account, zone, and which addresses to route. Drops a gitignored lala.env in the project source dir with everything consumers need (account id, D1 id, Worker URL, shared secret).

Read messages

lala count
show queue sizes (processed=0 vs 1)
lala list
print queued messages
lala show 42
full dump of one message
lala download 42
pull raw .eml + attachments to disk
lala mark 42
flip processed=1 with optional --notes
lala tail
poll for new unprocessed messages

Python:

from lala import Inbox

with Inbox.from_env() as inbox:
    for msg in inbox.unprocessed(limit=20):
        result = handle(msg)
        for att in msg.attachments:
            bytes_ = inbox.download_attachment(att)
        inbox.mark_processed(msg.inbox_id, notes=str(result))

Cron / scheduled drain

*/5 * * * * cd /path/to/openclaw \
  && set -a && . /path/to/lala.env && set +a \
  && python -m openclaw.drain

State lives in D1, so re-runs are safe — a partially-processed message just stays queued for the next sweep.

What it does NOT do

Skills for agents

The install script drops a Claude-Code-compatible skill at ~/.claude/skills/lala/SKILL.md. Agents discover it the same way any other skill is loaded. You can also fetch it directly:

curl -fsSL https://lala.postflight.co/skills/lala/SKILL.md

Reference