WWebBuddyMarketplace
Install and run

Configuration

Every environment variable Subsweep reads, with defaults and aliases, which ones do nothing yet, and the settings that live in code rather than in a panel.

8 min readUpdated Oct 17, 2026Applies to Subsweep 0.1.0

On this page

Subsweep is configured entirely through environment variables. There is no admin settings screen in version 0.1.0: keys, email, billing and feature switches are all read from the environment when the server starts. Workspace owners change their own workspace (name, currency, digest timing, notifications) under Settings in the app.

On your own server, put the variables in .env.production.local (Next.js loads it for both the build and npm start). On Vercel, add them under the project's environment variables.

Important

Variables starting with NEXT_PUBLIC_ are compiled into the browser bundle. Set them before npm run build, and rebuild after changing them.

How values are read

  • An empty value counts as unset, so FOO= behaves like leaving FOO out.
  • Trailing newlines and a literal \n at the end of a value are stripped, which helps when values are piped into a host's CLI.
  • Where a variable has aliases, the first non-empty one wins, in the order listed.
  • NODE_ENV=production (set automatically by next build and next start) switches off every development fallback and turns on the production checks below.

Required in production

Until all five are set, npm run build fails and a running server answers every request (including /api/health) with a 500 error, logging Missing production env: ....

Variable Aliases What it does
DATABASE_URL POSTGRES_URL, POSTGRES_PRISMA_URL, DATABASE_URL_UNPOOLED, POSTGRES_URL_NON_POOLING Postgres connection string. In development, leaving it empty uses PGlite in .data/subsweep.
BETTER_AUTH_SECRET AUTH_SECRET Signs sessions and sign-in tokens. Development falls back to a fixed built-in string.
BETTER_AUTH_URL NEXT_PUBLIC_APP_URL Your public origin, with no trailing slash. Builds every link, OAuth redirect URI and Stripe return URL. Default http://localhost:3000.
ENCRYPTION_KEY None Encrypts stored bank, email, Slack and accounting tokens with AES-256-GCM. 64 hex characters are used as the key directly; any other string is hashed (SHA-256) into one. In development an empty value derives the key from the auth secret.
CRON_SECRET JOB_SECRET Protects /api/cron/tick and /api/jobs/tick. If both are set, JOB_SECRET is the one checked. In development an empty value leaves the endpoint open.

App and sign-in

Variable Default What it does
NEXT_PUBLIC_APP_URL Unset Fallback for BETTER_AUTH_URL. Set it to the same origin.
DEMO_LOGIN_ENABLED On in development, off in production Shows the one-click demo shop on the sign-in page and enables /api/demo/login. Production needs the exact value true; development is on unless the value is exactly false. While it is off, the published demo password no longer signs in. See The demo account.
ALLOW_UNVERIFIED_SIGNUP Off Only matters in production with neither SMTP nor Resend. Exactly true accepts email sign-ups whose address is never verified. Magic links stay refused.
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET Unset "Continue with Google" appears when both are set. Redirect URI: <BETTER_AUTH_URL>/api/auth/callback/google. Also used for Gmail scanning when the Gmail variables are empty.
PORT 3000 Port for npm start. You can also pass -p to next start.

Passwords need at least 10 characters, and sessions last 30 days. When an email provider is configured in production, new email accounts must verify their address before they can sign in. In production without an email provider, email sign-up is refused ("Sign-up is unavailable because email delivery isn't set up, so we can't verify your address.") unless ALLOW_UNVERIFIED_SIGNUP=true, and magic links are refused in every case. Signing in with an existing password and Google sign-in still work. Development accepts sign-ups without verification.

Outbound email

SMTP is used when a user and password are set. Resend is used when an API key is set. If both are configured, SMTP is tried first and Resend is the fallback. With neither, nothing is sent: each message is logged as [subsweep] Email to .... Development also prints magic links in the log; production refuses email sign-up and magic links instead (see App and sign-in).

Variable Aliases Default What it does
SMTP_HOST None smtp.gmail.com Mail server.
SMTP_PORT None 465 Mail server port.
SMTP_SECURE None true Implicit TLS. Set false for port 587 (STARTTLS) or 25.
SMTP_USER GMAIL_SMTP_USER Unset SMTP username. SMTP is off until this and the password are set.
SMTP_PASS GMAIL_SMTP_PASS, GMAIL_APP_PASSWORD Unset SMTP password. Spaces are removed, so a Gmail app password can be pasted as shown.
SMTP_FROM RESEND_FROM, EMAIL_FROM Subsweep <SMTP_USER> Sender for SMTP.
RESEND_API_KEY None Unset Turns on Resend.
RESEND_FROM EMAIL_FROM, SMTP_FROM Subsweep <hello@localhost> Sender for Resend. Must use a domain verified in Resend.
EMAIL_PAUSED None Off Exactly true stops every outgoing email (sign-in links included) and logs it instead.

Email is sent for: email verification, magic links, team invites, renewal reminders, manual charge checks, new-subscription, price-increase and trial alerts, digests, the monthly statement reminder and the account-deleted confirmation. Addresses ending in @northwind.studio (the demo team) are never mailed.

Billing

See Plans, billing and selling it as SaaS for the setup steps.

Variable What it does
STRIPE_SECRET_KEY Turns on Stripe Checkout and the customer portal.
STRIPE_WEBHOOK_SECRET The endpoint's signing secret, used to verify POST /api/webhooks/stripe. Required for the webhook in every environment: without it the endpoint answers 503 to every event, and an unsigned or wrongly signed event gets 400.
STRIPE_PRICE_PRO_MONTHLY Price ID for Pro billed monthly. Also used for yearly when the yearly price is empty.
STRIPE_PRICE_PRO_YEARLY Price ID for Pro billed yearly. Also used for monthly when the monthly price is empty.
STRIPE_PRICE_ACCOUNTANT Price ID for the Accountant plan, charged per client with a quantity of at least 3.

Bank feeds

Variable Default What it does
PLAID_CLIENT_ID, PLAID_SECRET Unset Plaid for US and Canadian workspaces.
PLAID_ENV sandbox sandbox, development or production.
PLAID_REDIRECT_URI Unset Passed to Plaid Link for OAuth institutions.
TRUELAYER_CLIENT_ID, TRUELAYER_CLIENT_SECRET Unset TrueLayer for UK workspaces. Redirect URI: <BETTER_AUTH_URL>/api/oauth/truelayer/callback.
TRUELAYER_ENV Sandbox Exactly production uses the live TrueLayer hosts.
SALTEDGE_APP_ID, SALTEDGE_SECRET Unset Salt Edge for EU workspaces. Not tested against a live Salt Edge account; see Integrations.
SETU_AA_ENABLED false India Account Aggregator through Setu. Off unless exactly true and both Setu keys are set.
SETU_CLIENT_ID, SETU_CLIENT_SECRET Unset Setu credentials.
SETU_ENV Sandbox Exactly production uses the live Setu host.

What each provider actually does in this version is on Integrations.

Email receipt scanning

Variable Default What it does
GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET The Google sign-in client Google OAuth client for Gmail scanning (gmail.readonly). Redirect URI: <BETTER_AUTH_URL>/api/oauth/gmail/callback.
MICROSOFT_CLIENT_ID, MICROSOFT_CLIENT_SECRET Unset Microsoft app for Outlook scanning (Mail.Read). Redirect URI: <BETTER_AUTH_URL>/api/oauth/outlook/callback.
MICROSOFT_TENANT common Tenant ID or domain for single-tenant apps.

Slack, accounting and usage

Variable What it does
SLACK_CLIENT_ID, SLACK_CLIENT_SECRET Slack app for posting alerts. Redirect URI: <BETTER_AUTH_URL>/api/oauth/slack/callback.
QUICKBOOKS_CLIENT_ID, QUICKBOOKS_CLIENT_SECRET QuickBooks Online connection. Redirect URI: <BETTER_AUTH_URL>/api/oauth/quickbooks/callback.
XERO_CLIENT_ID, XERO_CLIENT_SECRET Xero connection. Redirect URI: <BETTER_AUTH_URL>/api/oauth/xero/callback.
GOOGLE_WORKSPACE_CLIENT_ID, GOOGLE_WORKSPACE_CLIENT_SECRET Usage connection for Google Workspace. No screen links to it, and no data is fetched in 0.1.0.
NOTION_CLIENT_ID, NOTION_CLIENT_SECRET Usage connection for Notion. Same status.
ZOOM_CLIENT_ID, ZOOM_CLIENT_SECRET Usage connection for Zoom. Same status.

Read Integrations before relying on Slack, QuickBooks or Xero: each has a gap in version 0.1.0.

Storage, exchange rates and jobs

Variable Default What it does
R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET Unset Cloudflare R2 for attachments. When all four are set, uploads go to R2 instead of .data/uploads. See the limitation.
R2_PUBLIC_URL Unset Public base URL for R2 files.
OPEN_EXCHANGE_RATES_APP_ID Unset Live USD-based exchange rates, refreshed by the hourly job. Without it, fixed fallback rates are used (1 USD = 83 INR, 0.78 GBP, 0.92 EUR, 1.36 CAD).
INNGEST_EVENT_KEY Unset Sends one subsweep/jobs.tick event to Inngest each time the hourly job runs. It does not run any jobs in Inngest.

Analytics and errors

Variable What it does
NEXT_PUBLIC_SENTRY_DSN Loads Sentry's browser bundle and reports front-end errors (10% trace sampling). Server errors are not sent.
NEXT_PUBLIC_POSTHOG_KEY, NEXT_PUBLIC_POSTHOG_HOST Intended for PostHog analytics, but the loader does not initialise PostHog in 0.1.0.

Variables that do nothing yet

These appear in .env.example but nothing in version 0.1.0 reads them:

  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY (Checkout is a redirect, so it is not needed)
  • SLACK_SIGNING_SECRET
  • INNGEST_SIGNING_KEY
  • SENTRY_DSN (only the NEXT_PUBLIC_ variant is used)

QUICKBOOKS_ENV is read but has no effect: the export always calls QuickBooks' sandbox API.

Values that live in code

Several things a SaaS owner might expect to configure are constants in the source. Change them there and rebuild.

What Value Where
Free plan subscription cap 10 lib/billing/plan.ts (FREE_SUBSCRIPTION_CAP)
Trial length 14 days lib/workspace/provision.ts, and app/api/clients/route.ts for client workspaces
Plan names, prices and bullet points shown in the app Pro $24/mo or $199/yr, Accountant $15 per client lib/billing/plan.ts, app/(marketing)/pricing/page.tsx, the billing tab in app/(app)/settings/page.tsx, and the landing page in components/landing/landing-experience.tsx
Prices created in Stripe by the setup script 2400, 19900 and 1500 cents, USD scripts/setup-stripe-prices.mjs
Renewal reminder schedule 30, 14 and 7 days before yearly charges or charges of 100.00 or more, otherwise 3 days lib/jobs/reminders.ts
Negotiation suggestions Over 100 a month, renewing within 60 days app/(app)/actions/page.tsx
Countries offered in onboarding United States, Canada, United Kingdom, Germany, India lib/country.ts
Currencies USD, CAD, GBP, EUR, INR lib/fx.ts
Share link lifetime, invite lifetime 30 days, 7 days app/api/share/route.ts, app/api/team/route.ts
Upload limits Statements 10 MB; attachments 20 MB, 10 per subscription app/api/sources/upload/route.ts, app/api/attachments/route.ts
Demo account priya@northwind.studio, Northwind Studio lib/demo-account.ts
Product name in emails and the app Subsweep lib/email/templates.ts, lib/email/send.ts, app/layout.tsx and the components

A complete production example

.env
# --- Required ---
DATABASE_URL=postgres://user:password@db-host:5432/subsweep
BETTER_AUTH_SECRET=base64-32-bytes
BETTER_AUTH_URL=https://subs.example.com
ENCRYPTION_KEY=64-hex-characters
CRON_SECRET=random-string

# --- Recommended ---
NEXT_PUBLIC_APP_URL=https://subs.example.com
# The demo shop stays off in production unless DEMO_LOGIN_ENABLED=true

# --- Email (without it, production refuses email sign-up and magic links) ---
SMTP_HOST=smtp.example.com
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USER=postmaster@example.com
SMTP_PASS=...
SMTP_FROM=Subsweep <no-reply@example.com>

# --- Google sign-in (optional) ---
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

# --- Stripe (optional; needed to charge customers) ---
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_PRO_MONTHLY=price_...
STRIPE_PRICE_PRO_YEARLY=price_...
STRIPE_PRICE_ACCOUNTANT=price_...

# --- Bank feeds (optional) ---
PLAID_CLIENT_ID=
PLAID_SECRET=
PLAID_ENV=production

# --- Exchange rates (optional) ---
OPEN_EXCHANGE_RATES_APP_ID=