Prerequisites

Before you start, have accounts and access to the following services:

Quick Start

# 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.

Environment Variables

VariableRequiredDefaultDescription
NEXT_PUBLIC_SUPABASE_URLrequiredYour Supabase project URL, e.g. https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEYrequiredSupabase anon/public key. Safe to expose to the browser (RLS enforced).
SUPABASE_SECRET_KEYrequiredSupabase 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_KEYrequired32-byte hex string for AES-256-GCM credential encryption. Generate with openssl rand -hex 32. Never expose this.
CRON_SECRETrequiredBearer token Vercel sends when calling /api/cron/poll. Set the same value in Vercel → Project → Cron secrets.
GOOGLE_CLIENT_IDrequiredOAuth 2.0 client ID from Google Cloud Console.
GOOGLE_CLIENT_SECRETrequiredOAuth 2.0 client secret. Keep server-side only.
GOOGLE_REDIRECT_URIrequiredhttp://localhost:3000/auth/callbackMust match exactly what you registered in Google Cloud Console. Change to your Vercel URL for production.
GMAIL_FETCH_LABELoptionalUPIGmail 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_BACKoptional3How many days back to look for emails in each poll run.
GMAIL_FETCH_MAX_RESULTSoptional25Maximum number of Gmail messages to process per run.
TELEGRAM_BOT_TOKENrequiredToken from @BotFather, format: 123456:ABC-DEF...
TELEGRAM_WEBHOOK_SECRETrequiredArbitrary secret you set when registering the webhook. Telegram sends it back so the endpoint can verify authenticity.
MONARCH_GRAPHQL_URLoptionalhttps://api.monarch.com/graphqlPre-filled in .env.example. Only change this if Monarch updates their API endpoint.

1 — Supabase

🗄
Supabase
Auth, Postgres, Row Level Security
  1. Create a new project at supabase.com. Pick a region close to your Vercel deployment region.
  2. Go to Settings → API Keys. Copy your Project URL (also under API settings), publishable (or legacy anon) key, and a secret key into NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, and SUPABASE_SECRET_KEY.
  3. Apply the database migrations. Open the SQL Editor in the Supabase dashboard and run each file in 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.sql
    Alternatively, if you have the Supabase CLI linked: supabase db push
  4. Verify RLS is enabled: in the dashboard go to Table Editor, click any table, and confirm the lock icon is shown.
Secret key: Dashboard routes use the publishable/anon key with Supabase SSR sessions (RLS enforced). The cron job and pipeline use 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.

2 — Google OAuth & Gmail API

G
Google Cloud Console
OAuth 2.0 client with Gmail read scope
  1. Go to console.cloud.google.com → create or reuse a project.
  2. Enable the Gmail API: APIs & Services → Library → search "Gmail API" → Enable.
  3. Go to APIs & Services → OAuth consent screen. Choose External. Add scope https://www.googleapis.com/auth/gmail.readonly. Add your own Gmail address as a test user.
  4. Go to Credentials → Create Credentials → OAuth client ID. Application type: Web application. Add redirect URIs:
    http://localhost:3000/api/auth/google        # local dev
    https://<your-vercel-domain>/api/auth/google  # production
  5. Copy the Client ID and Client Secret into your env file.
  6. In Gmail, create a label called UPI (or whatever you set GMAIL_FETCH_LABEL to), then set a filter to auto-apply it to your bank's notification emails.
Heads up: while your OAuth app is in Testing mode, refresh tokens expire after 7 days. Publish the app to get non-expiring tokens.

3 — Telegram Bot

Telegram BotFather
Inline keyboard prompts for unknown merchants
  1. Open Telegram and start a chat with @BotFather. Send /newbot and follow the prompts to get your bot token.
  2. Add TELEGRAM_BOT_TOKEN to your env. Choose a random string for TELEGRAM_WEBHOOK_SECRET (e.g. openssl rand -hex 20).
  3. Register the webhook with Telegram once after deploying:
    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>"
      }'
  4. In the app dashboard, go to Settings → Telegram. Copy the link code and send it to your bot to connect your account.
Local testing: Telegram cannot reach localhost. Deploy to a Vercel preview URL first, then register the webhook against that URL.

4 — Monarch Money

M
Monarch Money
GraphQL publisher for transactions
Don't have Monarch yet? Try it free → — budgets, net worth, investments, and your UPI transactions all in one place.
  1. MONARCH_GRAPHQL_URL is pre-configured to https://api.monarch.com/graphql — no change needed.
  2. In the app dashboard, go to Settings → Monarch and enter your Monarch account email and password. The app encrypts the credential with your ENCRYPTION_KEY and stores the ciphertext in Supabase — your plaintext password is never persisted.
  3. After connecting, use the Fetch Categories button to load your Monarch categories — this populates the suggestions shown in Telegram prompts.
  4. Optionally set a default Monarch account ID so transactions are automatically attributed to the right account.
Note: Monarch's GraphQL API is not officially documented. The publisher uses a minimal surface (createTransaction mutation). If Monarch updates their schema, check src/lib/publishers/monarch/publisher.ts.

5 — Deploy to Vercel

Vercel
Hosting, cron jobs, serverless functions
  1. Import the repo via the Vercel dashboard, or use the CLI:
    npx vercel          # preview deploy
    npx vercel --prod   # production
  2. In Project → Settings → Environment Variables, add all variables from .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.
  3. The cron job is pre-configured in 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.
  4. After deploying, re-register the Telegram webhook against your production URL (see step 3 above).
GitHub Pages: go to Settings → Pages → Source → Deploy from branch, select main and the /docs folder to serve this site.

Verification Checklist