Skip to content
AgenticMail edited this page May 18, 2026 · 1 revision

Setup (for operators)

The hosted App at github.com/apps/agenticmail runs on Netlify Functions. The same code can be re-deployed under any other GitHub App by setting the env vars below — the function itself is infrastructure-agnostic (any platform that delivers Request/Response and supports context.waitUntil works).


Architecture in one diagram

GitHub webhook delivery
   │
   ▼
POST /api/github/webhook
   ├─ HMAC-SHA256 verify (constant-time)
   ├─ dedup on X-GitHub-Delivery UUID (5-min TTL)
   ├─ 202 Accepted in <100ms
   │
   ├─ context.waitUntil(processWebhook(...))
   │      ├─ branch by event
   │      ├─ rate-limit check (paid verbs)
   │      ├─ plan check (paid verbs)
   │      ├─ fetchPRFiles (if PR + relevant verb)
   │      ├─ generateReply via Anthropic
   │      │     └─ writeUsage(account, tokens, cost)
   │      ├─ post comment OR state-changing action
   │      └─ writeAudit(status, latency, error)
   │
   └─ writeAudit(accepted)

The function never blocks on agent work. GitHub gets its 202 in under 100ms; the LLM call runs in context.waitUntil() so Netlify keeps the function alive for up to 15s of background work.


GitHub App settings

When you register the App at Settings → Developer settings → GitHub Apps:

  • Webhook URL: https://<your-host>/api/github/webhook
  • Webhook secret: strong random string (set on the App and in env as GITHUB_WEBHOOK_SECRET)
  • Subscribe to events: issue_comment, pull_request_review_comment, issues, pull_request, installation, marketplace_purchase

Permissions

Scope Access Why
Issues Read + write Read thread context, post comments, react, close
Pull requests Read + write Summarize, post review comments, post formal reviews
Contents Read + write Required for @agenticmail merge (modifies default branch)
Metadata Read Required by GitHub for any App

Drop Contents: write if you don't need the merge verb — it's the only feature that touches branch state.


Environment variables

Required

Var Purpose
GITHUB_APP_ID Numeric App ID from settings
GITHUB_APP_PRIVATE_KEY PEM-encoded RSA private key (escaped \n ok)
GITHUB_WEBHOOK_SECRET HMAC secret matching the App's webhook config
ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY At least one — see below

The function reads ANTHROPIC_AUTH_TOKEN first (Claude OAuth surface, sk-ant-oat01-…); if absent it falls back to ANTHROPIC_API_KEY (classic sk-ant-api03-…). OAuth tokens require model claude-haiku-4-5 or higher — earlier-generation aliases like claude-3-5-haiku-latest are not visible on the OAuth surface and return 404.

Optional but recommended

Var Purpose
ADMIN_AUDIT_TOKEN Enables /api/github/audit, /usage, /billing
SENDGRID_API_KEY Outbound email (welcome + ops notifications)
SENDGRID_FROM_EMAIL Verified sender address on SendGrid
AGENTICMAIL_OPS_EMAIL Where install/uninstall notifications land
AGENTICMAIL_SEND_URL Custom email endpoint (fallback if no SendGrid)
AGENTICMAIL_API_KEY API key for the custom endpoint

If no email provider is configured, the function continues working — ops notifications just don't fire.


Netlify Blob stores

The function uses six independent Blob stores:

Store Purpose Key TTL
github-webhook-dedup GitHub delivery UUID → seen-at-ms UUID 5 min
github-installations Installation metadata <installation-id> install lifetime
github-webhook-audit One entry per delivery (accepted, processed, errored, etc) <YYYY-MM-DD>/<UUID> none (forever)
github-rate-limit Per-installation token bucket <installation-id> 1 hour window
github-billing Plan record per Marketplace customer account <account-login-lowercased> until cancelled
github-usage Per-call token + cost telemetry <YYYY-MM-DD>/<account>/<UUID> none (forever)

Delete a store from the Netlify dashboard (Project → Blobs) only if you understand what it does. Deleting github-webhook-dedup is safe (might cause one duplicate reply). Deleting github-billing flips all paid accounts back to Free until the next marketplace_purchase webhook re-populates them.


Admin endpoints

All three require x-admin-token: <ADMIN_AUDIT_TOKEN> header.

GET /api/github/audit

GET /api/github/audit?day=YYYY-MM-DD&limit=100&installs=1

Returns the day's delivery entries + (with installs=1) the full installations list. Use it to answer "did GitHub's reviewer event arrive?" or "why didn't the bot reply to X?".

GET /api/github/usage

GET /api/github/usage?account=<login>&range=30

Rolls up Anthropic token usage by account. Returns per-verb breakdown

  • USD cost estimate at the current claude-haiku-4-5 rate. Use it to sanity-check unit economics before/after pricing changes.

GET | POST | DELETE /api/github/billing

# Inspect plan for one account
GET /api/github/billing?account=<login>

# Comp an account onto a paid plan without a real subscription
POST /api/github/billing  body: {"account":"<login>","planName":"Pro"}

# Clear a stale plan record
DELETE /api/github/billing?account=<login>

The marketplace_purchase webhook handler is the primary writer; this endpoint is an escape hatch for comps and corrections.

Note on propagation: Netlify Blob writes can take up to ~60s to be visible to function instances in other regions. After a DELETE, the gate may briefly still see "paid" for in-flight requests.


Deploy

The frontend repo (agenticmail/frontend) is wired to Netlify with auto-deploy on push to main. Workflow:

  1. Make changes locally, commit, push to main.
  2. Netlify picks up the push, runs next build, deploys functions.
  3. Health endpoint comes up at https://<your-host>/api/github/health in ~3–5 minutes.

For env var changes (no source change), trigger a redeploy from the Netlify dashboard — env var updates do not auto-trigger a deploy.

Clone this wiki locally