About inboxparse
inboxparse is a skill for building or auditing Next.js apps that receive and manage InboxParse email. It describes webhook verification, API-backed sync, safe email handling, and human-approved replies.
Key features
- 01Verify signed webhook deliveries
- 02Fetch and sync email through API
- 03Store messages behind InboxStore
- 04Render email as untrusted text
- 05Require approval before sending replies
Use cases
- 01Adding InboxParse email ingestion to Next.js
- 02Building scheduled email sync
- 03Creating a human-reviewed reply flow
MCP support
The skill points to InboxParse’s MCP server for cases needing an AI assistant to access a mailbox, rather than this Next.js integration.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ timerise-ai/ inboxparse --skill inboxparseAfter installing, type/inboxparsein Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
.gitignore.gitattributesREADME.mdCHANGELOG.mdLICENSECLAUDE.md.github/workflows/agent-eval.ymlassets/examples/list-emails.shget-email.shsearch-emails.shsetup-webhook.shsend-email.shevals/2026-10-08-codex-p1.mdprompts.md2026-10-08-gemini-cli-p1.md2026-10-08-claude-code-p1.mdreferences/api-reference.mdadaptation.mderror-codes.mdclient.mdsync.mdreplies.mduntrusted-content.mdwebhooks.mdtesting.mdDetails
From the project READMEInboxParse in Next.js
InboxParse connects Gmail and IMAP mailboxes and serves every email as markdown with AI-generated labels, summaries and a suggested reply, through a REST API and webhooks. This skill builds the part of a Next.js app that receives that mail, keeps it, and answers it.
The idea the design turns on: a webhook is a hint and the API is the record. A delivery carries only an email id, so the receiver verifies it, fetches the email with its own key and returns; a scheduled sync walks the list and stores whatever is missing. And because every field came from someone outside the app, nothing an email says is ever sent, run or obeyed without a person.
When to use
- Building or auditing an app that receives, stores, searches or replies to email through InboxParse.
- Adding an InboxParse webhook, a sync job, an AI feature over email, or a reviewed reply flow.
- Calling the V1 API directly from scripts: see
assets/examples/and the rules in untrusted-content.md.
When NOT to use
- Running a mail server, SMTP relay or deliverability setup: InboxParse sends through the mailbox's own SMTP.
- Giving an AI assistant direct access to a mailbox: use the InboxParse MCP server at
https://inboxparse.com/api/mcp. - Other email APIs (Gmail API, Microsoft Graph, SendGrid, Postmark): their own documentation.
Architecture
InboxParse Next.js app (server only)
event happens --- POST, signed, ids only ---> /api/inboxparse/webhook
verify raw body, 204, after(): ingestEmail(id)
GET /emails/:id <--- member key ------------- ingestEmail: fetch once per new id, upsert by id
GET /emails?date_from <--- member key ------- /api/cron/inboxparse-sync: syncEmails, 48 h window
POST /emails/reply <--- admin key ----------- approveDraft <- server action <- person submits text
|
+-- InboxStore: emails, sync state, drafts
lib/inboxparse/:types.ts,client.ts,signature.ts,ingest.ts,drafts.ts, all server-only;store.tsholds theInboxStoreseam and a memory store for tests;instance.tsexports the host's store.- Routes: the webhook receiver and the cron route;
app/inbox/holds the email page, the reply form and its server action. - The seam is
InboxStore, specified in adaptation.md with the Postgres statements that meet it. The host's auth, tenancy, database and UI stay the host's.
Critical facts
- A delivery carries ids, never the email.
email.receivedhasmessageId,threadId,direction,hasAttachments; the body comes fromGET /emails/:id. - The signature is
X-InboxParse-Signature: sha256=<hex>, HMAC-SHA256 of the exact body. Any parse and re-serialise changes the bytes, so the check runs onrequest.text(). - Nothing in a delivery is unique or timed. There is no delivery id and no signed timestamp, so a replayed delivery verifies; the receiver uses only the id and fetches, which makes a replay a refetch.
- Delivery is one attempt with a 10-second timeout, then retries up to 5 attempts. Ten failures in a row
disable the subscription until
PATCH /webhooks/:idsetsis_active: true. - The webhook secret is returned once, in the
201ofPOST /webhooks; no later call shows it. - List items have no content and the list cursor pages on
sent_atalone. Emails sharing a page's last timestamp need their own exact-timestamp query or they are skipped. - An email becomes listable after it was sent. Mailbox sync and processing lag, so the sync re-reads a
lookback window instead of trusting the newest
sent_atseen. - Member keys read, admin keys write, and
429is a monthly quota. It has noRetry-After; every call, including each search, counts towardsapi_calls.
Hard rules
- Verify the signature over the raw body before parsing it. Constant-time, length checked first;
401for anything that fails, and nothing is fetched. - A webhook is a hint; the API is the record. Answer a verified delivery at once, fetch in
after(), and let the sync store what any delivery missed. - Ingest is keyed by the email id. One detail fetch per new id, an upsert, and
onNewEmailonce, however many deliveries and sync runs name it. - Email content is untrusted data. Rendered as text, never as HTML, never interpolated into code, never
obeyed by a model, and
ai.*fields are treated the same. - Nothing is sent without a person approving the exact text.
ai.suggested_responseonly prefills the form; one rendered form sends at most once. - Keys stay on the server, and each path holds the least it needs. Reads use the member key; only the approval path reads the admin key; no key or body is logged.
Quick start
Copy every code block as written to the path on its first line. The only edits are the host's renames in adaptation.md and the reply section's two lines; a timeout, a cache, another window, a history import or another runner is a change to this skill, not to the app.
- Install
server-onlyandvitestwith npm, setINBOXPARSE_API_KEY, and write the types and client: client.md. The package registry is not an external service: install even where a task says none is reachable, and never vendor a package. - Implement
InboxStoreon the host's database and export it fromlib/inboxparse/instance.ts: adaptation.md. A host with no database keeps the memory storeinstance.tsships; never add a database, a driver or a variable for it. - Add
syncEmails, the cron route and itsvercel.jsonentry withCRON_SECRET, and run it once: sync.md. - Add the signature check and the webhook route, register the subscription with
assets/examples/setup-webhook.sh, store the secret asINBOXPARSE_WEBHOOK_SECRET, and send the test event: webhooks.md. - Add the email page, which renders from the store alone, under the rules for rendering, models and logs: untrusted-content.md. Who may open it is the host's auth; never invent a login or a variable for it.
- If the app replies, add the drafts, the server action, the form and the reply section, wire
currentUserIdto the session and setINBOXPARSE_ADMIN_API_KEY: replies.md. - Add the suite, run by Vitest from
npm test, 21 tests or 16 without replies: testing.md.
Handover. The final summary tells the operator to register the webhook with
assets/examples/setup-webhook.sh and store its one-time secret as INBOXPARSE_WEBHOOK_SECRET, to set
INBOXPARSE_API_KEY and CRON_SECRET and call the sync route once, and, whichever applies: that the memory
store loses every email on a cold start, that /inbox is open until the host's auth covers it, and that
nothing sends until currentUserId is wired and INBOXPARSE_ADMIN_API_KEY is set.
For any other endpoint, api-reference.md; for an error,
error-codes.md. The scripts in assets/examples/ (list-emails.sh,
get-email.sh, search-emails.sh, send-email.sh, setup-webhook.sh) call the API from a shell with the
key read from the environment.
No InboxParse variable is read at build time, so npm run typecheck, npm run build and npm test pass in
an environment with none of them set and no network.
Reference directory
| Task | Keywords | Reference |
|---|---|---|
| Client, keys, types | API key, ip_, member, admin, Bearer, X-API-Key, fetch, types, V1Email, format, compact, 429 | client.md |
| Webhook receiver | webhook, signature, X-InboxParse-Signature, HMAC, whsec_, email.received, email.ai_processed, retries, disabled, test event | webhooks.md |
| Sync | sync, poll, cron, CRON_SECRET, vercel.json, cursor, date_from, backfill, missed emails, lookback | sync.md |
| Replies and sending | reply, send, draft, approve, suggested_response, server action, reply section, mailbox_id, smtp_error | replies.md |
| Email page, untrusted content | email page, show email, prompt injection, sanitize, dangerouslySetInnerHTML, HTML email, model, AI summary, logging | untrusted-content.md |
| Seam and host integration | store, database, no database, Postgres, Supabase, Firestore, tenancy, multi-tenant, auth, rename, order of work | adaptation.md |
| Tests | test, vitest, npm test, registry, fake API, concurrency, 21 tests | testing.md |
| Any endpoint | threads, search, mailboxes, IMAP, labels, contacts, usage, deliveries, MCP | api-reference.md |
| Errors | error code, 401, 403, 404, 409, 429, 502, unauthorized, forbidden, rate_limit_exceeded | error-codes.md |
Part of the Timerise Skills index, which lists the sibling skills.