GPS Leaders

Help

Architecture & Integrations

The stack, the background worker, and every external integration

Internal engineering documentation for the GPS Leaders CRM V2 codebase (/workspace/gps-crm). Generated from the source tree; reflects the code as checked out, not aspirational design.


1. High-Level Architecture

GPS Leaders CRM V2 is a Next.js (App Router) monolith with a separate background worker process, backed by PostgreSQL and Redis.

Stack & versions (from package.json)

ComponentVersion / Detail
Next.js16.2.2 (App Router, output: "standalone")
React / React DOM19.2.4
TypeScript^5
Node runtimenode:22-bookworm-slim (Docker base image)
ORMPrisma ^7.7.0 with @prisma/adapter-pg (driver adapter over a pg Pool)
DatabasePostgreSQL 16
Queue / cacheRedis 7, driven by BullMQ ^5.73.1 over ioredis ^5.10.1
Payments SDKstripe ^22.0.1, @stripe/react-stripe-js, @stripe/stripe-js
Email SDKresend ^6.12.2
Authjsonwebtoken (JWT, 7-day expiry) + bcryptjs (password hashing, cost 12)
Calendar UIFullCalendar ^6.1.20
Miscpapaparse (CSV import), libphonenumber-js, gray-matter (docs frontmatter), react-markdown + remark-gfm

npm scripts

dev          next dev
build        NODE_ENV=production next build
start        next start
worker       tsx src/worker.ts          # background worker (production)
worker:dev   tsx watch src/worker.ts    # background worker (dev, hot reload)
lint         eslint

Process model

There are two long-lived processes, both built from the same image:

  1. Web app — Next.js standalone server (node server.js). Serves the UI, App Router API routes, and inbound webhooks.
  2. Sequence workernpx tsx src/worker.ts. A BullMQ worker that drains the sequence-steps queue and executes automated follow-up steps.

Build & deployment

  • Standalone build: next.config.ts sets output: "standalone". The production image copies .next/standalone, .next/static, public/, prisma/, and src/generated into a slim runtime stage.
  • Multi-stage Dockerfile: a base stage installs deps, runs prisma generate, and next build. Two runtime stages share that base:
    • worker-runnerCMD ["npx", "tsx", "src/worker.ts"]
    • runnerCMD ["node", "server.js"] (the last stage, so a bare docker build . produces the web image; a comment in the Dockerfile explicitly warns not to reorder the stages).
  • docker-compose.yml (local/dev): app, worker, postgres, redis, and a caddy reverse proxy. Postgres maps host port 54329, Redis 63799.
  • docker-compose.prod.yml: a single app container (gps-crm-v2) on the external crm_default network, no compose-managed DB/Redis.
  • deploy.sh: SSHes to hetzner-gps, pulls origin/main, rebuilds with --no-cache, stops/removes the old gps-crm-v2 container, and runs the new one with --env-file /opt/gpsleaders/apps/.env.prod, then hits /api/health.
  • Health check: GET /api/health returns a static JSON status payload.

Database access

src/lib/db.ts constructs a single PrismaClient over a pg.Pool (@prisma/adapter-pg), memoized on globalThis so dev hot-reload does not leak connections. Both the web app and the worker import prisma from this module. The Prisma schema (prisma/schema.prisma, ~1,164 lines, ~42 models) generates its client into src/generated/prisma (committed and copied into the runtime image).


2. Directory Map of src/

src/
├── app/                    Next.js App Router — pages + API routes
│   ├── (app)/              Authenticated CRM UI (route group, shared layout)
│   │   ├── dashboard, leads, companies, contacts, pipeline, inbox, sms,
│   │   │   email, calendar, tasks, invoices, fulfillment, inventory,
│   │   │   automations, partners, help, settings/...
│   ├── (auth)/             Login (unauthenticated)
│   ├── (public)/           Public, unauthenticated pages:
│   │   │   pay/[token] (hosted invoice payment), privacy, terms
│   └── api/                API route handlers (see §6 for webhooks)
│       ├── auth, users, leads, companies, contacts, invoices, payments,
│       │   pay/[token], proposals, tasks, sequences, products, devices,
│       │   sims, fulfillment, inbox, conversations, sms, email, calendar,
│       │   google, quickbooks, dashboard, imports, taxonomies, tags ...
│       └── webhooks/       Inbound webhook receivers (stripe, plusvibe,
│                            cal, sms-gateway)
│
├── components/             React components, grouped by domain:
│                            companies/, contacts/, inbox/, invoices/,
│                            leads/, tasks/, layout/, ui/
│
├── lib/                    Server-side business logic & integrations (see §3)
│   ├── email-templates/    Transactional HTML/text email renderers
│   └── payments/           Payment-driven side effects (promote.ts)
│
├── content/docs/           Markdown help articles (frontmatter via gray-matter)
│
├── generated/prisma/       Generated Prisma client (committed)
│
├── hooks/                  React hooks: useDebouncedEffect,
│                            useKeyboardShortcut, useTaxonomy
│
├── types/                  Shared TS types (sequences.ts)
│
└── worker.ts               Background worker entrypoint (BullMQ)

There is no src/worker/ directory — the worker is a single file, src/worker.ts.


3. src/lib/ Module Reference

Core infrastructure

  • db.ts — Exports the singleton prisma client built on a pg.Pool + PrismaPg adapter. Memoized on globalThis to survive hot reloads.
  • auth.ts — Server-side auth. hashPassword / verifyPassword (bcrypt), signToken / verifyToken (JWT, 7-day expiry, requires JWT_SECRET), getAuthUser(req) (resolves the user from the Authorization header or token cookie and confirms isActive), unauthorized(), and requireAdmin(req) (403 unless role ADMIN).
  • auth-client.ts — Browser-side token helpers: getToken(), authHeaders(), clearAuth() (reads/writes localStorage + the token cookie).
  • crypto.ts — AES-256-GCM secret encryption. encryptSecret / decryptSecret serialize as iv:tag:ciphertext (base64). Requires a 32-byte ENCRYPTION_KEY (hex or base64). Used to store per-user SMS gateway passwords at rest.
  • external-fetch.ts — Hardened fetch wrapper for third-party APIs: 8 s timeout, up to 2 retries with exponential backoff + jitter, honors Retry-After, classifies failures as provider_error / rate_limited. Used by every enrichment integration.
  • constants.ts — Domain constants: the 14-vertical ICP_MAP, the 10 PIPELINE_STAGES + labels/colors, company-status options, disposition & pre-call tag lists, SMS_BUSINESS_HOURS (8 AM–8 PM), product defaults.
  • format.tsformatMoney, formatDate, emptyToNull display helpers.
  • display.tsdisplayContactName(contact, company) — preferred label fallback chain: full name → company name → email → empty.
  • phone.tsnormalizeToE164 via libphonenumber-js (defaults to US).
  • docs.ts — Reads src/content/docs/*.md help articles, parses frontmatter with gray-matter; getAllArticles / getArticle.
  • settings-nav.ts — Static settings-page nav link list (SETTINGS_LINKS).

Queue & background processing

  • queue.ts — BullMQ setup. One ioredis connection (memoized, maxRetriesPerRequest: null), getSequenceQueue() (queue "sequence-steps"), and createSequenceWorker(handler) (concurrency 5).
  • sequences.ts — The sequence execution engine. scheduleSequenceStep enqueues a delayed BullMQ job; executeSequenceStep loads a SequenceRun, resolves the step, applies branch/condition filters (SMS-eligible branching), executes the action (send_sms, send_email, create_task; pause_plusvibe / stop_plusvibe / cancel_precall_sequences are handled inline in the tags route), logs SequenceStepLog + Activity, and schedules the next step or marks the run COMPLETED. Has idempotency guards against duplicate sends.
  • sequence-tasks.ts — Sequence task lifecycle. precreateTasksForRun creates all create_task steps up-front with future dueAt and pushes them to Google Calendar; cancelSequenceRun marks a run CANCELED and deletes pending future tasks + their calendar events; pauseActiveSequenceRuns pauses active runs on a lead (triggered by inbound SMS/email), logs the pause, and calls PlusVibe to pause the upstream email sequence.
  • step-summary.tssummarizeStep produces a human-readable description of a sequence step for the automations UI.
  • template-resolve.tsresolveTemplate / previewTemplate substitute {{contact.*}}, {{company.name}}, and {{icp.*}} placeholders in message-template bodies.

Lead lifecycle & enrichment

  • lead-ingest.ts — Shared lead-ingestion service. ingestLead produces a canonical Company / Contact / Lead shape regardless of source (PlusVibe webhook or Maps CSV import): dedups company (by googlePlaceId or normalizedName) and contact (by emailNormalized), runs phone + business enrichment, applies ICP and SMS-eligibility tags, and seeds a LeadScore.
  • lead-stage.tschangeLeadStage transitions a lead's pipeline stage inside a transaction, writes a stage_changed activity, sets wonAt/lostAt, fires a Slack "deal won" notification on WON, and revalidates the dashboard cache.
  • lead-tags.tsapplyLeadTag idempotently applies (or revives) a LeadTag for a given tag slug; rejects unknown/inactive tags.
  • enrichment.tslookupPhone (Trestle phone-intel — line type, carrier, SMS eligibility), searchBusiness (Brave Search — rating, review count, website, description), and the deprecated combined enrichLead.
  • hunter.ts — Hunter.io email discovery: findEmailByName (email-finder) and findEmailsByDomain (domain-search, personal emails only).
  • prospeo.ts — Prospeo mobile-number discovery: findMobileByName.
  • reoon.ts — Reoon email verification: verifyEmail (deliverable / disposable / role-based classification).
  • google-places.tsfetchGooglePlacesIntel — Places "searchText" + details to obtain rating, review count, Maps URL, and a hero photo.
  • linkedin-sniff.tssniffLinkedinOrMapsUrl classifies a raw URL as a contact LinkedIn, company LinkedIn, or Google Maps place URL.
  • cache-business-photo.tscacheBusinessPhoto downloads a business photo (direct URL or Google Places media) to data/lead-photos/<companyId>.<ext> and returns a /api/lead-photos/... path. Writes to a non-public data dir (standalone-build constraint).
  • icp-vars.tsgetIcpVars(icpTag) returns vertical-specific copy (vertical name, pricing block, page URL, opener line, unit price) for the 14 ICP verticals.
  • address.ts — Address formatting & selection helpers (formatAddressOneLine, formatAddressTwoLine, getDefaultBillingAddress, getDefaultShippingAddress, billing/shipping type predicates).

Visibility / RBAC

  • visibility.ts — Role-based visibility (RBAC v1). ADMIN and NICK roles see everything; REP / OPERATOR see only companies where assignedRepId matches, and entities derived through those companies. Exports companyVisibilityFilter, viaCompanyVisibilityFilter, viaLeadCompanyVisibilityFilter, userCanAccessCompany, userCanAccessLead.

Communications

  • gmail.ts — Gmail API wrapper (raw fetch, no SDK). getPrimaryGmailAccount / getGmailAccountForUser, OAuth access-token refresh, RFC-2822 MIME construction (with multipart attachments), and sendEmail. Sequence emails and operator replies send through here.
  • google-calendar.ts — Google Calendar API wrapper. getGoogleAccessToken (reuses the connected Gmail account's OAuth tokens), createCalendarEvent, updateCalendarEvent, deleteCalendarEvent, listCalendarEvents, and pushTaskToCalendar (one-way CRM-task → calendar-event push, keyed on GPS_CALENDAR_ID).
  • google-drive.ts — Google Drive API wrapper (uses the Gmail account's OAuth token). getDriveFileMetadata, fetchDriveFileContent, createDriveSharingLink — used to attach Drive files to outbound email.
  • sms.ts — SMS sending. sendSms tries the android-sms-gateway (capcom6) first and falls back to Twilio. Resolves a per-user gateway (getSmsGatewayForUser, decrypting the stored password) or the env-default gateway, gates on recipient business hours, and appends the operator's "- Name" signature.
  • resend.tssendTransactional — sends transactional email via Resend (invoice/payment/shipping notifications), with attachment support, and logs transactional_email_sent / transactional_email_failed activities.
  • slack.ts — Slack notification helper. postToSlack posts via chat.postMessage; typed wrappers for pipeline events (notifyDealWon, notifyPaymentReceived/notifyInvoicePaid, notifyOrderShipped, notifyInboundSms, notifyInboundEmail, notifyInvoiceViewed, notifyProposalSigned) and a rich notifyPositiveReply brief for PlusVibe replies.
  • ai.ts — AI drafting integration. Supports Anthropic (Claude) and OpenAI (GPT), provider/model selectable via DB Setting rows or env. draftReply (SMS/email drafts), summarizeLead, suggestNextAction.

Payments & invoicing

  • stripe.tsgetStripe() — memoized Stripe SDK client (API version 2026-03-25.dahlia).
  • quickbooks.ts — QuickBooks Online OAuth2 + Accounting API client. Stores OAuth tokens in DB Setting rows, mutex-guarded token refresh, exchangeCodeForTokens, generic qbRequest, syncCustomerToQB (with duplicate-name collision fallback), syncInvoiceToQB, syncPaymentToQB, syncItemsFromQB (catalog pull with a mass-deactivation guardrail), fetchInvoicePdf, and logQbSyncFailure.
  • payments/promote.tspromoteOnInvoicePaid — on first paid invoice, promotes the Company LEAD → CUSTOMER and the associated Lead to WON (with a most-recent-open-lead fallback when the invoice has no leadId).
  • agreement-render.tsrenderAgreementBody — substitutes {{customer_name}}, {{customer_address}}, {{effective_date}}, {{device_models}} into agreement/proposal templates; deriveDeviceModelsForAgreement derives device-model text from line items.

lib/email-templates/ — transactional email renderers

Each renderer returns { subject, bodyHtml, bodyText } and is consumed by sendTransactional.

  • types.tsInvoiceWithRelations Prisma payload type.
  • shared.ts — Brand-styled HTML shell (renderShell), escapeHtml, money, recipientFirstName, resolveInvoiceRecipientEmail (billing-contact preference logic), billingAddressLine, trackingUrl (carrier-aware).
  • invoice-sent.ts — "Invoice ready / pay here" email to the customer.
  • invoice-viewed.ts — Internal alert when a customer opens an invoice.
  • invoice-paid-alert.ts — Internal "invoice paid" alert to Nick.
  • payment-received.ts — Customer-facing payment receipt (QB PDF attached when available).
  • shipped.ts — Shipment notification with tracking link.

PlusVibe

  • plusvibe.ts — PlusVibe API wrapper (host api.plusvibe.ai). pauseLeadSequences / stopLeadSequences (pause/stop a lead's cold-email sequence when the CRM engages them), and fetchLeadEmails / persistThreadForLead (pull the PlusVibe unibox thread for a lead into EmailAccount / EmailThread / EmailMessage).

4. External Integrations

Secrets policy: only environment variable names are listed below. No secret values appear anywhere in this document.

Stripe — card payments

  • Use: hosted invoice payment (pay/[token]), checkout sessions, admin/manual charges, saved payment methods.
  • Lib: src/lib/stripe.ts (getStripe).
  • Inbound webhook: POST /api/webhooks/stripe — verifies the stripe-signature header via STRIPE_WEBHOOK_SECRET, handles checkout.session.completed and payment_intent.succeeded, records a Payment, updates the invoice (PAID / PARTIAL), then runs post-payment effects (QB payment sync, customer receipt + internal alert emails, Slack notify, lead/company promotion).
  • Env vars: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY.

QuickBooks Online — accounting sync

  • Use: sync customers, invoices, payments, and the product catalog; fetch invoice PDFs to attach to receipts.
  • Lib: src/lib/quickbooks.ts.
  • OAuth/API routes: api/quickbooks/connect, api/quickbooks/callback, api/quickbooks/disconnect, api/quickbooks/status, api/quickbooks/sync, api/quickbooks/sync-items. OAuth tokens + realm ID are stored as DB Setting rows (qb_access_token, qb_refresh_token, qb_realm_id, qb_token_expires_at, qb_oauth_state) — not env vars.
  • Inbound webhook: none.
  • Env vars: QB_CLIENT_ID, QB_CLIENT_SECRET, QB_REDIRECT_URI, QB_ENVIRONMENT (production vs sandbox).

Gmail / Google OAuth — email, calendar, drive

  • Use: send & sync operator/sequence email (Gmail), push CRM tasks to Google Calendar, attach Google Drive files to email. One Google OAuth consent grants all three scopes.
  • Libs: src/lib/gmail.ts, src/lib/google-calendar.ts, src/lib/google-drive.ts.
  • OAuth routes: api/auth/google/start and api/auth/google/callback (scopes: gmail.modify, calendar, userinfo.email, drive.readonly). OAuth tokens are stored per EmailAccount row in the DB.
  • Inbound webhook: none. Email is pulled via POST /api/inbox/sync (manual/triggered Gmail thread polling), and calendar sync is push-only (no inbound Google watcher).
  • Env vars: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI, GPS_CALENDAR_ID, GOOGLE_PLACES_API_KEY, NEXT_PUBLIC_GOOGLE_PLACES_API_KEY.

Resend — transactional email

  • Use: customer-facing transactional email (invoice sent/viewed, payment receipt, shipped) and internal alerts.
  • Lib: src/lib/resend.ts (sendTransactional).
  • Inbound webhook: none.
  • Env vars: RESEND_API_KEY, RESEND_FROM_EMAIL, RESEND_REPLY_TO.

Note: Resend handles transactional mail only. Sequence/cold-outreach email sends via Gmail OAuth (src/lib/gmail.ts), not Resend.

Slack — pipeline notifications

  • Use: pipeline event notifications (deal won, payment received, order shipped, inbound SMS/email, invoice viewed, proposal signed, PlusVibe positive reply).
  • Lib: src/lib/slack.ts.
  • Inbound webhook: none (outbound chat.postMessage only).
  • Env vars: SLACK_BOT_TOKEN, SLACK_CHANNEL_PIPELINE, SLACK_CHANNEL_POSITIVE_REPLIES (falls back to the pipeline channel), PUBLIC_BASE_URL (for deep links).

PlusVibe — cold-email outbound platform

  • Use: receive positive-reply leads, pause/stop a lead's cold-email sequence when the CRM engages them, pull unibox email threads.
  • Lib: src/lib/plusvibe.ts.
  • Inbound webhook: POST /api/webhooks/plusvibe — HMAC-SHA256 verified via the signature header against PLUSVIBE_WEBHOOK_SECRET; normalizes the payload, detects ICP from campaign_name, delegates to ingestLead, syncs the email thread, and fires a Slack notification.
  • Env vars: PLUSVIBE_API_KEY, PLUSVIBE_WORKSPACE_ID, PLUSVIBE_WEBHOOK_SECRET.

Cal.com — meeting booking

  • Use: receive self-hosted Cal.com booking events and turn them into CRM contacts / leads / Booking records.
  • Lib: none — handled entirely in the webhook route.
  • Inbound webhook: POST /api/webhooks/cal — HMAC-SHA256 verified via the x-cal-signature-256 header against CAL_WEBHOOK_SECRET; handles BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED (idempotent on calBookingUid).
  • Env vars: CAL_WEBHOOK_SECRET.

Android SMS Gateway (capcom6) + Twilio — SMS

  • Use: send/receive P2P SMS from Nick's real phone number via android-sms-gateway; Twilio is a fallback sender. Per-user gateways are supported (passwords encrypted at rest).
  • Lib: src/lib/sms.ts (send), src/lib/crypto.ts (password encryption).
  • Inbound webhook: POST /api/webhooks/sms-gateway — HMAC-SHA256 verified via x-signature + x-timestamp headers against SMS_GATEWAY_SECRET (event sms:received); records an inbound SmsMessage, updates the conversation, notifies Slack, and auto-pauses sequences on a high-confidence prospect match.
  • Env vars: SMS_GATEWAY_URL, SMS_GATEWAY_USERNAME, SMS_GATEWAY_PASSWORD, SMS_GATEWAY_SECRET, SMS_NICK_PHONE_NUMBER, TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE_NUMBER.

Enrichment providers (no webhooks)

ProviderPurposeLibEnv var
TrestlePhone line-type / SMS eligibilityenrichment.tsTRESTLE_API_KEY
Brave SearchBusiness search enrichmentenrichment.tsBRAVE_API_KEY
Hunter.ioEmail discoveryhunter.tsHUNTER_API_KEY
ProspeoMobile-number discoveryprospeo.tsPROSPEO_API_KEY
ReoonEmail verificationreoon.tsREOON_API_KEY
Google PlacesBusiness intel / hero photosgoogle-places.tsGOOGLE_PLACES_API_KEY

AI providers (no webhooks)

  • Anthropic and OpenAI, selectable via DB Setting (ai_provider, ai_model) or env. Lib: src/lib/ai.ts.
  • Env vars: ANTHROPIC_API_KEY, OPENAI_API_KEY, AI_PROVIDER, AI_MODEL.

5. The Background Worker

Entrypoint: src/worker.ts — run in production as npx tsx src/worker.ts (npm script worker; Docker worker-runner stage).

Responsibilities

The worker runs a single BullMQ worker on the sequence-steps queue (createSequenceWorker, concurrency 5). Its job is automated follow-up sequence processing.

  • Each job carries an optional sequenceRunId and stepNumber.
  • The handler calls processDueSequenceRuns(sequenceRunId), which queries SequenceRun rows that are status: ACTIVE with nextStepAt <= now (one specific run if a sequenceRunId is supplied; otherwise a batch of up to 25, ordered by nextStepAt).
  • For each due run it computes the next step. If no next step exists, the run is marked COMPLETED; otherwise it calls executeSequenceStep(runId, nextStepNumber) (in src/lib/sequences.ts), which performs the action (send_sms, send_email, create_task), logs a SequenceStepLog and an Activity, and schedules the following step.

Scheduling / loop model

  • The worker is event-driven via BullMQ delayed jobs, not a polling cron loop. Steps are enqueued with a delay (delayHours * 3600000 ms) by scheduleSequenceStep.
  • A sequence run is kicked off when a disposition tag is applied to a lead — src/app/api/leads/[id]/tags/route.ts calls scheduleSequenceStep for the first step and precreateTasksForRun to pre-create task steps.
  • When a delayed job fires, the handler re-queries due runs, so it also naturally drains any runs whose nextStepAt has elapsed.
  • Lifecycle handlers log ready, failed, and error events; SIGINT/SIGTERM trigger a graceful worker.close() then process.exit(0).

Connections

  • Redis: via src/lib/queue.ts — a memoized ioredis connection (REDIS_URL, maxRetriesPerRequest: null).
  • PostgreSQL: via the shared prisma client (src/lib/db.ts, DATABASE_URL). The worker process is otherwise self-contained — no HTTP server.

6. Inbound Webhooks & Scheduled Jobs

Webhook receivers (src/app/api/webhooks/)

All four verify a signature before processing and run on the Node.js runtime.

PathSourceSignature verificationWhat it does
POST /api/webhooks/stripeStripestripe-signature header, constructEvent with STRIPE_WEBHOOK_SECRETOn checkout.session.completed / payment_intent.succeeded: records a Payment, updates invoice status, syncs the payment to QuickBooks, sends receipt + internal alert emails, posts Slack, promotes lead/company.
POST /api/webhooks/plusvibePlusVibeHMAC-SHA256, signature header vs PLUSVIBE_WEBHOOK_SECRET (timingSafeEqual)Normalizes a positive-reply payload, detects ICP from campaign_name, calls ingestLead, syncs the PlusVibe email thread, creates a conversation, posts a Slack brief.
POST /api/webhooks/calCal.comHMAC-SHA256, x-cal-signature-256 header vs CAL_WEBHOOK_SECRETHandles BOOKING_CREATED / RESCHEDULED / CANCELLED; upserts Contact / Company / Lead and a Booking record; idempotent on calBookingUid.
POST /api/webhooks/sms-gatewayandroid-sms-gateway (capcom6)HMAC-SHA256, x-signature + x-timestamp headers vs SMS_GATEWAY_SECRETRecords an inbound SmsMessage, resolves the owning user from the destination number, updates the conversation + lead score, posts Slack, auto-pauses sequences on a high-confidence prospect match.

Scheduled / cron logic

  • No in-process cron scheduler. There is no node-cron, no setInterval loop, and no internal scheduler.
  • Time-based work is BullMQ-delayed-job driven (see §5). The sequence worker effectively acts as the scheduler: delayed jobs fire follow-up steps at their due time.
  • Pull-based sync jobs are operator/HTTP triggered, not scheduled:
    • POST /api/inbox/sync — polls Gmail for new messages across tracked threads (per assigned EmailAccount); records inbound/outbound EmailMessages, updates conversations, posts Slack, auto-pauses sequences on prospect replies.
    • api/quickbooks/sync and api/quickbooks/sync-items — on-demand QuickBooks customer/invoice/payment and catalog syncs.
  • If recurring execution of these pull jobs is desired, it must be driven by an external scheduler (host cron / uptime pinger) hitting these routes — there is nothing in-repo doing it.

Appendix: Complete Environment Variable Inventory

All process.env.* names referenced under src/ (names only — no values):

Core / infra

  • DATABASE_URL
  • REDIS_URL
  • JWT_SECRET
  • ENCRYPTION_KEY
  • PUBLIC_BASE_URL
  • NEXT_PUBLIC_APP_URL

Stripe

  • STRIPE_SECRET_KEY
  • STRIPE_WEBHOOK_SECRET
  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY

QuickBooks

  • QB_CLIENT_ID
  • QB_CLIENT_SECRET
  • QB_REDIRECT_URI
  • QB_ENVIRONMENT

Google (OAuth / Calendar / Places)

  • GOOGLE_CLIENT_ID
  • GOOGLE_CLIENT_SECRET
  • GOOGLE_REDIRECT_URI
  • GPS_CALENDAR_ID
  • GOOGLE_PLACES_API_KEY
  • NEXT_PUBLIC_GOOGLE_PLACES_API_KEY

Resend (transactional email)

  • RESEND_API_KEY
  • RESEND_FROM_EMAIL
  • RESEND_REPLY_TO

Slack

  • SLACK_BOT_TOKEN
  • SLACK_CHANNEL_PIPELINE
  • SLACK_CHANNEL_POSITIVE_REPLIES

PlusVibe

  • PLUSVIBE_API_KEY
  • PLUSVIBE_WORKSPACE_ID
  • PLUSVIBE_WEBHOOK_SECRET

Cal.com

  • CAL_WEBHOOK_SECRET

SMS (android-sms-gateway + Twilio)

  • SMS_GATEWAY_URL
  • SMS_GATEWAY_USERNAME
  • SMS_GATEWAY_PASSWORD
  • SMS_GATEWAY_SECRET
  • SMS_NICK_PHONE_NUMBER
  • TWILIO_ACCOUNT_SID
  • TWILIO_AUTH_TOKEN
  • TWILIO_PHONE_NUMBER

Enrichment providers

  • TRESTLE_API_KEY
  • BRAVE_API_KEY
  • HUNTER_API_KEY
  • PROSPEO_API_KEY
  • REOON_API_KEY

AI

  • ANTHROPIC_API_KEY
  • OPENAI_API_KEY
  • AI_PROVIDER
  • AI_MODEL