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)
| Component | Version / Detail |
|---|---|
| Next.js | 16.2.2 (App Router, output: "standalone") |
| React / React DOM | 19.2.4 |
| TypeScript | ^5 |
| Node runtime | node:22-bookworm-slim (Docker base image) |
| ORM | Prisma ^7.7.0 with @prisma/adapter-pg (driver adapter over a pg Pool) |
| Database | PostgreSQL 16 |
| Queue / cache | Redis 7, driven by BullMQ ^5.73.1 over ioredis ^5.10.1 |
| Payments SDK | stripe ^22.0.1, @stripe/react-stripe-js, @stripe/stripe-js |
| Email SDK | resend ^6.12.2 |
| Auth | jsonwebtoken (JWT, 7-day expiry) + bcryptjs (password hashing, cost 12) |
| Calendar UI | FullCalendar ^6.1.20 |
| Misc | papaparse (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:
- Web app — Next.js standalone server (
node server.js). Serves the UI, App Router API routes, and inbound webhooks. - Sequence worker —
npx tsx src/worker.ts. A BullMQ worker that drains thesequence-stepsqueue and executes automated follow-up steps.
Build & deployment
- Standalone build:
next.config.tssetsoutput: "standalone". The production image copies.next/standalone,.next/static,public/,prisma/, andsrc/generatedinto a slim runtime stage. - Multi-stage
Dockerfile: abasestage installs deps, runsprisma generate, andnext build. Two runtime stages share that base:worker-runner—CMD ["npx", "tsx", "src/worker.ts"]runner—CMD ["node", "server.js"](the last stage, so a baredocker 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 acaddyreverse proxy. Postgres maps host port54329, Redis63799.docker-compose.prod.yml: a singleappcontainer (gps-crm-v2) on the externalcrm_defaultnetwork, no compose-managed DB/Redis.deploy.sh: SSHes tohetzner-gps, pullsorigin/main, rebuilds with--no-cache, stops/removes the oldgps-crm-v2container, and runs the new one with--env-file /opt/gpsleaders/apps/.env.prod, then hits/api/health.- Health check:
GET /api/healthreturns 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 singletonprismaclient built on apg.Pool+PrismaPgadapter. Memoized onglobalThisto survive hot reloads.auth.ts— Server-side auth.hashPassword/verifyPassword(bcrypt),signToken/verifyToken(JWT, 7-day expiry, requiresJWT_SECRET),getAuthUser(req)(resolves the user from theAuthorizationheader ortokencookie and confirmsisActive),unauthorized(), andrequireAdmin(req)(403 unless roleADMIN).auth-client.ts— Browser-side token helpers:getToken(),authHeaders(),clearAuth()(reads/writeslocalStorage+ thetokencookie).crypto.ts— AES-256-GCM secret encryption.encryptSecret/decryptSecretserialize asiv:tag:ciphertext(base64). Requires a 32-byteENCRYPTION_KEY(hex or base64). Used to store per-user SMS gateway passwords at rest.external-fetch.ts— Hardenedfetchwrapper for third-party APIs: 8 s timeout, up to 2 retries with exponential backoff + jitter, honorsRetry-After, classifies failures asprovider_error/rate_limited. Used by every enrichment integration.constants.ts— Domain constants: the 14-verticalICP_MAP, the 10PIPELINE_STAGES+ labels/colors, company-status options, disposition & pre-call tag lists,SMS_BUSINESS_HOURS(8 AM–8 PM), product defaults.format.ts—formatMoney,formatDate,emptyToNulldisplay helpers.display.ts—displayContactName(contact, company)— preferred label fallback chain: full name → company name → email → empty.phone.ts—normalizeToE164vialibphonenumber-js(defaults to US).docs.ts— Readssrc/content/docs/*.mdhelp articles, parses frontmatter withgray-matter;getAllArticles/getArticle.settings-nav.ts— Static settings-page nav link list (SETTINGS_LINKS).
Queue & background processing
queue.ts— BullMQ setup. Oneioredisconnection (memoized,maxRetriesPerRequest: null),getSequenceQueue()(queue"sequence-steps"), andcreateSequenceWorker(handler)(concurrency 5).sequences.ts— The sequence execution engine.scheduleSequenceStepenqueues a delayed BullMQ job;executeSequenceSteploads aSequenceRun, 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_sequencesare handled inline in the tags route), logsSequenceStepLog+Activity, and schedules the next step or marks the runCOMPLETED. Has idempotency guards against duplicate sends.sequence-tasks.ts— Sequence task lifecycle.precreateTasksForRuncreates allcreate_tasksteps up-front with futuredueAtand pushes them to Google Calendar;cancelSequenceRunmarks a runCANCELEDand deletes pending future tasks + their calendar events;pauseActiveSequenceRunspauses active runs on a lead (triggered by inbound SMS/email), logs the pause, and calls PlusVibe to pause the upstream email sequence.step-summary.ts—summarizeStepproduces a human-readable description of a sequence step for the automations UI.template-resolve.ts—resolveTemplate/previewTemplatesubstitute{{contact.*}},{{company.name}}, and{{icp.*}}placeholders in message-template bodies.
Lead lifecycle & enrichment
lead-ingest.ts— Shared lead-ingestion service.ingestLeadproduces a canonical Company / Contact / Lead shape regardless of source (PlusVibe webhook or Maps CSV import): dedups company (bygooglePlaceIdornormalizedName) and contact (byemailNormalized), runs phone + business enrichment, applies ICP and SMS-eligibility tags, and seeds aLeadScore.lead-stage.ts—changeLeadStagetransitions a lead's pipeline stage inside a transaction, writes astage_changedactivity, setswonAt/lostAt, fires a Slack "deal won" notification onWON, and revalidates the dashboard cache.lead-tags.ts—applyLeadTagidempotently applies (or revives) aLeadTagfor a given tag slug; rejects unknown/inactive tags.enrichment.ts—lookupPhone(Trestle phone-intel — line type, carrier, SMS eligibility),searchBusiness(Brave Search — rating, review count, website, description), and the deprecated combinedenrichLead.hunter.ts— Hunter.io email discovery:findEmailByName(email-finder) andfindEmailsByDomain(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.ts—fetchGooglePlacesIntel— Places "searchText" + details to obtain rating, review count, Maps URL, and a hero photo.linkedin-sniff.ts—sniffLinkedinOrMapsUrlclassifies a raw URL as a contact LinkedIn, company LinkedIn, or Google Maps place URL.cache-business-photo.ts—cacheBusinessPhotodownloads a business photo (direct URL or Google Places media) todata/lead-photos/<companyId>.<ext>and returns a/api/lead-photos/...path. Writes to a non-public data dir (standalone-build constraint).icp-vars.ts—getIcpVars(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).ADMINandNICKroles see everything;REP/OPERATORsee only companies whereassignedRepIdmatches, and entities derived through those companies. ExportscompanyVisibilityFilter,viaCompanyVisibilityFilter,viaLeadCompanyVisibilityFilter,userCanAccessCompany,userCanAccessLead.
Communications
gmail.ts— Gmail API wrapper (rawfetch, no SDK).getPrimaryGmailAccount/getGmailAccountForUser, OAuth access-token refresh, RFC-2822 MIME construction (with multipart attachments), andsendEmail. 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, andpushTaskToCalendar(one-way CRM-task → calendar-event push, keyed onGPS_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.sendSmstries 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.ts—sendTransactional— sends transactional email via Resend (invoice/payment/shipping notifications), with attachment support, and logstransactional_email_sent/transactional_email_failedactivities.slack.ts— Slack notification helper.postToSlackposts viachat.postMessage; typed wrappers for pipeline events (notifyDealWon,notifyPaymentReceived/notifyInvoicePaid,notifyOrderShipped,notifyInboundSms,notifyInboundEmail,notifyInvoiceViewed,notifyProposalSigned) and a richnotifyPositiveReplybrief for PlusVibe replies.ai.ts— AI drafting integration. Supports Anthropic (Claude) and OpenAI (GPT), provider/model selectable via DBSettingrows or env.draftReply(SMS/email drafts),summarizeLead,suggestNextAction.
Payments & invoicing
stripe.ts—getStripe()— memoized Stripe SDK client (API version2026-03-25.dahlia).quickbooks.ts— QuickBooks Online OAuth2 + Accounting API client. Stores OAuth tokens in DBSettingrows, mutex-guarded token refresh,exchangeCodeForTokens, genericqbRequest,syncCustomerToQB(with duplicate-name collision fallback),syncInvoiceToQB,syncPaymentToQB,syncItemsFromQB(catalog pull with a mass-deactivation guardrail),fetchInvoicePdf, andlogQbSyncFailure.payments/promote.ts—promoteOnInvoicePaid— on first paid invoice, promotes the CompanyLEAD → CUSTOMERand the associated Lead toWON(with a most-recent-open-lead fallback when the invoice has noleadId).agreement-render.ts—renderAgreementBody— substitutes{{customer_name}},{{customer_address}},{{effective_date}},{{device_models}}into agreement/proposal templates;deriveDeviceModelsForAgreementderives device-model text from line items.
lib/email-templates/ — transactional email renderers
Each renderer returns { subject, bodyHtml, bodyText } and is consumed by
sendTransactional.
types.ts—InvoiceWithRelationsPrisma 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 (hostapi.plusvibe.ai).pauseLeadSequences/stopLeadSequences(pause/stop a lead's cold-email sequence when the CRM engages them), andfetchLeadEmails/persistThreadForLead(pull the PlusVibe unibox thread for a lead intoEmailAccount/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 thestripe-signatureheader viaSTRIPE_WEBHOOK_SECRET, handlescheckout.session.completedandpayment_intent.succeeded, records aPayment, 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 DBSettingrows (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(productionvs 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/startandapi/auth/google/callback(scopes:gmail.modify,calendar,userinfo.email,drive.readonly). OAuth tokens are stored perEmailAccountrow 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.postMessageonly). - 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 thesignatureheader againstPLUSVIBE_WEBHOOK_SECRET; normalizes the payload, detects ICP fromcampaign_name, delegates toingestLead, 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 /
Bookingrecords. - Lib: none — handled entirely in the webhook route.
- Inbound webhook:
POST /api/webhooks/cal— HMAC-SHA256 verified via thex-cal-signature-256header againstCAL_WEBHOOK_SECRET; handlesBOOKING_CREATED,BOOKING_RESCHEDULED,BOOKING_CANCELLED(idempotent oncalBookingUid). - 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 viax-signature+x-timestampheaders againstSMS_GATEWAY_SECRET(eventsms:received); records an inboundSmsMessage, 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)
| Provider | Purpose | Lib | Env var |
|---|---|---|---|
| Trestle | Phone line-type / SMS eligibility | enrichment.ts | TRESTLE_API_KEY |
| Brave Search | Business search enrichment | enrichment.ts | BRAVE_API_KEY |
| Hunter.io | Email discovery | hunter.ts | HUNTER_API_KEY |
| Prospeo | Mobile-number discovery | prospeo.ts | PROSPEO_API_KEY |
| Reoon | Email verification | reoon.ts | REOON_API_KEY |
| Google Places | Business intel / hero photos | google-places.ts | GOOGLE_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
sequenceRunIdandstepNumber. - The handler calls
processDueSequenceRuns(sequenceRunId), which queriesSequenceRunrows that arestatus: ACTIVEwithnextStepAt <= now(one specific run if asequenceRunIdis supplied; otherwise a batch of up to 25, ordered bynextStepAt). - For each due run it computes the next step. If no next step exists, the run
is marked
COMPLETED; otherwise it callsexecuteSequenceStep(runId, nextStepNumber)(insrc/lib/sequences.ts), which performs the action (send_sms,send_email,create_task), logs aSequenceStepLogand anActivity, 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 * 3600000ms) byscheduleSequenceStep. - A sequence run is kicked off when a disposition tag is applied to a lead —
src/app/api/leads/[id]/tags/route.tscallsscheduleSequenceStepfor the first step andprecreateTasksForRunto pre-create task steps. - When a delayed job fires, the handler re-queries due runs, so it also
naturally drains any runs whose
nextStepAthas elapsed. - Lifecycle handlers log
ready,failed, anderrorevents;SIGINT/SIGTERMtrigger a gracefulworker.close()thenprocess.exit(0).
Connections
- Redis: via
src/lib/queue.ts— a memoizedioredisconnection (REDIS_URL,maxRetriesPerRequest: null). - PostgreSQL: via the shared
prismaclient (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.
| Path | Source | Signature verification | What it does |
|---|---|---|---|
POST /api/webhooks/stripe | Stripe | stripe-signature header, constructEvent with STRIPE_WEBHOOK_SECRET | On 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/plusvibe | PlusVibe | HMAC-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/cal | Cal.com | HMAC-SHA256, x-cal-signature-256 header vs CAL_WEBHOOK_SECRET | Handles BOOKING_CREATED / RESCHEDULED / CANCELLED; upserts Contact / Company / Lead and a Booking record; idempotent on calBookingUid. |
POST /api/webhooks/sms-gateway | android-sms-gateway (capcom6) | HMAC-SHA256, x-signature + x-timestamp headers vs SMS_GATEWAY_SECRET | Records 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, nosetIntervalloop, 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 assignedEmailAccount); records inbound/outboundEmailMessages, updates conversations, posts Slack, auto-pauses sequences on prospect replies.api/quickbooks/syncandapi/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_URLREDIS_URLJWT_SECRETENCRYPTION_KEYPUBLIC_BASE_URLNEXT_PUBLIC_APP_URL
Stripe
STRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
QuickBooks
QB_CLIENT_IDQB_CLIENT_SECRETQB_REDIRECT_URIQB_ENVIRONMENT
Google (OAuth / Calendar / Places)
GOOGLE_CLIENT_IDGOOGLE_CLIENT_SECRETGOOGLE_REDIRECT_URIGPS_CALENDAR_IDGOOGLE_PLACES_API_KEYNEXT_PUBLIC_GOOGLE_PLACES_API_KEY
Resend (transactional email)
RESEND_API_KEYRESEND_FROM_EMAILRESEND_REPLY_TO
Slack
SLACK_BOT_TOKENSLACK_CHANNEL_PIPELINESLACK_CHANNEL_POSITIVE_REPLIES
PlusVibe
PLUSVIBE_API_KEYPLUSVIBE_WORKSPACE_IDPLUSVIBE_WEBHOOK_SECRET
Cal.com
CAL_WEBHOOK_SECRET
SMS (android-sms-gateway + Twilio)
SMS_GATEWAY_URLSMS_GATEWAY_USERNAMESMS_GATEWAY_PASSWORDSMS_GATEWAY_SECRETSMS_NICK_PHONE_NUMBERTWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKENTWILIO_PHONE_NUMBER
Enrichment providers
TRESTLE_API_KEYBRAVE_API_KEYHUNTER_API_KEYPROSPEO_API_KEYREOON_API_KEY
AI
ANTHROPIC_API_KEYOPENAI_API_KEYAI_PROVIDERAI_MODEL