-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path.env.example
More file actions
154 lines (135 loc) · 9.23 KB
/
Copy path.env.example
File metadata and controls
154 lines (135 loc) · 9.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# ═══════════════════════════════════════════════════════════════════════════
# ClientFlow - Environment Variables
# ═══════════════════════════════════════════════════════════════════════════
# Copy this file to `.env` and fill in real values before running the app.
# See docs/DEPLOYMENT.md for a setup runbook.
# ───────────────────────────────────────────────────────────────────────────
# ─── Core app ──────────────────────────────────────────────────────────────
NEXT_PUBLIC_APP_URL=http://localhost:3000
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=replace-with-a-secure-random-secret
BETTER_AUTH_REQUIRE_EMAIL_VERIFICATION=false
# ─── Database (Neon Postgres) ──────────────────────────────────────────────
NEON_DATABASE_URL=postgresql://<user>:<password>@<host>/<database>?sslmode=require
# ─── Rate limiting (Upstash Redis) - REQUIRED ──────────────────────────────
# The proxy middleware (proxy.ts) enforces rate limits on /api/auth/* and /api/*
# via these credentials. The app will fail at request time if unset.
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
# ─── File storage (Cloudinary) ─────────────────────────────────────────────
CLOUDINARY_CLOUD_NAME=
CLOUDINARY_API_KEY=
CLOUDINARY_API_SECRET=
# ─── Email delivery ────────────────────────────────────────────────────────
# Pick ONE provider. If both EmailJS and Resend vars are set, EmailJS wins.
#
# Option A - EmailJS (browser-style templates)
EMAILJS_PUBLIC_KEY=
EMAILJS_PRIVATE_KEY=
EMAILJS_SERVICE_ID=
EMAILJS_TEMPLATE_ID=
EMAILJS_TRANSACTIONAL_TEMPLATE_ID=
# EMAILJS_TRANSACTIONAL_TEMPLATE_ID requires a template with variables:
# {{to_email}}, {{subject}}, {{html_content}}
# Set the template body type to HTML in the EmailJS dashboard.
#
# Option B - Resend (recommended for production)
RESEND_API_KEY=
EMAIL_FROM="ClientFlow <no-reply@yourdomain.com>"
# Reply-to / support address shown in email footers
RESEND_REPLY_TO_EMAIL=support@yourdomain.com
# Resend webhook signing secret (from Resend dashboard → Webhooks → endpoint).
# Protects /api/webhooks/resend against forged bounce / complaint events.
RESEND_WEBHOOK_SECRET=whsec_...
# ─── OAuth (optional) ──────────────────────────────────────────────────────
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# ─── Web push notifications ────────────────────────────────────────────────
# Generate VAPID keys with: npx web-push generate-vapid-keys
NEXT_PUBLIC_VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
VAPID_SUBJECT=mailto:admin@yourdomain.com
# ─── Stripe billing ────────────────────────────────────────────────────────
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Legacy fallback - only used if a plan has no stripe_monthly_price_id in DB.
# DB-driven plans (created via admin UI) populate these IDs automatically.
# Run `npm run stripe:backfill-plans` to retro-fit pre-seeded plans.
STRIPE_PRICE_ID_STARTER=price_...
STRIPE_PRICE_ID_PROFESSIONAL=price_...
# ─── Seed overrides (dev / CI) ─────────────────────────────────────────────
SEED_ORGANIZATION_NAME=ClientFlow Seed Org
SEED_ORGANIZATION_SLUG=clientflow-seed-org
SEED_ADMIN_NAME=ClientFlow Admin
SEED_ADMIN_EMAIL=admin@clientflow.local
SEED_ADMIN_PASSWORD=Admin@123456
SEED_USER_NAME=ClientFlow User
SEED_USER_EMAIL=user@clientflow.local
SEED_USER_PASSWORD=User@123456
# ─── Bot protection (Cloudflare Turnstile) ─────────────────────────────────
# When BOTH are set, /contact, /sign-up and /forgot-password require a valid
# Turnstile token. When unset, the widget hides and server-side checks soft-pass
# so local dev isn't blocked. Get keys from Cloudflare dashboard → Turnstile.
NEXT_PUBLIC_TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
# ─── Product analytics (PostHog) ───────────────────────────────────────────
# Public env vars - exposed to the browser. Loaded only after the user grants
# analytics consent (see lib/consent.ts and components/analytics/PostHogProvider).
# Get the key from PostHog → Project Settings → Project API Keys.
NEXT_PUBLIC_POSTHOG_KEY=
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
# ─── India GST (platform-side seller info) ─────────────────────────────────
# Two-digit GST state code for the platform's GST registration (e.g. "27" for
# Maharashtra, "29" for Karnataka). When set AND a customer org has a GSTIN
# on file AND the invoice currency is INR, every Stripe-paid invoice gets a
# GST breakdown (CGST/SGST for intra-state, IGST for inter-state) snapshotted
# onto the invoice row at `invoice.paid` time, alongside HSN/SAC code 998314
# and the buyer's GSTIN. Leave blank for non-Indian platforms - the GST module
# soft-skips rather than failing when this is unset.
#
# Note: this only captures the *snapshot* on the invoice. Stripe invoices are
# still gross-priced; full Stripe Tax integration (which reverse-engineers the
# pricing into a tax-exclusive base + tax line) is the next step.
PLATFORM_GST_STATE_CODE=
# ─── Async background jobs (Inngest) ───────────────────────────────────────
# Inngest hosts a serverless queue we use to decouple email sends (and
# eventually other side effects like webhook dispatch) from request handlers.
# When INNGEST_EVENT_KEY is unset, the app falls back to synchronous email
# sends so local dev and prod-without-Inngest both work fine - the queue is
# strictly an opt-in production hardening.
#
# Operator setup:
# 1. Sign up at inngest.com (free tier covers low-volume launch traffic).
# 2. Create an "app" - copy the Event Key + Signing Key here.
# 3. Set the app's "Serve URL" to https://client-flow.in/api/inngest.
INNGEST_EVENT_KEY=
INNGEST_SIGNING_KEY=
# ─── Cron jobs ─────────────────────────────────────────────────────────────
# Shared secret for cron-job.org → /api/cron/* endpoints.
# Generate: `openssl rand -hex 32` or `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
# Also paste the SAME value (with "Bearer " prefix) into every cron-job.org
# entry's Authorization header.
CRON_SECRET=replace-with-a-random-hex-string
# ─── Slack integration (optional) ─────────────────────────────────────────
# Powers the "Add to Slack" install flow under Settings → Integrations.
# Create the app at https://api.slack.com/apps → "Create New App" → "From
# scratch", then enable Incoming Webhooks and add `incoming-webhook` +
# `chat:write` to OAuth & Permissions → Bot Token Scopes. Set the redirect
# URL to {NEXT_PUBLIC_APP_URL}/api/integrations/slack/callback for both
# localhost and prod.
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=
SLACK_SIGNING_SECRET=
# ─── Security (CSP rollback switch) ────────────────────────────────────────
# Production sends Content-Security-Policy in *enforcing* mode (the browser
# blocks resources outside the directive list in next.config.ts). If a real
# user-impacting block lands, set CSP_REPORT_ONLY=1 in Vercel env and redeploy
# - the header key flips to Content-Security-Policy-Report-Only and nothing
# is blocked, while violations still surface in the browser console for
# diagnosis. Unset (or set to anything other than "1") to restore enforcing.
CSP_REPORT_ONLY=
# ─── Testing (E2E) ─────────────────────────────────────────────────────────
PLAYWRIGHT_BASE_URL=http://localhost:3000
TEST_USER_EMAIL=playwright@test.clientflow.dev
TEST_USER_PASSWORD=Playwright123!