-
Notifications
You must be signed in to change notification settings - Fork 0
Setup
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).
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.
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
| 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.
| 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.
| 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.
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.
All three require x-admin-token: <ADMIN_AUDIT_TOKEN> header.
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?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.
# 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.
The frontend repo (agenticmail/frontend) is wired to Netlify with
auto-deploy on push to main. Workflow:
- Make changes locally, commit, push to
main. - Netlify picks up the push, runs
next build, deploys functions. - Health endpoint comes up at
https://<your-host>/api/github/healthin ~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.