Skip to main content

Inngest setup runbook

This runbook describes how to set up and run the InboxOps worker with Inngest for async jobs (email fetch cron, fetch-per-inbox, and future send/auto-response jobs). See PRODUCT-AND-ARCHITECTURE.md §4.7 and implementation-plan/M06-email-pipeline.md.


Overview

  • Worker app (apps/worker): Express server that exposes an Inngest serve endpoint at /api/inngest and runs Inngest functions (cron + event-driven).
  • Inngest Cloud (production): You create an app, get Event Key and Signing Key, and configure the worker URL so Inngest can invoke your functions.
  • Inngest Dev Server (local): Run the open-source dev server locally so you can develop and test without Inngest Cloud.

Environment variables

The worker loads env from the monorepo root (.env and .env.local when cwd is apps/worker). Required for the worker to start:

VariableRequiredDescription
DATABASE_URLYesPostgres connection string (pooled).
DATABASE_URL_UNPOOLEDYesPostgres connection string (direct).
AUTH_SECRETYesLucia session signing (min 32 chars).
ENCRYPTION_KEYYesAES-256 key (base64) for inbox credentials.
INNGEST_EVENT_KEYYes*Event key from Inngest Cloud (or dummy for local dev).
INNGEST_SIGNING_KEYYes*Signing key from Inngest Cloud (or dummy for local dev).
APP_URLYesBase URL of the web app (e.g. http://localhost:3000).

* For local dev with the Inngest Dev Server, dummy values (e.g. dev-event-key and dev-signing-key) are accepted; the dev server does not validate keys.

Optional (used by worker functions):

VariableWhen neededDescription
PORTOptionalWorker HTTP port. Default: 8288.
GOOGLE_CLIENT_IDGmail inboxesOAuth client ID for Gmail API (token refresh).
GOOGLE_CLIENT_SECRETGmail inboxesOAuth client secret for Gmail API.
MICROSOFT_CLIENT_IDMicrosoft inboxesOAuth app ID for Microsoft Graph.
MICROSOFT_CLIENT_SECRETMicrosoft inboxesOAuth client secret.
MICROSOFT_TENANT_IDMicrosoft inboxesTenant ID (e.g. common). Defaults to common if unset.
GOOGLE_PUBSUB_TOPICGmail pushFull topic name (e.g. projects/my-project/topics/inboxops-gmail) for Gmail watch. When unset, Gmail uses polling only.
GRAPH_PUSH_NOTIFICATION_URLMicrosoft pushWebhook URL for Graph change notifications (e.g. https://worker.example.com/api/webhooks/graph-push). When unset, Microsoft uses polling only.
AWS_ACCESS_KEY_IDAttachmentsS3 upload for inbound/outbound attachments.
AWS_SECRET_ACCESS_KEYAttachmentsS3 secret.
AWS_REGIONAttachmentse.g. eu-central-1.
AWS_S3_BUCKETAttachmentsBucket name.

For local dev with the Inngest Dev Server:

VariableDescription
INNGEST_DEVSet to 1 so the SDK sends events to the dev server.
INNGEST_BASE_URLIf the dev server runs on a non-default port, set e.g. http://localhost:8289.

See .env.example for a full list and §9 in PRODUCT-AND-ARCHITECTURE.md.


Local development

1. Install dependencies

From the repo root:

pnpm install

2. Configure env

Copy .env.example to .env.local and fill in at least the required variables. For local dev you can use dummy Inngest keys:

INNGEST_EVENT_KEY=dev-event-key
INNGEST_SIGNING_KEY=dev-signing-key

3. Start the worker

From the repo root:

pnpm --filter @inboxops/worker dev

The worker listens on http://localhost:8288 and serves Inngest at http://localhost:8288/api/inngest.

4. Start the Inngest Dev Server

The Inngest Dev Server defaults to port 8288, which conflicts with the worker. Run the dev server on another port and point it at the worker:

npx --ignore-scripts=false inngest-cli@latest dev -p 8289 -u http://localhost:8288/api/inngest
  • Dev Server UI: http://localhost:8289 (dashboard, function list, invoke, event log).
  • The dev server will poll the worker for functions and run crons / invoke functions by calling the worker.

Optional: so that the worker sends events (e.g. from the cron step) to the dev server on 8289, set in .env.local:

INNGEST_DEV=1
INNGEST_BASE_URL=http://localhost:8289

If you run the dev server on the default port 8288, you must run the worker on a different port (e.g. PORT=8290 pnpm --filter @inboxops/worker dev) and use -u http://localhost:8290/api/inngest when starting the dev server.

5. Verify

  • Open http://localhost:8289 and confirm the app is registered and functions appear (e.g. email-fetch-cron, email-fetch).
  • Use Invoke on email-fetch-cron to trigger the cron once, or wait for the 5-minute schedule; then check that inboxops/email.fetch events are sent and email-fetch runs for each connected inbox.

Production (Inngest Cloud)

1. Create an Inngest app

  1. Sign up at app.inngest.com.
  2. Create an app and add a Sync URL that Inngest can reach (your worker’s public URL), e.g. https://worker.yourdomain.com/api/inngest.
  3. Create an Event Key and a Signing Key in the Inngest dashboard.

2. Configure env

Set in your production env (e.g. Fly.io secrets or your platform’s config):

  • INNGEST_EVENT_KEY = Event Key from the dashboard.
  • INNGEST_SIGNING_KEY = Signing Key from the dashboard.
  • Ensure the worker has the same required vars (database, auth, encryption, APP_URL).

Do not set INNGEST_DEV or INNGEST_BASE_URL in production.

3. Deploy the worker

Deploy the worker so it is reachable at the Sync URL you registered. Inngest will poll this URL to discover functions and invoke them (crons and events).

4. Verify

In the Inngest Cloud dashboard, open your app and confirm that functions are synced and that runs appear when the cron triggers or when events are sent.


Functions (M06.3–M06.6)

Function IDTriggerDescription
email-fetch-cronCron */5 * * * *Every 5 minutes, lists connected inboxes and sends one inboxops/email.fetch event per inbox.
email-fetchEvent inboxops/email.fetchLoads inbox, enforces rate limit, fetches new messages (IMAP/Gmail/Graph), processes messages (spam/bounce/dedupe/thread/new ticket), persists attachments to S3, updates sync state. Concurrency: one per inbox.
gmail-watch-registerEvent inboxops/gmail.watch.registerRegisters Gmail push watch (when GOOGLE_PUBSUB_TOPIC set). Called after Google OAuth connect and by renewal cron.
gmail-watch-renewal-cronCron daily 06:00Finds Gmail inboxes with watch expiring within 24h and emits gmail.watch.register per inbox.
graph-subscription-createEvent inboxops/graph.subscription.createCreates Graph change notification subscription (when GRAPH_PUSH_NOTIFICATION_URL set). Called after Microsoft OAuth connect.
graph-subscription-renewal-cronCron every 12hRenews Microsoft Graph subscriptions expiring within 24h.

Gmail and Graph push (M06.6)

  • Gmail: Configure a Google Cloud Pub/Sub topic and grant the Gmail API permission to publish to it. Set GOOGLE_PUBSUB_TOPIC to the full topic name (e.g. projects/my-project/topics/inboxops-gmail). Point the topic’s push subscription to your worker URL: https://<worker-host>/api/webhooks/gmail-push. After a user connects Gmail, the worker registers a watch and Gmail sends push messages to Pub/Sub, which forwards to the worker; the worker enqueues inboxops/email.fetch.
  • Microsoft Graph: Set GRAPH_PUSH_NOTIFICATION_URL to the worker webhook URL (e.g. https://<worker-host>/api/webhooks/graph-push). The worker must be reachable over HTTPS for Graph validation. After a user connects Microsoft, the worker creates a subscription; Graph sends change notifications to the webhook, which enqueues inboxops/email.fetch per notification.
  • If these env vars are unset, the worker still runs; Gmail and Microsoft inboxes use the 5-minute polling cron only.

Troubleshooting

  • Worker fails to start: Run from repo root so .env/.env.local are found. Check that all required env vars are set; getEnv() will throw with a clear message.
  • Dev server does not see functions: Ensure the worker is running and the -u URL is correct. Open http://localhost:8288/api/inngest in a browser to see the Inngest debug payload (e.g. functionsFound).
  • Cron not firing locally: In the Dev Server UI, use Invoke on email-fetch-cron to run it once. For automatic 5-min runs, leave the dev server and worker running.
  • Gmail/Graph fetch fails with token errors: Set GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET or MICROSOFT_CLIENT_ID/MICROSOFT_CLIENT_SECRET (and MICROSOFT_TENANT_ID if needed). The worker uses these to refresh OAuth tokens when fetching mail.
  • Attachments not stored: Configure AWS_* env vars. If any are missing, the worker skips S3 upload and still creates tickets/messages.