-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path.env.example
More file actions
160 lines (138 loc) · 7.83 KB
/
Copy path.env.example
File metadata and controls
160 lines (138 loc) · 7.83 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
155
156
157
158
# Postgres 16. The scan_runs table IS the job queue -- there is no Redis, so
# there is no second queue to forget to poll.
#
# ON A SERVERLESS HOST, USE THE TRANSACTION POOLER (Supabase port 6543). Port 5432 is
# the SESSION pooler: it holds one server connection per client for that client's whole
# life and caps the project at ~15, so a host that runs many concurrent function
# instances exhausts it. That fails as scattered 500s under load, not as a config error.
# Prepared statements are disabled in db.ts for the same reason.
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/rank_and_rent
E2E_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/rank_and_rent_e2e
# Session-mode URL for `pnpm db:push` ONLY. drizzle-kit takes advisory locks and issues
# DDL, neither of which survives a transaction pooler handing each statement a different
# backend. Unset falls back to DATABASE_URL, which is right for a local Postgres.
DIRECT_DATABASE_URL=
# MASTER SWITCH. Anything other than the exact string 'true' routes every
# provider call to the deterministic fixture implementations at zero cost.
# Defaulting to fixtures (rather than to live) means a missing/typo'd env var
# fails toward $0 rather than toward spending money.
LIVE_CALLS_ENABLED=false
# DataForSEO. HTTP Basic: base64(login:password).
#
# NOTE: DataForSEO enforces an IP WHITELIST. A non-whitelisted IP with perfectly
# valid credentials gets HTTP 200, top-level status_code 20000, status_message
# "Ok." -- and tasks[0].status_code 40207 with result: null. Read as "no
# results", that scores every SERP as a wide-open jackpot. Add your IP at
# https://app.dataforseo.com/api-access before running pnpm probe:dfs.
DATAFORSEO_LOGIN=
DATAFORSEO_PASSWORD=
# Hard ceiling per scan run, in whole US cents, checked BEFORE each purchase.
# A cold 40-niche locality costs ~24c (40 x [live SERP + map pack] + one
# batched link lookup), so 200 is ~8x headroom.
SCAN_BUDGET_CAP_CENTS=200
# ---------------------------------------------------------------------------
# Sites & CRM (voice agent)
# ---------------------------------------------------------------------------
# Retell API key -- WITH the webhook badge. Used for two different things:
# 1. verifying every inbound webhook signature
# 2. outbound reads (get-call backfill, recording download)
#
# There is NO bypass flag for signature verification. A SKIP_SIGNATURE env var
# would eventually ship and turn /api/retell/tool into an open write endpoint that
# anyone could POST fabricated leads to. `pnpm voice:simulate` signs its payloads
# with this key instead, so the verified path is also the tested path.
RETELL_API_KEY=
# Default inbound agent. Overridable per site.
RETELL_AGENT_ID=
# What the webhook URLs are built from, and what gets pasted into Retell and
# Twilio. In development this is an ngrok/cloudflared tunnel, NOT localhost --
# Retell has to be able to reach it.
PUBLIC_BASE_URL=http://localhost:3000
# Where call recordings are stored.
#
# Retell's recording_url is an S3 link with no documented lifetime -- and a hard
# TEN MINUTES when "Opt-Out of Personal and Sensitive Data Storage" is enabled. So
# the bytes are copied here and calls.recording_path, not the upstream URL, is what
# the UI reads. A CRM whose headline feature is "recordings of every call" cannot be
# built on a link someone else expires.
#
# LOCAL DISK ONLY. Resolved against the WORKSPACE ROOT, never process.cwd() -- the worker
# runs from the repo root and `next dev` from apps/web, and when this was cwd-relative they
# disagreed about what `.` meant: every file saved, every play button 404'd, and the browser
# rendered that as a 0-second clip.
RECORDINGS_DIR=./.recordings
# Vercel Blob, for hosts with no persistent disk. SET THIS INSTEAD OF RECORDINGS_DIR in
# production: its presence is what selects the blob backend.
#
# Objects are written with access:'private', so the blob URL is not a capability -- reads
# go through /api/recordings/:callId, which is where authorisation already lives. A public
# bucket would make an unguessable URL the only thing protecting a stranger's phone number.
#
# On a serverless host with this UNSET, recording storage REFUSES rather than falling back
# to a disk that evaporates between invocations. A write that "succeeds" and sets
# recording_path on bytes that are already gone is a lost recording that reads as a stored
# one, and that is the one outcome not worth being lenient about.
BLOB_READ_WRITE_TOKEN=
# Bearer token for /api/cron/drain, the queue consumer on a host with no always-on
# process. Vercel Cron sends it as `Authorization: Bearer $CRON_SECRET`.
#
# UNSET MEANS THE ROUTE IS DISABLED (503), not open. It spends money -- SERP purchases and
# outbound SMS -- so a misconfigured deploy must not leave an endpoint anyone can hold open.
CRON_SECRET=
# Connections per process. Defaults to 4 everywhere, and a value of 1 is REFUSED.
#
# A pool of 1 shipped once on the theory that a serverless instance serves one request at a
# time. One render issues several queries at once, and against the transaction pooler those
# never completed: every database-backed page returned zero bytes and was killed at 300
# seconds, while route handlers that queried sequentially answered in 300ms. Rarely needs
# setting; the same value in every runtime is the point.
DATABASE_POOL_MAX=
# --- Twilio ----------------------------------------------------------------
# Voice reaches Retell over an Elastic SIP Trunk; SMS stays on Twilio and is what
# delivers lead alerts. See docs/telephony.md for the full runbook.
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
# The ONE shared trunk. Every site is a number on it; per-site behaviour comes from
# the inbound webhook resolving to_number, not from trunk topology.
TWILIO_TRUNK_SID=
# REGIONAL termination URI, e.g. rnr-hvac.pstn.us1.twilio.com -- not the default.
# A mismatched region silently adds a cross-country hop to every conversational
# turn. calls.latency_e2e_p50_ms makes that measurable: ten calls on one URI, ten on
# another, compare.
TWILIO_TERMINATION_URI=
# Termination auth. Credentials rather than an IP allowlist: Retell publishes
# several SBC CIDRs across regions and a range change would break outbound silently
# and remotely.
TWILIO_SIP_CRED_USERNAME=
TWILIO_SIP_CRED_PASSWORD=
# Run the whole app against an isolated copy of the schema, e.g. rnr_crm_smoke.
# Unset means the default search_path, which is the normal case.
DATABASE_SCHEMA=
# Daily ceiling for SERP monitoring, per market, in whole US cents.
# 17c/day is about $5/month. Checked BEFORE each purchase, like SCAN_BUDGET_CAP_CENTS --
# a monitor that discovers it overspent has already overspent.
SERP_MONITOR_DAILY_CAP_CENTS=17
# ---------------------------------------------------------------------------
# Google Ads — keyword search volume (NOT DataForSEO)
# ---------------------------------------------------------------------------
# Used when promoting discovery hits to fill serp_keywords.volume.
# Customer IDs may include dashes; they are stripped before the API call.
# Live volume also requires OAuth (Desktop/Web app client + refresh token).
GOOGLE_ADS_DEVELOPER_TOKEN=
GOOGLE_ADS_LOGIN_CUSTOMER_ID=
GOOGLE_ADS_CUSTOMER_ID=
GOOGLE_ADS_CLIENT_ID=
GOOGLE_ADS_CLIENT_SECRET=
GOOGLE_ADS_REFRESH_TOKEN=
GOOGLE_ADS_API_VERSION=v21
# ---------------------------------------------------------------------------
# Bulk market research catalog
# ---------------------------------------------------------------------------
# RESEARCH_CATALOG_ENABLED=false hides #import / #catalog on Market research.
# Default on (anything other than the string "false").
# RESEARCH_BULK_ENABLED must be exact "true" to show confirmable bulk bar
# (dry-run preview is still available when the bar is shown). Default off until
# live canary.
# RESEARCH_CATALOG_ENABLED=true
# RESEARCH_BULK_ENABLED defaults on (opportunity funnel). Set false to hard-block bulk.
# RESEARCH_BULK_ENABLED=false