Everything you need to go from zero to a working pipeline in production.
Before you start, have accounts and access to the following services:
# Clone and install git clone <repo-url> && cd upi-ingestor npm install # Create your local env file cp .env.example .env.local # Start the dev server npm run dev # → http://localhost:3000
Fill in .env.local following the sections below, then apply the Supabase migrations before testing the pipeline.
| Variable | Required | Default | Description |
|---|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | required | — | Your Supabase project URL, e.g. https://xxxx.supabase.co |
NEXT_PUBLIC_SUPABASE_ANON_KEY | required | — | Supabase anon/public key. Safe to expose to the browser (RLS enforced). |
SUPABASE_SECRET_KEY | required | — | Supabase secret API key (sb_secret_...) or legacy service_role JWT (server-only). Used by the cron job and pipeline to read/write user data without a browser session. Create under Settings → API Keys. Never prefix with NEXT_PUBLIC_ or commit this value. |
ENCRYPTION_KEY | required | — | 32-byte hex string for AES-256-GCM credential encryption. Generate with openssl rand -hex 32. Never expose this. |
CRON_SECRET | required | — | Bearer token Vercel sends when calling /api/cron/poll. Set the same value in Vercel → Project → Cron secrets. |
GOOGLE_CLIENT_ID | required | — | OAuth 2.0 client ID from Google Cloud Console. |
GOOGLE_CLIENT_SECRET | required | — | OAuth 2.0 client secret. Keep server-side only. |
GOOGLE_REDIRECT_URI | required | http://localhost:3000/auth/callback | Must match exactly what you registered in Google Cloud Console. Change to your Vercel URL for production. |
GMAIL_FETCH_LABEL | optional | UPI | Gmail label to filter for transaction emails. Create this label and set a filter rule to apply it to UPI notifications from your bank. |
GMAIL_FETCH_DAYS_BACK | optional | 3 | How many days back to look for emails in each poll run. |
GMAIL_FETCH_MAX_RESULTS | optional | 25 | Maximum number of Gmail messages to process per run. |
TELEGRAM_BOT_TOKEN | required | — | Token from @BotFather, format: 123456:ABC-DEF... |
TELEGRAM_WEBHOOK_SECRET | required | — | Arbitrary secret you set when registering the webhook. Telegram sends it back so the endpoint can verify authenticity. |
MONARCH_GRAPHQL_URL | optional | https://api.monarch.com/graphql | Pre-filled in .env.example. Only change this if Monarch updates their API endpoint. |
NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, and SUPABASE_SECRET_KEY.supabase/migrations/ in order:
202605051230_init.sql 202605051640_pending_reviews_unique.sql 202605061832_monarch_fx_cache.sql 202605061845_transactions_email_received_at.sql 202605071220_merchant_mappings_last_seen_and_prune.sqlAlternatively, if you have the Supabase CLI linked:
supabase db push
SUPABASE_SECRET_KEY via lib/supabase/admin.ts so they can process all connected users without a login cookie. Keep this key server-side only.
https://www.googleapis.com/auth/gmail.readonly. Add your own Gmail address as a test user.http://localhost:3000/api/auth/google # local dev https://<your-vercel-domain>/api/auth/google # production
GMAIL_FETCH_LABEL to), then set a filter to auto-apply it to your bank's notification emails./newbot and follow the prompts to get your bot token.TELEGRAM_BOT_TOKEN to your env. Choose a random string for TELEGRAM_WEBHOOK_SECRET (e.g. openssl rand -hex 20).curl -X POST "https://api.telegram.org/bot<TOKEN>/setWebhook" \
-H "Content-Type: application/json" \
-d '{
"url": "https://<your-vercel-domain>/api/telegram/webhook",
"secret_token": "<TELEGRAM_WEBHOOK_SECRET>"
}'
localhost. Deploy to a Vercel preview URL first, then register the webhook against that URL.
MONARCH_GRAPHQL_URL is pre-configured to https://api.monarch.com/graphql — no change needed.ENCRYPTION_KEY and stores the ciphertext in Supabase — your plaintext password is never persisted.createTransaction mutation). If Monarch updates their schema, check src/lib/publishers/monarch/publisher.ts.
npx vercel # preview deploy npx vercel --prod # production
.env.local to the Production environment — including SUPABASE_SECRET_KEY (required for the daily cron). Make sure to update GOOGLE_REDIRECT_URI to your production domain.vercel.json:
{ "crons": [{ "path": "/api/cron/poll", "schedule": "30 3 * * *" }] }
On Vercel Hobby, crons run at most once per day — this schedule is within that limit.
main and the /docs folder to serve this site.
/login — you should land on the Transactions dashboardnpm run dev running, call GET /api/cron/poll with Authorization: Bearer $CRON_SECRET. Expect totalUsers ≥ 1 after Gmail is connected.needs_review, check Telegram — inline keyboard should have arrivedpublished