配置参考
列出 Warmbly 读取的所有环境变量、各自的用途和默认值,以及修改后是否需要重启。
环境配置具有最终决定权。Warmbly 绝不会允许网页表单覆盖由环境控制的设置,因此无须推断优先级,也不会有文件在后台悄然自行改写。
在查看表格前,需要明确这条规则带来的三个结果:
- 本页所有项目都在运行进程的环境中设置。 Docker Compose 使用
docker-compose.yml旁的.env;Kubernetes 使用 Pod 规格或 Secret;直接运行二进制文件时则使用 Shell 环境或 systemd 服务单元。 - 大多数解析后的值可在管理面板中只读查看,位置为 实例 > 配置 > 环境(默认安装中为
http://localhost:5174/configuration?tab=environment)。每行显示变量名、Warmbly 实际解析的值、来源(env、default、derived或unset),以及修改后是否需要重启。通过该页面即可确认变量是否真正生效,无须阅读源码。 - 少量设置保存在数据库中,因为它们不受环境变量控制。这些项目列在存储在数据库中的设置中,也是唯一能通过浏览器编辑的设置。
任何 API 都不会返回机密值。配置页面仅显示敏感密钥是否已设置,以及其值的四字符指纹;这足以确认两个服务使用相同的 AUTH_SECRET,而不会向任何人披露实际值。
查看实际生效的配置
| 方式 | 提供的内容 |
|---|---|
| 管理面板中的 实例 > 配置 > 环境 | 后端配置条目,包含解析值、来源、分组及重启要求 |
make doctor | 从 Shell 执行健康检查,包括检测到的配置问题 |
GET /admin/instance/config | 相同列表的 JSON 形式,需要 manage_settings 管理员权限 |
实例健康状态中标记的问题都会链接到本页对应的修复说明。
在 .env 中设为空,并不等于最终值为空
docker-compose.yml 以 ${VAR:-default} 形式读取此文件,Compose 对空赋值和缺失值采用相同处理。KMS_LOCAL_MASTER_KEY= 不会清空密钥,而会代入公开默认值。在 Compose 中希望某项保持未设置时,请注释掉对应行。
部署
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
APP_ENV | dev 或 prod。dev 允许公开默认密钥,并启用 Gin 调试日志。任何可被其他人访问的实例都应设为 prod | dev | 是 |
DEPLOYMENT_MODE | self_hosted 或 cloud。选择身份验证中的默认设置;每项仍可独立覆盖 | Compose 下为 self_hosted | 是 |
ALLOW_INSECURE_DEFAULTS | true 允许后端在密钥仍为公开默认值时启动。仅适用于用后即弃的实例 | 未设置 | 是 |
GIN_MODE | debug 或 release | release | 是 |
ENV_LABEL | 管理面板在实例名称旁显示的标签 | 未设置 | 是(容器启动时) |
WARMBLY_CLOUD_URL | 自托管实例为使用托管预热池而连接的 Warmbly Cloud API(指南)。仅发起出站 HTTPS 请求 | https://api.warmbly.com | 否 |
INSTANCE_NAME | 关联此实例时,Warmbly Cloud 批准页面显示的名称 | 主机名 | 否 |
WARMBLY_VERSION | 关联请求中向 Warmbly Cloud 报告的版本字符串 | dev | 否 |
WARMBLY_ALLOW_UNSAFE_WEBHOOK_URLS | true 允许客户 Webhook 指向 http:// 和私有地址。仅限开发使用:此选项会允许任何工作区成员让后端访问你的内部网络 | false | 是 |
在 Gin release 模式下,访问日志会省略成功的健康探测和内部 GET 查询,包括预期的邮件消息映射缺失。身份验证失败、服务器错误、其他资源缺失以及全部修改操作仍会记录。Gin debug 模式保留每条访问日志。两种模式都会隐藏查询字符串及凭据路径参数;组织审计事件不受影响。
prod 不代表云托管
APP_ENV=prod 不需要云账户。配置后才使用错误上报和 GeoIP 查询;未配置时会记录说明并跳过。
密钥与机密值
五项值保护整个实例。Compose 为每项提供可用默认值,使刚克隆的仓库无须配置即可启动;但这些默认值都已公开在仓库中,因此不提供任何保护。
| 变量 | 格式 | 保护对象 | 是否需要重启 |
|---|---|---|---|
AUTH_SECRET | 至少 32 个字符,启动时强制检查 | JWT 和会话签名。实时服务通过 JWT_SECRET 读取相同值,并执行同样的最小长度要求;长度不足时两者都会拒绝启动,因为较短的 HS256 密钥可以根据服务签发的任意令牌离线恢复。它还用于生成工作区发布到 _warmbly TXT 记录中的 warmbly-verify= 域名证明值,因此更改它会改变全部证明值:根域名重定向会在下次检查时停止,直到新值被发布 | 是 |
INTERNAL_API_TOKEN | 任意随机字符串 | 跟踪和表单服务调用的后端 /api/v1/internal/ 路由(点击票据、根域名重定向、页面浏览和托管表单)。未设置 NODE_BROKER_TOKEN 时,集群节点也使用它进行身份验证 | 是 |
NODE_BROKER_TOKEN | 任意随机字符串 | 仅由集群节点调用的全部内部路由:组织数据密钥的读取、存储和解封装,Blob 签名,Warmbly Cloud 管理邮箱的服务商访问令牌,邮件映射和同步查询,工作节点配置及节点心跳。可选,未设置时回退到 INTERNAL_API_TOKEN。控制平面和每个节点必须设置相同值,否则这些路由返回 401;设置后新加入的节点会在 node.env 中收到此值,替代 INTERNAL_API_TOKEN,此前加入的节点需重新加入。新安装会由安装程序生成该值;已有安装在你主动设置前继续使用共享令牌 | 是 |
NODE_ACCEPT_INTERNAL_TOKEN | true 或未设置 | 设为 true 时,除密钥和 Blob 代理外,节点专用路由也接受 INTERNAL_API_TOKEN。升级本身不需要此选项:使用 INTERNAL_API_TOKEN 发送的心跳始终会收到节点版本响应,且不记录任何内容,因此旧节点能够自行更新。仅在需要让旧节点更新完成前继续同步时启用,随后删除;启用期间,跟踪和表单服务的令牌也能访问这些路由 | 否 |
NODE_LOG_TOKEN | 注册时签发的 43 字符 base64url 密钥,每个节点不同 | 仅用于特定节点的日志接收。在受保护的集群加入环境中签发,哈希保存在主 Redis;绝不从集群共享的消息代理或内部凭据派生或交换。重新加入仅轮换此诊断证据凭据,删除节点会撤销它。先部署后端,再升级节点,然后使用相同配置目录和 UUID 重新执行经身份验证的加入流程,并保留本地覆盖项。无须删除集群节点、重新分配邮箱或手动复制密钥;脚本会重启服务。仅升级不能补足不可用的采集覆盖,也不会回填历史日志。绝不要在整个集群共享同一值 | 是 |
SECRET_KEY_BASE | 至少 64 个字符 | 实时服务中的 Phoenix 会话签名 | 是 |
KMS_LOCAL_MASTER_KEY | base64 编码,恰好 32 字节 | 用于加密各组织数据密钥的根密钥 | 是 |
CREDENTIALS_ENCRYPTION_KEY | 恰好 64 个十六进制字符 | 静态存储的邮箱凭据:SMTP、IMAP 密码,以及 Gmail、Outlook 的 OAuth 访问令牌和刷新令牌 | 是 |
在允许其他人访问实例前,请生成真正的随机值:
cat >> .env <<EOF
AUTH_SECRET=$(openssl rand -base64 32)
INTERNAL_API_TOKEN=$(openssl rand -hex 24)
SECRET_KEY_BASE=$(openssl rand -base64 64 | tr -d '\n')
KMS_LOCAL_MASTER_KEY=$(openssl rand -base64 32)
CREDENTIALS_ENCRYPTION_KEY=$(openssl rand -hex 32)
APP_ENV=prod
EOF如果只需要新的 KMS_LOCAL_MASTER_KEY,运行 make gen-key 即可输出一个。
最后设置 APP_ENV=prod。它会将使用公开默认值的行为从日志警告提升为拒绝启动,因此如果其他五项尚未配置就设为 prod,实例将无法启动。
各服务分别读取自身副本,因此以下值必须在服务之间完全一致:
| 值 | 读取方 | 不一致的后果 |
|---|---|---|
AUTH_SECRET,实时服务以 JWT_SECRET 读取 | backend、realtime | 控制台可加载,但实时功能始终不可用:WebSocket 拒绝所有令牌 |
INTERNAL_API_TOKEN,工作节点通过 ENCRYPTED_KEYS_WORKER_TOKEN 读取 | backend、worker、tracking | 工作节点无法获取解密密钥,跟踪服务无法解析点击票据;两者均以 401 拒绝访问 |
KMS_LOCAL_MASTER_KEY | backend、consumer、worker | 无法解封装数据密钥,导致邮箱凭据无法解密 |
CREDENTIALS_ENCRYPTION_KEY | backend、worker | 无法解密已存 SMTP、IMAP 密码及 OAuth 令牌,导致所有邮箱都无法发送或同步 |
只有后端会拒绝使用已公开默认值启动
密钥检查运行在后端。消费者和工作节点即使使用公开默认值也能正常启动,因此实例看似健康,某个进程却可能使用任何人都能从 GitHub 读到的密钥。实例健康状态上的 secret_published_default 检查用于发现此问题。
备份两项加密密钥
KMS_LOCAL_MASTER_KEY 和 CREDENTIALS_ENCRYPTION_KEY 用于加密所有存储的凭据和邮件正文。丢失后无法恢复,只有数据库备份而没有它们也无法解密。
地址
邮件中的所有链接(密码重置、邀请、首次运行认领链接)都由 APP_URL 构建。未设置时,自托管实例会尝试从 CORS_ALLOW_ORIGINS 或 PUBLIC_HOST 推断来源,绝不会回退到托管服务:无人能打开的链接只会带来支持请求,而指向他人控制台的可用链接会将有效重置令牌带出你的部署。请务必设置。
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
APP_URL | 控制台来源地址,也是所有邮件链接的基础 | 先从 CORS_ALLOW_ORIGINS 推断,再尝试 PUBLIC_HOST | 否(每次请求时读取) |
FRONTEND_BASE_URL | 同一值的别名,在未设置 APP_URL 时读取 | 未设置 | 否 |
API_PUBLIC_URL | 后端公开基础地址。前端、Blob URL 和 OIDC 重定向均由它派生 | Compose 下从 PUBLIC_HOST 派生 | 是 |
BACKEND_PUBLIC_URL | 第三方调用的后端公开 URL:用于生成工作节点配置、集成 OAuth 重定向和 Slack 应用 请求 URL。仅在该地址不同于 API_PUBLIC_URL 时需要 | 回退到 API_PUBLIC_URL | 是 |
APP_ORIGIN | 邮箱和集成 OAuth 回调页面发回消息的默认来源。仅在控制台不位于 APP_URL 时需要。逐邮箱 Google 流程也可返回允许列表中的发起控制台,详见下文 | 从 APP_URL 派生 | 是 |
API_HOST | 监听地址 | 0.0.0.0:8080 | 是 |
PUBLIC_HOST | 仅用于 Compose。其他 URL 均由此主机名或局域网 IP 派生 | localhost | 是 |
CORS_ALLOW_ORIGINS | 允许调用 API 的来源,以逗号分隔。未列出的来源在预检时收到 403 | Compose 下从 PUBLIC_HOST 派生 | 是 |
WEBSOCKET_URL | 控制台连接的 WebSocket URL | 由 Compose 派生 | 是(容器启动时) |
PHX_HOST | 实时服务自身的主机名 | localhost | 是 |
TRACKING_DOMAIN | 提供打开像素、点击链接,以及已通过自定义域名验证的工作区退订页面的主机;也是客户跟踪子域名应指向的 CNAME 值。生产环境应使用独立、中性的域名。未设置时,营销邮件不带像素,链接不经包装,无法验证任何自定义跟踪域名,所有退订均由 API_PUBLIC_URL 提供 | Compose 下为 localhost:3000,其他情况下未设置 | 否 |
TRACKING_SERVICE_URL | 后端在内部访问跟踪服务的地址 | 未设置 | 是 |
FORMS_DOMAIN | 托管表单页面和嵌入内容的主机(forms.example.com,路由到表单服务)。后端据此生成分享链接和嵌入代码;未设置时,表单没有公开 URL | 未设置 | 否(每次请求时读取) |
设置 PUBLIC_HOST 会禁用 localhost
设置 PUBLIC_HOST 后,所有派生 URL 都会使用它,http://localhost:5173 将无法使用,因为 localhost 来源已不在 CORS_ALLOW_ORIGINS 中。如需同时保留两者,请自行在 CORS_ALLOW_ORIGINS 中列出。
网络与代理
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
TRUSTED_PROXIES | 允许设置 X-Forwarded-For 的 CIDR 网段,以逗号分隔 | 空(不信任任何来源) | 是 |
直接暴露后端时,留空是正确选择;使用反向代理时则不适合。如果没有可信 CIDR,Warmbly 会将代理地址记录为客户端地址,按 IP 登录限流、会话记录、审计记录和 API 密钥 IP 允许列表都会使用错误地址。请设置为代理连接所用的 CIDR:
TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12身份验证
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
AUTH_LOGIN_CODE | always、new_device 或 off。控制登录是否还需要发送到账户邮箱的验证码 | 自托管时为 off,云端为 new_device | 是 |
REQUIRE_EMAIL_VERIFICATION | 注册时是否必须先确认邮件验证码,才创建账户 | 自托管时为 false | 是 |
DISABLE_REGISTRATION | false、invite_only 或 true。参见注册模式 | 自托管时为 invite_only | 是 |
DISABLE_PASSWORD_LOGIN | 完全关闭邮箱和密码登录,适用于仅使用单点登录的部署 | false | 是 |
SSO_AUTO_PROVISION | true 允许已验证身份提供方的断言创建账户,不受 DISABLE_REGISTRATION 限制 | false | 是 |
AUTH_IP_RATE_LIMIT | 每个来源 IP 在每 15 分钟内允许发起的未登录身份验证请求数 | 60 | 是 |
CLI_AUTH_IP_RATE_LIMIT | 每个来源 IP 每 15 分钟允许的 CLI 登录握手请求数。使用独立额度,因为一次 warmbly auth login 大约轮询 200 次,不能耗尽上述额度 | 500 | 是 |
PASSKEY_IP_RATE_LIMIT | 每个来源 IP 每 15 分钟允许的通行密钥登录挑战数。使用独立额度,因为登录页面加载时就请求挑战,而不是等用户操作,因此打开页面不应消耗上述额度。完成验证流程属于身份验证尝试,计入 AUTH_IP_RATE_LIMIT | 300 | 是 |
APP_ATTEST_IP_RATE_LIMIT | 每个来源 IP 每 15 分钟允许的原生应用 App Attest 挑战及密钥注册数。使用独立额度,因为应用每次执行受保护调用前都会请求挑战;使用该挑战的实际调用计入 AUTH_IP_RATE_LIMIT | 300 | 是 |
PUBLIC_IP_RATE_LIMIT | 其余公开路由上每个来源 IP 每 15 分钟允许的请求数:退订、邀请预览、集群注册和分析代理。这些路由通过高熵令牌携带授权,因此限制的是无限额写入和转发带宽,而非猜测尝试。与身份验证限流器不同,读取和写入都会计数 | 600 | 是 |
WARMBLY_BOOTSTRAP_EMAIL | 首位所有者的邮箱地址,仅在用户表为空时读取 | 未设置 | 是 |
WARMBLY_BOOTSTRAP_PASSWORD_HASH | 该所有者的 Argon2id PHC 字符串,由 warmblyctl hash-password 输出。优先于明文形式。格式错误,或成本参数低于 m=65536,t=3,p=2、盐长度不足 16 字节、密钥长度不足 32 字节的哈希会被拒绝,不会创建所有者 | 未设置 | 是 |
WARMBLY_BOOTSTRAP_PASSWORD | 便于使用的明文形式。遵循与普通密码相同的规则(长度、泄露密码列表);不符合时会被拒绝,不会创建所有者。启动时会警告,并使密码留在进程环境中 | 未设置 | 是 |
WARMBLY_BOOTSTRAP_ORG | 与该所有者一起创建的组织名称。不符合命名规则的值会被忽略并给出警告 | 从名称派生 | 是 |
TWOFA_SECRET | 用于加密已存 TOTP 密钥的密钥。回退到 AUTH_SECRET,以保证现有部署继续运行;轮换会使所有已注册 TOTP 密钥失效 | AUTH_SECRET | 是 |
WEBAUTHN_RP_ID | 通行密钥依赖方 ID。未设置时从 APP_URL 派生。更改会使所有已注册通行密钥失效 | 派生值 | 是 |
WEBAUTHN_RP_ORIGINS | 通行密钥验证流程接受的来源 | 从 APP_URL 派生 | 是 |
WEBAUTHN_RP_DISPLAY_NAME | 通行密钥提示中显示的名称 | Warmbly | 是 |
GOOGLE_CLIENT_ID、GOOGLE_CLIENT_SECRET | 浏览器中的 Google 登录。两项均必需,与 BOX_GOOGLE_* 邮箱客户端无关。同一客户端也用于 Google Workspace 管理员为整域连接证明域名时登录,仅请求 openid 和 email;此过程不会使任何人登录 Warmbly | 未设置 | 是 |
GOOGLE_REDIRECT_URI | 在 Google 注册的重定向 URI。由 API 提供,而非控制台。管理员域名证明也返回此处,再交回发起流程的控制台窗口,因此无须注册第二个 URI | API_PUBLIC_URL,以及 /v1/auth/google/callback | 是 |
GOOGLE_IOS_CLIENT_ID | 接受来自 iOS 应用的额外 Google 客户端 ID。仅用于原生应用,不会启用浏览器按钮 | 未设置 | 是 |
APPLE_APP_ID、APPLE_TEAM_ID、APPLE_KEY_ID、APPLE_KEY_SECRET | 使用 Apple 登录。APPLE_APP_ID 是 Services ID;APPLE_KEY_SECRET 是 PEM 或 base64 形式的 .p8 文件(base64 -i AuthKey_XXXX.p8 可使其保持单行) | 未设置 | 是 |
APPLE_REDIRECT_URI | 在 Apple 注册的返回 URL。必须使用 HTTPS | API_PUBLIC_URL,以及 /v1/auth/apple/callback | 是 |
APPLE_IOS_BUNDLE_ID | 接受来自 iOS 应用的 Bundle ID | com.warmbly.app | 是 |
OIDC_ISSUER_URL | 通用 OpenID Connect 签发方。启动时执行发现 | 未设置 | 是 |
OIDC_CLIENT_ID、OIDC_CLIENT_SECRET | Warmbly 用于身份验证的客户端 | 未设置 | 是 |
OIDC_REDIRECT_URL | 在身份提供方注册的重定向 URI。默认为 API_PUBLIC_URL 加 /v1/auth/oidc/callback | 派生值 | 是 |
OIDC_SCOPES | 向身份提供方请求的权限范围 | openid,profile,email | 是 |
OIDC_ALLOWED_DOMAINS | 允许通过该身份提供方登录的邮箱域名 | 空(任意域名) | 是 |
OIDC_DEFAULT_ORG | 所有单点登录用户加入的组织 UUID | 未设置 | 是 |
OIDC_PROVIDER_NAME | 登录按钮上的文字 | Single sign-on | 是 |
完整行为说明(包括每种注册模式对注册表单的影响)请参见账户与访问。
代理 PostHog 请求
使用代理时务必设置界面主机地址
POSTHOG_HOST 是事件发送目标,POSTHOG_UI_HOST 是 PostHog 应用自身的地址。只有不使用代理时,两者才相同。将前者指向代理,后者保持为 https://us.posthog.com 或 https://eu.posthog.com;否则 SDK 会基于代理生成工具栏和会话回放链接,而代理并不提供应用页面。
内容拦截器会丢弃发往 posthog.com 的请求,导致相当一部分访客的分析和错误上报悄然停止。为此,后端在 /ingest 提供反向代理。请让各前端指向它:
| 目录 | 设置 | 值 |
|---|---|---|
| 控制台、管理面板 | POSTHOG_HOST | https://api.example.com/ingest |
| 营销网站 | PUBLIC_POSTHOG_HOST | https://api.example.com/ingest |
其他配置不变:密钥保持相同,代理原样转发载荷。代理放在后端,而非三个前端中分别实现,以免三份副本逐渐产生差异。
代理只转发 PostHog 读取请求所需的内容。Cookie 和 Authorization 留在实例这一侧,因此实例会话绝不会交给第三方;调用者地址放入 X-Forwarded-For,以便定位访客位置,而不是服务器位置。
人机验证
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
CAPTCHA_PROVIDER | none 或 turnstile | 派生值,见下文 | 是 |
TURNSTILE_SECRET | Cloudflare Turnstile 密钥,由后端读取 | 未设置 | 是 |
WARMBLY_TURNSTILE_KEY | Turnstile 站点密钥,由控制台和管理面板在容器启动时读取 | Compose 中的测试密钥 | 是(容器启动时) |
WARMBLY_BETA_NOTICE | 向控制台所有用户展示的一句话:首次访问时以对话框显示,之后在页眉中显示可重新打开它的 Beta 标记。留空则不显示,因此生产部署无须设置。容器启动或 Pages 构建时写入 config.js,所以更改后需要重启或重新构建,仅刷新页面无效 | 未设置 | 是 |
WARMBLY_CONFIG_OUT | 控制台和管理面板写入运行时 config.js 的位置。默认为 nginx 提供的路径;静态托管时将其设为构建输出目录,使同一脚本在构建阶段而非容器启动阶段生成文件 | nginx 路径 | 是(构建或容器启动时) |
WARMBLY_CSP_CONNECT_ORIGINS | 控制台 CSP 中额外允许的连接来源,以空格分隔。实时服务由后端自动发现;此项用于离线后备或其他连接目标。请使用准确的 HTTP(S) 或 WS(S) URL,不要使用通配主机或 CSP 指令。VITE_CSP_CONNECT_ORIGINS 提供构建时和开发时的后备值 | 未设置 | 是(构建或容器启动时) |
TURNSTILE_BYPASS_TOKEN | 跳过验证的令牌。仅在 APP_ENV=dev 时生效 | 未设置 | 是 |
APP_ATTEST_APP_IDS | 以逗号分隔的 <team id>.<bundle id> App ID;后端接受其 App Attest 证明来代替人机验证。此项未设置且没有 APPLE_TEAM_ID 时,关闭 App Attest | 设置 APPLE_TEAM_ID 时,为 APPLE_TEAM_ID + . + APPLE_IOS_BUNDLE_ID | 是 |
APP_ATTEST_ALLOW_DEVELOPMENT | 接受开发签名构建(通过 Xcode 安装到设备)的 App Attest 证明。生产环境请保持关闭 | false | 是 |
TURNSTILE_SITE_KEY | 公开的 Turnstile 小组件密钥。后端将其交给表单服务,由表单服务在启用垃圾提交防护的托管表单页面上呈现。各前端在 WARMBLY_TURNSTILE_KEY 中持有自己的副本 | 未设置 | 否(每次请求时读取) |
CAPTCHA_PROVIDER 没有固定默认值。未设置时,如果 TURNSTILE_SECRET 有值(无论来自环境还是 AWS Secrets Manager),则解析为 turnstile,否则为 none。因此配置密钥会开启人机验证,清除密钥则关闭。面板将解析值的来源显示为 derived。
唯一会出问题的组合,是在 TURNSTILE_SECRET 为空时显式设置 CAPTCHA_PROVIDER=turnstile:所有验证都会失败,导致无人能登录。请设置密钥,或将提供方改回 none。
原生 iOS 应用从不显示 Turnstile。它使用 Apple App Attest 证明自己是真实应用副本,在登录、注册和密码重置时替代人机验证。CAPTCHA_PROVIDER=none 时应用不发送任何证明。开启 Turnstile 后,只有 APP_ATTEST_APP_IDS 包含应用的 App ID 时,应用才提供邮箱地址和密码登录;否则只提供 Apple 登录、Google 登录和通行密钥,这些方式从不需要人机验证。自托管实例如果启用 Turnstile 且希望 iOS 应用支持密码登录,需将 APP_ATTEST_APP_IDS 设置为 iOS 应用的 App ID。验证不需要 Apple 账户,也不发起外部调用:使用二进制文件内置的 Apple App Attest 根证书检查。每次安装的密钥保存在 app_attest_keys 表中,属于实例,不包含在工作区导出中。iOS 模拟器不支持 App Attest,因此本地开发继续使用 TURNSTILE_BYPASS_TOKEN。
平台邮件
平台邮件是产品自身发送的邮件:注册验证码、密码重置、团队邀请、通知摘要,以及启用时的登录验证码。它与营销活动邮件相互独立,后者通过你连接的邮箱发送。
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
MAIL_TRANSPORT | smtp、log 或 ses | Compose 下为 log;直接运行二进制文件且未设置 SMTP_HOST 时为 ses | 是 |
EMAIL_NAME | 平台邮件的发件人显示名称 | Warmbly | 是 |
EMAIL_ADDRESS | 平台邮件的发件地址 | 无;缺少时后端拒绝启动 | 是 |
SMTP_HOST | 中继主机名 | 未设置 | 是 |
SMTP_PORT | 中继端口。未设置时根据 SMTP_SECURITY 确定 | 派生值 | 是 |
SMTP_USERNAME、SMTP_PASSWORD | 中继凭据。绝不通过未加密连接发送 | 未设置 | 是 |
SMTP_SECURITY | starttls(587)、tls(465)或 none(25) | starttls | 是 |
SMTP_AUTH | auto、plain、login、cram-md5 或 none | auto | 是 |
SMTP_EHLO_NAME | 向中继声明的 EHLO 名称 | 发件人域名 | 是 |
SMTP_TLS_INSECURE_SKIP_VERIFY | 跳过证书验证。仅适用于使用私有证书颁发机构的中继 | false | 是 |
EMAIL_BRAND_NAME | 事务性邮件主题和页眉中的产品名称 | Warmbly | 是 |
EMAIL_BRAND_LEGAL_ENTITY、EMAIL_BRAND_COMPANY_NUMBER、EMAIL_BRAND_PLACE_OF_REG、EMAIL_BRAND_ADDRESS | 事务性邮件页脚中的身份说明行 | 自托管时未设置,不显示该行 | 是 |
EMAIL_BRAND_WEBSITE_URL、EMAIL_BRAND_TERMS_URL、EMAIL_BRAND_PRIVACY_URL、EMAIL_BRAND_SUPPORT_EMAIL | 页脚链接和支持邮箱地址 | 自托管时未设置,不显示链接行 | 是 |
NOTIFICATION_EMAIL_DAILY_CAP | 每位用户每天的通知邮件数。0 表示不限量 | 25 | 是 |
NOTIFICATION_PUSH_WINDOW | 通知等待多久后还会通过推送发送 | 5h | 是 |
自托管实例绝不会自动填入我们的信息。Warmbly 自身的公司信息和网站属于托管服务,不属于你,因此未设置的 EMAIL_BRAND_* 不显示任何内容,避免给你的用户发送带有无关公司名称的页脚。如果所在司法辖区要求商业邮件包含身份说明,请设置为你自己的信息。
虽然带有邮件前缀,这些配置不只用于邮件。GET /v1/auth/config 提供其中可公开的内容(名称、网站、条款、隐私政策、支持地址),原先硬编码 warmbly.com 的各处都改为读取它:登录页的文字标志和条款、隐私链接,API 密钥页面中可复制的 API 示例,共享统计卡片,以及托管表单页面底部的“技术支持”说明。未设置对应值时均不显示,确保填写你的表单的人不会被引向与其无关的网站。
log 是有效的传输方式,并非故障状态:它将每封邮件写入后端日志,不实际投递。这样新安装在没有中继时也能完成首次登录。缺少的功能是密码重置邮件、邀请投递和摘要通知;其替代操作见账户与访问。
从日志中读取验证码:
docker compose -p warmbly logs backend | grep -B2 -A12 "MAIL_TRANSPORT=log"消费者进程只会发出警告
缺少 EMAIL_ADDRESS 或 EMAIL_NAME 时,后端拒绝启动。消费者则记录警告,并静默禁用所有通知及摘要邮件,因此实例可能看似健康却不发送任何邮件。请在每个进程上设置这两项。
发送前验证
营销活动向某地址发送前,后端可以先检查地址:检查语法,再检查 MX,最后对收件人邮件服务器执行 SMTP RCPT 探测。返回 invalid 的地址会被跳过,不会发送,从而将可能的硬退信转为静默跳过。探测由后端发起,绝不从工作节点发起,因为工作节点使用你的发信 IP。
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
EMAIL_VERIFY_HELO_HOST | 探测在 EHLO/HELO 中声明的主机名。必须是属于此实例的公开完整域名 | APP_URL 的主机部分 | 是 |
EMAIL_VERIFY_MAIL_FROM | 探测在 MAIL FROM 中使用的信封发件人 | verify@ 加 HELO 主机名 | 是 |
EMAIL_VERIFY_MILLIONVERIFIER_API_KEY | 实例级 MillionVerifier 密钥。未连接自有密钥的工作区都会通过此密钥进行验证并消耗其额度,替代内置探测 | 未设置 | 是 |
不完整的 HELO 主机名会导致整个会话被拒绝
邮件服务器会拒绝不是真实主机名的问候(如 localhost、不含域名的名称,或 .local、.internal、.lan 等保留后缀下的名称)。尤其是 Postfix,会在 RCPT 阶段而非 HELO 阶段拒绝,因此返回形式与针对收件人地址的 504 5.5.2 <localhost>: Helo command rejected: need fully-qualified hostname 完全相同。Warmbly 将此类回复识别为探测遭拒,而非邮箱失效,因此绝不会将联系人标为无效。如果既未设置 EMAIL_VERIFY_HELO_HOST,也没有可用的 APP_URL 主机名,就完全跳过探测,所有地址保持 unknown,仍可发送。
明确判定在 90 天后复查,不确定判定在 30 天后复查;每分钟分批处理 200 位联系人,有积压时持续运行。在集成中连接了 MillionVerifier 的工作区始终使用自身额度,无论是否设置实例级密钥。
EMAIL_VERIFY_HELO_HOST 与 SMTP_EHLO_NAME 不同。SMTP_EHLO_NAME 是平台邮件中平台邮件中继看到的问候;本项是收件人服务器从验证器看到的问候。
加密
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
KMS_PROVIDER | local(下述 AES 主密钥)、aws(AWS KMS)或 brokered(不持有密钥材料,请控制平面解封装)。实例使用 aws 时,新加入节点会自动获得 brokered 配置,因此集群内机器都无须云凭据 | Compose 下为 local,直接运行二进制文件时为 aws | 是 |
KMS_LOCAL_MASTER_KEY | base64 编码,恰好 32 字节。所有组织数据密钥的信任根 | Compose 中提供的公开默认值 | 是 |
KMS_LOCAL_MASTER_KEY_FILE | 改为保存该密钥的文件路径。与内联值互斥 | 未设置 | 是 |
KMS_AWS_KEY_ID | KMS_PROVIDER=aws 时使用的密钥 ID 或别名 | 未设置 | 是 |
CREDENTIALS_ENCRYPTION_KEY | 恰好 64 个十六进制字符。加密静态存储的邮箱 SMTP 和 IMAP 密码 | Compose 中提供的公开默认值 | 是 |
ENCRYPTED_KEYS_PROVIDER | 后端及具有 DSN 的消费者使用 postgres,工作节点使用 http | 调用方的后备值,因此应显式设置 | 是 |
ENCRYPTED_KEYS_BACKEND_URL | 工作节点访问后端密钥端点的地址 | 未设置 | 是 |
ENCRYPTED_KEYS_WORKER_TOKEN | 工作节点持有的 INTERNAL_API_TOKEN 副本 | 未设置 | 是 |
CREDENTIALS_ENCRYPTION_KEY 为空不会导致启动失败,而会禁用加密,使邮箱密码以未加密形式存储。连接任何邮箱前,请先设置并备份。
存储
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
BLOB_PROVIDER | filesystem、s3 或 brokered(不持有存储桶凭据,请控制平面为每次操作签名;数据仍直接传输)。实例使用 s3 时,新加入节点自动获得 brokered 配置 | Compose 下为 filesystem,直接运行二进制文件时为 s3 | 是 |
BLOB_FS_ROOT | 存储正文、附件、头像和邮件正文图片的目录。同一主机上的后端、消费者及所有工作节点必须共享该目录 | /data/blobs | 是 |
BLOB_BUCKET | BLOB_PROVIDER=s3 时使用的存储桶名称 | 未设置 | 是 |
BLOB_PUBLIC_BASE_URL | 后端 /public 路由的公开基础地址。邮件正文中的图片由此提供,因此必须是收件人邮件客户端能够访问的地址 | 派生值 | 是 |
AWS_REGION、AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY | S3 或 SES 凭据 | 未设置 | 是 |
AWS_ENDPOINT_URL_S3 | 非 AWS 的 S3 端点(MinIO、R2、B2) | 未设置 | 是 |
AWS_CONFIG_ENABLED | true 表示从 AWS SSM 或 Secrets Manager 读取机密值 | false | 是 |
使用 filesystem 时,远程工作节点会将 Blob 写入自身磁盘,而非后端可读的卷。工作节点在其他主机运行时,请使用 s3,并配置双方可访问的存储桶。
事件总线
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
EVENTBUS_PROVIDER | nats 或 kafka。Kafka 需要 -kafka 镜像,或使用 GO_TAGS=kafka 构建 | Compose 下为 nats,直接运行二进制文件时为 kafka | 是 |
FLEET_IMAGE_VARIANT | 附加到下发给集群节点版本后的标签后缀,使节点拉取能连接当前实例事件总线的构建。EVENTBUS_PROVIDER=kafka 时默认为 -kafka,其他情况下无后缀;设为空值可关闭 | 从 EVENTBUS_PROVIDER 派生 | 否 |
NATS_URL | JetStream 地址。所有服务(包括 Rust 跟踪发布器)都会使用 URL 中的凭据:用户认证使用 nats://user:pass@host:4222,令牌认证使用 nats://token@host:4222,TLS 使用 tls:// | nats://nats:4222 | 是 |
NATS_CREDS | NATS 凭据文件路径(用户 JWT 和 nkey 种子),适用于使用 JWT 而非令牌认证的总线。Synadia Cloud 和任何由 nsc 管理的账户都会签发此类文件 | 未设置 | 是 |
NATS_CREDS_B64 | 同一文件的单行 base64 编码形式。集群使用这种形式:节点接收环境变量而非文件,Docker 读取的环境文件无法表示多行值。优先于 NATS_CREDS | 未设置 | 是 |
NATS_MAX_BYTES | 流的磁盘占用上限。接受纯字节数或容量值(2GiB、512MB、1G)。未设置时,流仅受 NATS_STREAM_MAX_AGE 和账户自身配额限制,托管总线可能拒绝此配置:Synadia 的“Max Bytes Required”会拒绝未指定上限的流 | 未设置(不限量) | 是 |
NATS_STREAM_NAME、NATS_SUBJECT_PREFIX | 流和主题命名 | warmbly | 是 |
KAFKA_BOOTSTRAP_SERVERS | EVENTBUS_PROVIDER=kafka 时的消息代理列表 | 未设置 | 是 |
KAFKA_SASL_USERNAME、KAFKA_SASL_PASSWORD | 消息代理凭据 | 未设置 | 是 |
SCHEMA_REGISTRY_URL、SCHEMA_REGISTRY_KEY、SCHEMA_REGISTRY_SECRET | Avro 编解码器使用的注册表 | 未设置 | 是 |
CODEC_PROVIDER | json 或 avro | Compose 下为 json,直接运行二进制文件时为 avro | 是 |
EVENTBUS_HANDLER_TIMEOUT | 单次处理器执行超过多久后放弃本次交付。需要更长时间的订阅可自带时间预算,但不能短于此值:工作节点命令订阅使用 195 秒,因为发送可用 120 秒(SMTP 客户端 90 秒会话及此前检查),之后还需记录结果。在 NATS 上,确认等待时间为订阅预算加 5 秒 | 30s | 是 |
PUBSUB_ENABLED | false 使用 Redis 桥接分发实时消息,true 使用 Google Pub/Sub | false | 是 |
GCP_PROJECT_ID | PUBSUB_ENABLED=true 时使用的项目 | 未设置 | 是 |
Kafka 事件发布会等待消息代理的交付报告,以及所有同步副本确认,受调用方截止时间和 EVENTBUS_HANDLER_TIMEOUT 限制。进入本地队列不代表已经发布。结果被拒绝或超时时,原命令保持未确认,以便重放;超时不能证明事件从未交付,因此消费者仍需对重放结果去重。通用生产者事件在关闭期间单独排空,不会消耗某次发布独立的交付报告。
主题由事件总线创建,无需手动创建
工作节点的命令主题以加入时分配的节点 ID 命名,因此无法预先知道全部主题。Kafka 后端会创建自己使用的主题,每进程每主题一次,不依赖消息代理的 auto.create.topics.enable;Confluent Cloud 默认关闭该项,且只允许 Dedicated 集群配置,Basic、Standard、Enterprise 和 Freight 不支持。因此,除读取和写入外,凭据还需具有主题的 CREATE 权限。
json 无须额外组件,是默认值。avro 根据 SCHEMA_REGISTRY_URL 解析每个事件,只包含在 -kafka 镜像中。生产者和消费者必须采用同一种编解码器:消息内没有格式标记,使用另一种编解码器的消费者无法读取总线中已有消息。切换前应排空总线,不能原地更改。封装消息主题需要 BACKWARD 兼容性,因为新事件类型会增加联合类型分支;发布器遇到 FORWARD 或 FULL 时,会自行将主题设为 BACKWARD。后端、消费者和实时服务的 PUBSUB_ENABLED 必须一致。
Go 服务将每个解码后的 Avro 数组或映射限制为 65,536 项,跨数据块累计;每个字符串或字节字段限制为 1 MiB。超限载荷会被拒绝。当前维护的编码器使用现有模式和 Confluent 封装。符合这些限制的队列消息仍可读取,无须排空总线或重新注册主题。升级期间请保持相同编解码配置。
跟踪主题由两种语言的服务读取
Rust 发布器和 Go 订阅器都会读取 KAFKA_TRACKING_TOPIC。如果只在一处覆盖,打开和点击事件将停止被消费,而且任何位置都不会报错。
数据库
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
PRIMARY_DB | PostgreSQL 连接字符串。包含内联凭据,因此任何 API 都不会返回 | Compose 中的 Postgres | 是 |
PGSSLROOTCERT | 用于验证数据库 TLS 证书的证书包,由 Postgres 驱动读取。Amazon RDS 证书链的根证书不在公共信任库中,因此连接 RDS 并使用 sslmode=verify-full 时需要 AWS 证书包;后端和消费者镜像在 /etc/ssl/rds/global-bundle.pem 提供该文件。推荐在 DSN 中使用 sslrootcert=,让设置随对应连接一起传递 | 未设置(使用系统信任库) | 是 |
DB_MAX_CONNS | 每个 Go 进程(后端、消费者)的 Postgres 连接池上限。真正的总上限是数据库的 max_connections,每个副本、消费者及每次 warmblyctl 执行都会占用一部分。启动时,进程查询服务器上限,并将自身连接池降低到服务器非预留连接数的四分之一。四分之一假设有四个数据库客户端,即滚动部署时各有两个后端和消费者,因此它只是单进程保护,而非集群级配额分配;超过四个客户端,或有其他应用共享服务器时,仍需主动设置。只有探测成功时才会下调连接池,绝不会调高;无法连接服务器或读取设置时,继续使用下列值。此变量未设置时,会采用 DSN 中的 pool_max_conns | 25,并按探测到的份额下调 | 是 |
DB_MIN_CONNS | 每个 Go 进程空闲时保持打开的连接数,上限为 DB_MAX_CONNS | 2 | 是 |
DATABASE_URL | 实时服务对同一数据库使用的变量名 | Compose 中的 Postgres | 是 |
DATABASE_POOL_SIZE | 连接池最大连接数,仅用于实时服务。Go 服务使用驱动默认值,不读取此项 | 10 | 是 |
DATABASE_SSL | 实时服务是否通过 TLS 连接 Postgres | true,Compose 下为 false | 是 |
DATABASE_SSL_CA_FILE | 实时服务用于验证 Postgres 的 CA 证书包。未设置时使用系统信任库,其中不含 Amazon RDS 根证书,因此 RDS 数据库需将此项设为 /etc/ssl/rds/global-bundle.pem(镜像内提供) | 未设置 | 是 |
迁移已嵌入后端二进制文件,并在启动时应用。无须单独执行迁移;如有需要,镜像中也提供独立的 /app/migrate 二进制文件。
实时服务升级兼容性
仍支持以主机、/socket 或 /socket/websocket 结尾的现有 WEBSOCKET_URL 值,包括自定义路径前缀、端口和查询参数。HTTP(S) 形式会转换为 WS(S)。发现响应和票据响应使用相同的规范化处理。
控制台可通过 API 来源上的 /v1/realtime/socket/websocket 连接。无须新增环境变量、CSP 允许列表条目或数据迁移。API 只转发到已配置的实时网关,并保留浏览器 Origin。旧 Docker 安装若声明的是回环网关,而 API 容器无法访问自身回环地址,可回退到相同端口上的现有 realtime 服务。
请将后端、实时服务和控制台镜像一起升级到同一版本;实时服务更新会为经过 API 代理的连接保留按客户端 IP 的限制。现有直连客户端和较旧的控制台/后端组合继续使用直连 URL。自定义反向代理应同时允许 API 路由和直接实时路由上的 WebSocket 升级;直连仍作为重试后备。请保持 CHECK_ORIGIN_HOSTS 和共享的 AUTH_SECRET/JWT_SECRET 一致。重试标签不代表已恢复:实时连接表示套接字已打开,正在重连表示仍在重试。
缓存
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
REDIS | Redis 连接字符串。包含内联凭据,因此任何 API 都不会返回 | Compose 中的 Redis | 是 |
REDIS_URL | 实时服务对同一实例使用的变量名 | Compose 中的 Redis | 是 |
Redis 保存限流计数器、组织密钥缓存、实时消息桥接状态和首次运行设置令牌。在尚未认领的实例上清空 Redis,会同时销毁认领链接和全部待完成身份验证会话。
GeoIP
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
GEODB_PATH | GeoLite2 City 数据库路径 | 无;未设置此变量时后端拒绝启动 | 是 |
GEODB_URL | GEODB_PATH 尚无文件时,下载该数据库的地址 | 未设置,仅使用已有文件 | 是 |
TRACKING_SCANNER_ASN_DB | 跟踪服务上的 GeoLite2 ASN 数据库路径 | 未设置,ASN 匹配关闭 | 是 |
TRACKING_SCANNER_ASN_DB_URL | 跟踪服务下载该数据库的地址 | 未设置 | 是 |
后端在所有环境中都必须设置 GEODB_PATH。文件本身可选:路径下没有文件时,会话和审计记录不包含城市信息,其他行为不变。消费者也可选读取同一变量,为每次邮件打开和点击补充国家与城市;未配置时,这些记录只有客户端和设备信息。
这是两种不同数据库和两个不同文件。GeoLite2-City 回答地址位于何处;GeoLite2-ASN 回答由哪个网络宣告该地址,用于匹配跟踪服务的 asn: 扫描器条目。两者不包含对方数据,因此读取器指向错误文件时不会得到结果。同一个 MaxMind 账户可免费获取 GeoLite2-City 和 GeoLite2-ASN 两个版本,geoipupdate 可下载任意一种。
GEODB_PATH 应配置在后端和消费者上,TRACKING_SCANNER_ASN_DB 应配置在跟踪服务上;对应文件缺失时都会被忽略。缺少城市数据库会失去位置标签,缺少 ASN 数据库会失去 asn: 匹配。如果将 TRACKING_SCANNER_ASN_DB 指向 City 数据库,启动时会拒绝并记录实际发现的类型,而非悄然不匹配任何内容。
导入地理位置数据库
能够挂载文件是最简单的方式,不需要下列任何变量。大多数容器托管环境没有可挂载来源,此时设置相应的 _URL 变量,服务会在启动时下载自己的副本。
# MaxMind's download endpoint, which always serves the current build of an
# edition, so a restart is how an instance picks up a newer one. Both halves of
# the credential come from the same page of your MaxMind account: the account
# id is the username and the licence key is the password.
GEODB_URL=https://ACCOUNT_ID:[email protected]/geoip/databases/GeoLite2-City/download?suffix=tar.gz
TRACKING_SCANNER_ASN_DB_URL=https://ACCOUNT_ID:[email protected]/geoip/databases/GeoLite2-ASN/download?suffix=tar.gz旧版永久链接已失效
许多指南仍展示 MaxMind 的 app/geoip_download?license_key= URL,但如今签发的许可证密钥使用它会收到 Invalid license key。当前密钥以 _mmk 结尾,需要与账户 ID 一起在上述端点进行身份验证,因此仅携带密钥的 URL 无论密钥为何都会失败。
任何提供 MaxMind 格式数据库的 URL 都可使用;文件形式根据内容而非文件名识别,因此 .tar.gz、.mmdb.gz 和未压缩的 .mmdb 均受支持。如上例,URL 用户信息部分中的凭据以 HTTP Basic 认证发送。DB-IP 免费的 Lite 数据库 完全不需要账户,以 dbip-city-lite 和 dbip-asn-lite 名称发布相同格式;其国家数据质量相当,城市数据较弱。
需要了解两点:
- 其他组件不会等待下载。 服务启动时位置查询先返回
Unknown,数据库下载完成后再加载。因此镜像源缓慢或不可用只会影响城市标签,不会导致部署健康检查失败。 - 已有文件会保留使用,直到过期。 不满七天的数据库直接使用,不发送任何请求,因此挂载副本、重启后保留的卷及手动放入的副本均优先。超过七天后通过
If-Modified-Since重新验证,通常返回304 Not Modified,不产生费用,也不计入 MaxMind 每日额度;只有确有更新数据库时才下载。未设置 URL 时绝不会发起请求,文件完全由你管理。 - 下载被拒绝时会重试。 最多尝试四次,逐次增加等待时间,并遵守服务器返回的
Retry-After。此前下载只在启动时执行一次且不再重试,因此镜像源限流或短暂不可达会导致容器整个生命周期都没有地理数据。对于必然重复的拒绝,如许可证密钥无效或名称无法解析,不会重试。 - 下载内容不是数据库时会被丢弃,不会安装。 过期许可证密钥可能返回错误页面;若将其保存,下次启动会因文件已存在而不再重试,导致位置查询永久失效。因此内容会在放到目标位置前尝试打开;失败时路径保持为空,并记录一行日志。
- 携带凭据的 URL 不允许使用明文
http,无论凭据位于查询参数还是用户信息部分,因为这会在网络上传输明文许可证密钥。URL 中不含任何机密的http镜像源可以使用;但无论如何,从https降级重定向到http都会被拒绝。
两个 URL 都属于机密信息
这些 URL 携带账户凭据:上例位于用户信息部分,旧式链接则位于查询参数。后端将这两个变量视为机密,任何 API 都不会返回实际值,管理面板只显示已设置。日志也不会完整记录其中任意一个:用户信息和查询参数都会被移除,包括 net/http 和 reqwest 生成、原本会嵌入传入 URL 的错误信息。
跟踪服务的路径处理有所不同:ASN 数据库保存在内存中,从不写入磁盘,因此单独设置 TRACKING_SCANNER_ASN_DB_URL 即可,TRACKING_SCANNER_ASN_DB 可以不设。
使用数据库镜像,而非直接从 MaxMind 拉取
GeoLite2 账户每 24 小时允许直接下载 30 次,超限返回 429;付费 GeoIP 账户允许 2,000 次。每次消费者启动会下载 GeoLite2-City,每次跟踪服务启动会下载 GeoLite2-ASN,因此一次发布消耗两次下载,重启循环每次重启消耗一次。繁忙时很容易达到上限,而且后果不只是下载失败:启动时遇到 429 的容器,在被替换前都没有地理数据。
可以将 URL 指向你自己的副本。.github/workflows/geoip-mirror.yml 按 MaxMind 自身发布节奏,在周二和周五运行,每次分别下载两个版本一次,并上传到私有存储桶。之后整个集群都从该处读取,无论部署多频繁,MaxMind 每周只看到两次请求。同区域拉取 60 MB 文件通常也只需一两秒,无须等待 MaxMind 响应。
该任务需要以下仓库设置;未设置 MAXMIND_ACCOUNT_ID 时会自行跳过,因此分叉仓库不会因此失败:
| 设置 | 类型 | 含义 |
|---|---|---|
MAXMIND_ACCOUNT_ID | 变量 | 数字账户 ID |
MAXMIND_LICENSE_KEY | 机密值 | 该账户的许可证密钥 |
GEOIP_MIRROR_BUCKET | 变量 | 写入副本的存储桶 |
GEOIP_MIRROR_REGION | 变量 | 存储桶区域 |
GEOIP_MIRROR_PREFIX | 变量 | 对象键前缀,包含末尾斜杠 |
GEOIP_MIRROR_AWS_ACCESS_KEY_ID | 机密值 | 仅允许写入该前缀、无其他权限的凭据 |
GEOIP_MIRROR_AWS_SECRET_ACCESS_KEY | 机密值 |
然后将 GEODB_URL 和 TRACKING_SCANNER_ASN_DB_URL 指向镜像对象。两者都接受位于用户信息或查询参数中的凭据,因此可使用预签名 URL,或使用无法猜测对象键下的对象,而无须让服务持有存储桶凭据。只有镜像任务持有 MaxMind 许可证密钥,这也是推荐此方式的另一原因:密钥从多个服务的部署配置,变为单个仓库机密。
请保持副本私有。GeoLite2 不允许再分发,因此发布镜像中也从不内置数据库。如果希望完全不使用账户,DB-IP Lite 数据库采用 CC-BY 4.0 许可,无须账户。
工作节点
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
WORKER_ID | 此工作节点的稳定 UUID。运行共享同一环境的扩容副本时,请保持未设置 | 优先派生,否则随机生成 | 是 |
WORKER_BIND_IP | 出站连接绑定的源地址,同时用于派生 WORKER_ID | 未设置 | 是 |
WORKER_PUBLIC_IP | 工作节点报告的公网 IPv4,后端不会用其观察到的地址覆盖,因为该地址可能属于反向代理。未设置时,节点每十分钟使用 api.ipify.org 发现 HTTPS 出站 IPv4。邮件和 HTTPS 出站路由不同时,应显式设置 | 自动检测 | 是 |
WARMBLY_WORKER_CAPACITY | 此工作节点的邮箱分配规划目标。用于指导分配,不是邮箱服务商的发送限制 | 100 | 是 |
WORKER_COMMAND_CONCURRENCY | Kafka 和 NATS 上的工作节点命令处理通道数,范围为 1 至 64。命令按载荷中的邮箱分组,也包括旧版按任务键划分的发送命令。每个邮箱保持顺序,不同通道并发执行,因此一个缓慢的邮件服务器只会阻塞自己的通道。NATS 会防止通道中等待的命令在等待期间被重复投递。无效值使用默认值 | 8 | 是 |
WARMBLY_NODE_REGION | 描述节点出站位置的自由格式标签,例如 eu-central。分配时优先选择接近邮箱服务商预期登录位置的工作节点;未设置时评分中立。加入脚本根据 --region 写入 | 未设置 | 是 |
ENCRYPTED_KEYS_BACKEND_URL | 工作节点获取组织密钥的后端基础地址 | 未设置 | 是 |
ENCRYPTED_KEYS_WORKER_TOKEN | 工作节点持有的 INTERNAL_API_TOKEN 副本 | 未设置 | 是 |
MAIL_TLS_INSECURE | 跳过邮箱连接的证书验证 | false | 是 |
未设置密钥 URL 时不会提示
ENCRYPTED_KEYS_BACKEND_URL 或 ENCRYPTED_KEYS_WORKER_TOKEN 为空时,工作节点仍会启动并订阅,但始终不会注册,也不会记录日志。实例健康状态上的 no_worker_heartbeat 检查用于暴露此问题。
工作节点按设计不持有数据库连接。它们需要的所有关系数据均通过后端内部 HTTP API 获取。
节点加入时会获得哪些配置
warmbly join 根据后端自身环境生成 /etc/warmbly/node.env,因此本页多数配置会自动传递给节点,无须设置两次。有三项采用转换或判断,而非直接复制:
- 加密和 Blob 提供方会进行转换。 使用
KMS_PROVIDER=aws的实例向节点下发brokered,使用BLOB_PROVIDER=s3的实例也向节点下发brokered,使集群机器无须持有云凭据。不需要凭据的提供方原样传递。 - 工作节点绝不会收到
PRIMARY_DB。 消费者会收到,因为它直接访问 Postgres。如果实例将 DSN 保存在机密管理器而非环境中,就没有可下发的值,加入脚本会明确提示。 - 地址按原样继承。
NATS_URL、REDIS和ENCRYPTED_KEYS_BACKEND_URL完全沿用后端的值。如果地址只能在容器网络内解析,节点虽然能顺利注册,却无法访问任何服务。
控制平面无法获知的设置
每次加入都会重写 node.env。旁边的 /etc/warmbly/node.local.env 只创建一次,之后绝不重写;容器在第二步读取它,因此其中重复的变量名会覆盖前者。
每台机器特有的值应写在此处,例如当前实例没有的 DSN、自有基础设施凭据,或有意覆盖回直接访问提供方的配置。
printf 'PRIMARY_DB=%s\n' "postgres://..." >> /etc/warmbly/node.local.env
systemctl restart warmbly-consumer邮箱连接
后端以及每个工作节点都需要配置:后端发起 OAuth 流程,各工作节点在令牌到期时刷新令牌。
| 变量 | 作用 | 默认值 |
|---|---|---|
BOX_GOOGLE_CLIENT_ID、BOX_GOOGLE_CLIENT_SECRET | 通过逐邮箱 Google 登录连接的 Gmail 邮箱使用的 Google 应用。启用新连接需要同时设置两项。只要还有邮箱使用 Google 登录,就应保留配置。未设置 GOOGLE_CLIENT_ID 时,还承担 Workspace 管理员域名证明流程。重定向 URI 为 API 基础地址加 /addresses/google/callback | 未设置 |
BOX_GOOGLE_OAUTH_CONNECT | 可选的后端开关,用于禁用新的 Gmail OAuth 连接。未设置时,只要两项 Google 邮箱凭据都存在就启用;显式设为 false 或无效值则禁用。无论开关状态如何,已有 Google 登录邮箱都继续工作并可重新授权。能力通过 GET /v1/auth/config 上的 gmail_oauth_connect 公布 | 配置完整时启用 |
BOX_OUTLOOK_CLIENT_ID、BOX_OUTLOOK_CLIENT_SECRET | 连接 Outlook 和 Microsoft 365 邮箱。重定向 URI 为 API 基础地址加 /addresses/outlook/callback | 未设置 |
普通 SMTP 和 IMAP 邮箱无须这些配置。如果工作节点缺少这些值,邮箱初次连接会正常,但约一小时后首个访问令牌到期时会悄然停止。
为各控制台部署配置 Google 登录
控制台有独立的显示开关,只影响连接对话框和邮箱导入中的逐邮箱 Google 登录,不影响 Microsoft 登录、Google 控制台登录或 Workspace 整域授权。
| 变量 | 设置位置 | 默认值 |
|---|---|---|
WARMBLY_GMAIL_OAUTH_CONNECT | 控制台容器环境或 Cloudflare Pages 环境。设为 true 后,在后端支持时提供 Google 邮箱登录 | false |
VITE_GMAIL_OAUTH_CONNECT | 本地 Vite 或直接 Vite 构建使用 web/.env。运行时配置优先 | false |
两个控制台部署可以共享同一后端,并为此开关设置不同值。例如:
# Public dashboard environment
WARMBLY_GMAIL_OAUTH_CONNECT=false
# Assessment dashboard environment (a separate deployment)
WARMBLY_GMAIL_OAUTH_CONNECT=true两个控制台都将 WARMBLY_API_URL 指向同一 API,并将 WARMBLY_APP_URL 设为各自的控制台 URL。容器在启动时生成 /config.js,因此不需要第二份镜像。Cloudflare Pages 在 build:pages 期间生成配置;请在目标 Pages 项目上设置变量并重新部署。
客户控制台也会根据相同部署设置生成内容安全策略。静态引导脚本和样式使用构建生成的哈希;通常不允许作为标记注入的脚本和样式。API、Sentry 和 PostHog 来源从各自配置的 URL 派生。nginx 启动前,控制台会访问已配置 API URL 的公开 /v1/auth/config 端点,只允许准确的 websocket_url 来源。因此,已有后端 WEBSOCKET_URL 配置无须再重复设置 CSP。如果 Web 容器也接收到 WEBSOCKET_URL,则直接使用它提供的来源,无须 HTTP 请求。
pnpm build:pages 在构建时执行相同发现,使用构建主机上的 curl 和 jq(控制台容器已包含两者)。发现过程采用有界重试;后端不可达或返回无效配置时,绝不会放宽 CSP。后端不可用会产生启动或构建警告;待其就绪后,请重启控制台或重新构建 Pages。离线或自定义部署可通过 WARMBLY_CSP_CONNECT_ORIGINS 显式允许实时服务来源,例如 wss://realtime.example.com。请将其传入 Web 服务环境,而不只是写入 Compose 的 .env。更改来源需要重启容器或重新构建 Pages。Pages 仍完全静态,无须 Function。
Pages 控制台会从静态响应中移除 Cloudflare 默认的 Access-Control-Allow-Origin: * 标头。控制台资源不是跨来源公共 API。这不会更改后端的 CORS_ALLOW_ORIGINS,也不会更改客户邮件使用的公开图片 URL。
邮件正文在独立的静态沙盒中呈现,脚本被禁用。该文档允许邮件样式和读者选择的远程内容,但不会放宽控制台策略。控制台图片可从自身来源、已配置 API、data URL 或临时 Blob URL 加载。外部界面图片和编辑器预览使用后端图片预览端点;该端点只允许 HTTPS 公网目标,检查每次连接和重定向,并将图片限制为 5 MiB、请求限制为十秒。自有公开图片通过已配置 Blob 存储读取,因此文件系统存储和私有 MinIO 端点不需要公网限制例外。开发用不安全 Webhook 开关不会放宽图片抓取限制。
此代理仅用于显示。已有记录无须迁移,保存和发送的邮件 HTML 保留原有图片 URL、链接和样式。收件人不使用控制台代理,也不需要会话即可加载邮件图片。来信继续使用独立的远程内容同意机制,不使用此代理。为支持编辑器生成的邮件设计,控制台仍允许内联 style 属性,但不允许内联脚本或任意样式块。开发环境为 Vite 注入的脚本和样式表使用每次响应新生成的随机数。
共享后端应保持 APP_URL 指向主控制台,并将两个准确的控制台来源都加入 CORS_ALLOW_ORIGINS,例如 https://app.example.com,https://assessment.example.com。Google 和 Microsoft 邮箱连接、重新授权、整域管理员授权、集成 OAuth 以及 Slack 身份验证,都会将发起浏览器的来源绑定到服务端 OAuth 状态。回调只向该允许来源发送消息;没有 opener 的弹出窗口返回其 /oauth-return 页面。路由状态缺失、过期或已使用时,显示重试提示,不会将授权码转发给另一个控制台。不含来源的有效旧状态仍使用主控制台。OAuth 返回来源不接受通配符。服务商注册的回调 URI 仍位于同一 API。
邮箱地址和密码登录、OTP/2FA、注册、退出以及普通控制台导航都使用本地路由。从允许列表中的控制台发起的密码重置和邀请邮件,会链接回该控制台。Stripe 结账、积分充值、账单门户和预热套餐返回均使用发起控制台,并保留安全的同来源路径。推荐分享链接也使用该控制台的注册页。没有可信浏览器来源的请求使用主控制台。
对于 Warmbly 托管的评估控制台,共享后端需要同时允许以下两个来源:
APP_URL=https://app.warmbly.com
CORS_ALLOW_ORIGINS=https://app.warmbly.com,https://tac-security-assessment.warmbly.com这只是环境配置示例,不是内置允许列表。请保留管理控制台等其他已有来源:将 https://tac-security-assessment.warmbly.com 追加到当前 CORS_ALLOW_ORIGINS,而不是替换整个列表。在 Railway 上,修改后端服务变量并重新部署;修改前端变量或合并代码不会更新线上后端环境。APP_URL 和 APP_ORIGIN 应继续指向主控制台,而非评估控制台。评估前端设置 WARMBLY_APP_URL=https://tac-security-assessment.warmbly.com 和 WARMBLY_GMAIL_OAUTH_CONNECT=true。
向评估人员开放访问前:
- 将评估前端的
WARMBLY_API_URL指向共享 API,并使用不带末尾斜杠的准确WARMBLY_APP_URL来源。 - 在现有 Cloudflare Turnstile 小组件中允许评估主机名。仅允许
app.warmbly.com的组件不会覆盖同级评估主机名。请保持人机验证开启。 - 将评估来源追加到实时服务的
CHECK_ORIGIN_HOSTS,保留已有来源。API 来源允许列表不会配置 WebSocket 来源检查。 - 对于仅使用密码的评估账户,可使用 管理 > 测试人员,或在用户下的现有账户页面启用跳过邮件登录验证码。这些账户免于邮件登录验证码步骤,但仍使用人机验证和现有权限检查。已启用 2FA 的账户仍需要第二因素。
- 请保持现有
WEBAUTHN_RP_ID不变。app.warmbly.com的通行密钥 RP ID 无法用于同级评估主机名,更改它会使已有凭据失效。评审人员可使用邮箱地址和密码;跨两个主机支持通行密钥需另行决定迁移方案。
这不会全局改写所有主控制台链接。控制台的 Google、Apple 和 OIDC 登录仍路由到主控制台;评估人员应使用邮箱地址和密码。服务器生成的通知、从 Slack 打开的链接、CLI 或引导链接,以及自托管 Cloud 代理返回仍使用实例的规范路由。Cloud 代理返回来源继续绑定已注册实例,而非托管评估控制台。
即使前端开关关闭,已有 Google OAuth 邮箱仍可重新授权。应用密码和 SMTP/IMAP 选项继续可用。关联 Warmbly Cloud 时,Google 同意流程由 Cloud 而非本地后端代理,但仍需启用前端选项。
可见并不代表已获授权
隐藏按钮不能阻止已认证客户端调用 API。要在后端禁用新的 Google OAuth 连接,请设置 BOX_GOOGLE_OAUTH_CONNECT=false。前端开关不会改变 Google 对受限权限范围的验证或安全评估要求;同一后端上的第二个控制台共享其数据。扫描前,请与评估方确认评估范围和评审账户。
整域连接
工作区可以通过一次管理员授权,连接Google Workspace 域名或 Microsoft 365 组织中的所有邮箱(添加账户 > Google > 整个 Workspace 域名、添加账户 > Microsoft > 整个组织)。以下配置仅由后端读取:后端自行签发这些邮箱的所有令牌,并在每次需要时交给工作节点,因此工作节点无须此处配置。完整分步设置见整域邮箱连接。
| 变量 | 作用 | 默认值 |
|---|---|---|
GOOGLE_WORKSPACE_DELEGATION_KEY | 启用全域委托的 Google Cloud 服务账户 JSON 密钥。Workspace 超级管理员授权的是其中的 client_id。未设置时,Google Workspace 授权关闭。若值不是服务账户密钥,启动时记录一行日志并保持关闭 | 未设置 |
GOOGLE_WORKSPACE_DELEGATION_KEY_FILE | 将相同密钥作为文件挂载时的路径。在 GOOGLE_WORKSPACE_DELEGATION_KEY 为空时读取 | 未设置 |
Google,每个实例配置一次:
- 在 Google Cloud 控制台 中创建项目,或选择将长期使用的项目。项目 ID 无法更改。
- 在 API 和服务 > 库 下启用 Gmail API 和 Admin SDK API。
- 在 IAM 和管理 > 服务账户 下创建服务账户,例如
warmbly-workspace。不要授予它任何项目角色。 - 在其密钥选项卡中选择 添加密钥 > 创建新密钥 > JSON。新的 Google Cloud 组织默认通过禁用服务账户密钥创建策略阻止此操作,组织策略管理员可以为此项目解除限制。
- 将密钥文件路径写入
GOOGLE_WORKSPACE_DELEGATION_KEY_FILE(只读挂载),或将 JSON 写入GOOGLE_WORKSPACE_DELEGATION_KEY,然后重启后端。
每位客户的授权都绑定服务账户的客户端 ID,因此服务账户只创建一次,后续轮换密钥,不要更换账户:重新创建会生成新的客户端 ID,所有域名都必须再次授权。该密钥可为任何已授权域名中的任意地址签发令牌,因此请像保护 CREDENTIALS_ENCRYPTION_KEY 一样保护它;它是所有此类邮箱背后的统一凭据。
只有工作区证明自己控制该域名,且目录将管理员地址列为超级管理员时,才会为该工作区记录 Google 授权。可采用以下任一证明方式:
- 管理员的 Google 登录通过控制台自身的登录客户端(
GOOGLE_CLIENT_ID、GOOGLE_CLIENT_SECRET)及现有重定向 URI(GOOGLE_REDIRECT_URI,或API_PUBLIC_URL加/v1/auth/google/callback)进行,仅请求openid和email范围。回调识别证明流程后,会交回控制台窗口,而不是让任何人登录,因此无须注册新重定向 URI。未配置该客户端时,以相同方式使用BOX_GOOGLE_*邮箱客户端。两者均不依赖BOX_GOOGLE_OAUTH_CONNECT。 - 在
_warmbly.<domain>发布TXT记录 ,内容为工作区和域名独有的warmbly-verify=值。此方式始终可用;两种客户端均未设置时,这是唯一证明方式。该值使用AUTH_SECRET作为生成密钥。
Microsoft 365 组织复用 Outlook 应用的 BOX_OUTLOOK_CLIENT_ID 和 BOX_OUTLOOK_CLIENT_SECRET,设置两者后即启用。应用注册需要增加三项配置:
- 支持的账户类型:任何组织目录中的账户(多租户),以便其他组织同意该应用。
- 重定向 URI:使用已有地址,即 API 基础地址加
/addresses/outlook/callback。管理员同意也返回此处。 - API 权限:在单邮箱登录已有的委托权限之外,添加 Microsoft Graph 应用权限
Mail.ReadWrite、Mail.Send和User.Read.All。
除非使用自己的租户测试,否则不要在自身租户授予这些权限的管理员同意。每个客户组织由其全局管理员通过控制台分别同意。授权对应的组织是管理员实际登录的组织,从同意登录流程本身读取,绝不从请求读取。发布者验证可选。
应用权限能够访问已同意组织中的每个邮箱。建议各组织通过 Exchange Online ApplicationAccessPolicy 或 RBAC for Applications,将应用限制到 Warmbly 应访问的邮箱;策略范围之外的邮箱连接时会被拒绝。客户操作步骤见连接整个 Microsoft 365 组织。
GET /emails/grants/config 通过 google_missing 和 microsoft_missing 报告仍缺少的配置:列出上述未设置项目的名称;提供方就绪后返回空列表。
集成
| 变量 | 作用 | 默认值 |
|---|---|---|
<PROVIDER>_OAUTH_CLIENT_ID、<PROVIDER>_OAUTH_CLIENT_SECRET | CRM 和消息集成的 OAuth 客户端 | 未设置 |
HUBSPOT_OAUTH_CLIENT_ID、HUBSPOT_OAUTH_CLIENT_SECRET | 你的 HubSpot 应用。后端和每个消费者都需要:消费者运行 HubSpot 模式 的拉取与同步队列,并在过程中刷新令牌。密钥还用于验证 HubSpot Webhook 签名 | 未设置 |
PIPEDRIVE_OAUTH_CLIENT_ID、PIPEDRIVE_OAUTH_CLIENT_SECRET | 你的 Pipedrive 应用(参见Pipedrive 自托管配置)。后端和每个消费者都需要:消费者运行 Pipedrive 模式 的拉取与同步队列,并在过程中刷新令牌。密钥还用于派生 Pipedrive 每次 Webhook 投递携带的密码,以及验证 Warmbly 面板的签名调用 | 未设置 |
SALESFORCE_OAUTH_CLIENT_ID、SALESFORCE_OAUTH_CLIENT_SECRET | 你的 Salesforce 应用(参见Salesforce 自托管配置)。执行同步的后端,以及执行 Salesforce 自动化操作并为其刷新令牌的每个消费者都需要 | 未设置 |
GOOGLE_SHEETS_OAUTH_CLIENT_ID、GOOGLE_SHEETS_OAUTH_CLIENT_SECRET | 用于联系人同步源的 Google Web 应用客户端。后端必须同时设置两项。与 Google 控制台登录和 Gmail 邮箱 OAuth 使用不同配置 | 未设置 |
INTEGRATIONS_OAUTH_REDIRECT_URL | 这些流程共用的重定向 URI | BACKEND_PUBLIC_URL(否则为 API_PUBLIC_URL)加 /integrations/oauth/callback |
SLACK_OAUTH_CLIENT_ID、SLACK_OAUTH_CLIENT_SECRET | 实例自己的 Slack 应用。未设置时,控制台显示 Slack 尚未配置,任何工作区都无法连接 | 未设置 |
SLACK_SIGNING_SECRET | 验证 Slack 向事件和交互 URL 发送的每个请求。未设置时,Slack 仍会发布通知和收件箱对话,但助手及按钮关闭,这些 URL 返回 503 slack_not_configured | 未设置 |
三个 Slack 值均来自 Slack 应用的基本信息页面。请在后端和消费者上设置,并重启两者;随附的 docker-compose.yml 会将三个值传递给两个服务。请求 URL 优先基于 BACKEND_PUBLIC_URL,未设置时使用 API_PUBLIC_URL。从清单创建应用的方式见 Slack 应用。
Google Sheets 应用
Google Sheets 从 联系人 > 同步源 连接,而非集成目录。按需同步只在你主动运行时读取选定工作表并导入联系人。
仅仅权限不同,并不要求独立 Google Cloud 项目,但同一项目中的 OAuth 客户端共享同意配置和验证。若希望 Sheets 的同意和验证与现有 Google 登录或 Gmail 配置分离,请使用具有独立同意配置和 Web 应用客户端的专用项目。同一项目中的第二个客户端不能提供这种隔离。Google 也建议在为已批准应用评估新权限范围时使用独立测试项目。独立项目仍需完成所请求权限范围要求的验证。
- 在 Google Cloud 项目 中启用 Google Sheets API。
- 配置项目的 Google Auth Platform 品牌和受众。处于测试状态的外部应用,应将要连接的 Google 账户添加为测试用户。公开发布时,请遵循Google 敏感权限范围验证要求。
- 创建类型为Web 应用的 OAuth 客户端,或复用该项目中已有的 Web 应用客户端。在其授权重定向 URI中添加准确的后端回调:优先使用
INTEGRATIONS_OAUTH_REDIRECT_URL;未设置时为BACKEND_PUBLIC_URL(否则API_PUBLIC_URL)加/integrations/oauth/callback。例如https://api.example.com/integrations/oauth/callback。这不是控制台 URL、Google 登录回调或 Gmail 邮箱回调。 - 当前客户端请求
https://www.googleapis.com/auth/spreadsheets和https://www.googleapis.com/auth/userinfo.email,请在同意配置中包含这些范围。Sheets 范围属于敏感权限,授予读写访问,即使联系人同步并不会回写。它不是受限的 Gmail 权限范围;已有 Google 登录或 Gmail 批准不会自动批准 Sheets 访问。 - 在后端进程环境中,将客户端 ID 和密钥分别设为
GOOGLE_SHEETS_OAUTH_CLIENT_ID和GOOGLE_SHEETS_OAUTH_CLIENT_SECRET,然后重启或重新部署后端。容器部署必须显式将两个变量传入服务;只写入 Compose 的.env不会自动转发。不要将密钥放入控制台构建变量。更改这些设置需要重启,无须数据库迁移。
不会回退到 GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET,也不会回退到 BOX_GOOGLE_CLIENT_ID/BOX_GOOGLE_CLIENT_SECRET。缺少任意一项 Sheets 凭据时,通过 Google 连接会在联系 Google 前返回 501,提示 OAuth 尚未配置。Warmbly Cloud 由服务运维方为所有工作区统一配置客户端一次,工作区成员只需授权自己的 Google 账户。
配置后,连接同步源:授权能够访问电子表格的账户,粘贴表格 ID,选择工作表,映射列(包括邮箱)并保存。需要导入新行时运行立即同步。第一行应为列标题,之后每行对应一位联系人。
HubSpot 应用
自托管实例中的 HubSpot 模式 使用你在自己 HubSpot 开发者账户中创建的 HubSpot 应用:
- 重定向 URL:使用
INTEGRATIONS_OAUTH_REDIRECT_URL的值;未设置时为BACKEND_PUBLIC_URL(否则API_PUBLIC_URL)加/integrations/oauth/callback - 必需权限范围:
oauth,crm.objects.contacts.read,crm.objects.contacts.write,crm.objects.companies.read,crm.objects.companies.write,crm.objects.deals.read,crm.objects.deals.write,crm.objects.owners.read,crm.schemas.contacts.read,crm.schemas.contacts.write - 可选权限范围:
crm.lists.read(从 HubSpot 列表导入)和sales-email-read。无法授予这些权限的账户仍可连接 - Webhook(可选):目标 URL 为
<backend>/api/v1/integrations/hubspot/webhooks,订阅contact.creation、contact.propertyChange、contact.deletion、deal.creation、deal.propertyChange和deal.deletion。每个 HubSpot 请求都根据公开 URL 上的X-HubSpot-Signature-v3签名验证;该 URL 为BACKEND_PUBLIC_URL(否则API_PUBLIC_URL)加路径,因此必须与 HubSpot 实际调用的地址一致。没有 Webhook 时,消费者每分钟拉取一次以保持数据最新
HubSpot 联系人的 warmbly_link 属性也链接到 APP_URL。通过 warmbly join 加入集群的消费者会连同其余环境一起收到 HubSpot 变量和 APP_URL;工作节点不会收到。
仓库的 hubspot-app/ 目录包含同一应用的 HubSpot 开发者项目:应用定义、联系人记录上的 Warmbly 卡片、“添加到 Warmbly 营销活动”工作流操作及 Webhook 订阅。将其 *-hsmeta.json 文件和 src/app/cards/WarmblyCard.tsx 中的 https://api.warmbly.com 替换为你的后端 URL,再通过 HubSpot CLI 上传(在该目录执行 hs project upload)。CLI 会忽略隐藏目录中的文件,因此上传路径不能包含点开头的目录,否则卡片构建会报告入口文件缺失。卡片只允许请求 <backend>/api/v1/integrations/hubspot/app/(配置于 app-hsmeta.json 的 permittedUrls.fetch)。卡片和工作流操作调用以下公开路由,每个请求都使用应用客户端密钥验证。卡片路由从 HubSpot 附加到查询参数的值读取门户和用户;Webhook 和工作流操作路由从请求体读取门户,并拒绝携带这些查询值的请求:
| 路由 | 调用方 |
|---|---|
POST /api/v1/integrations/hubspot/webhooks | HubSpot Webhook |
POST /api/v1/integrations/hubspot/app/card | Warmbly 卡片,用于显示联系人 |
POST /api/v1/integrations/hubspot/app/enroll | 卡片的“添加到营销活动”操作 |
POST /api/v1/integrations/hubspot/app/pause | 卡片的暂停和恢复操作 |
POST /api/v1/integrations/hubspot/actions/enroll | “添加到 Warmbly 营销活动”工作流操作 |
POST /api/v1/integrations/hubspot/actions/campaigns | 工作流操作的营销活动列表 |
Pipedrive 应用
自托管实例中的 Pipedrive 模式 使用你在 Pipedrive Developer Hub 中创建的 Pipedrive 应用:
-
回调 URL:使用
INTEGRATIONS_OAUTH_REDIRECT_URL的值;未设置时为BACKEND_PUBLIC_URL(否则API_PUBLIC_URL)加/integrations/oauth/callback -
权限范围:商机、联系人和活动的完全访问权限,以及用户读取权限。授予后还会使用潜在客户完全访问权限(有兴趣的回复会添加潜在客户)、联系人字段完全访问权限(Warmbly 人员字段)和 Webhook 完全访问权限(即时更新);未授予时跳过相应步骤。Pipedrive 从应用配置而非请求中获取权限范围
-
Webhook:无须预先配置。工作区切换到此模式时,Warmbly 为每个连接在
<backend>/api/v1/integrations/pipedrive/webhooks/<connection id>自动注册,切换离开时移除。它们要求BACKEND_PUBLIC_URL(否则API_PUBLIC_URL)为 Pipedrive 可访问的https地址;否则消费者改为每五分钟检查 Pipedrive -
应用扩展(可选):人员和商机页面上的 Warmbly 面板。在应用扩展下添加 JSON 面板,API 端点为
<backend>/api/v1/integrations/pipedrive/app/panel,JSON 数据结构来自pipedrive-app/panel.schema.json,展示位置为人员详情和商机详情。再添加两个具有相同展示位置的 JSON 弹窗:“添加到 Warmbly 营销活动”(<backend>/api/v1/integrations/pipedrive/app/enroll,模式pipedrive-app/enroll-modal.schema.json)和“在 Warmbly 中暂停”(<backend>/api/v1/integrations/pipedrive/app/pause,模式pipedrive-app/pause-modal.schema.json),并将两者添加为面板操作。所有 JWT 密钥留空,使 Pipedrive 使用应用客户端密钥签署每次调用;后端会验证该密钥,以及令牌指定的用户和公司。Pipedrive 要求后端使用https,并配备公共证书颁发机构签发的证书
人员字段 Open in Warmbly(在 Warmbly 中打开) 也链接到 APP_URL。通过 warmbly join 加入集群的消费者会连同其余环境收到 Pipedrive 变量、APP_URL 和 BACKEND_PUBLIC_URL(或 API_PUBLIC_URL),因为消费者负责注册 Webhook;工作节点不会收到。
Salesforce 读取 SALESFORCE_OAUTH_CLIENT_ID 和 SALESFORCE_OAUTH_CLIENT_SECRET,它们来自你组织中的 External Client App 或 Connected App,需强制 PKCE,并配置 api、refresh_token 和 id 权限范围。一个应用可支持生产、沙盒和 My Domain 登录,各成员连接时自行选择。参见Salesforce 自托管配置。
AI 与搜索
| 变量 | 作用 | 默认值 |
|---|---|---|
AI_PROVIDER | openai、openrouter、groq、ollama、anthropic 或 custom。省略全部 AI 变量即可关闭 AI,此时 AI 端点明确返回 503 | 未设置 |
AI_API_KEY | 提供方密钥。ollama 不需要 | 未设置 |
AI_MODEL、AI_MODEL_TRIAL、AI_MODEL_PAID | 模型选择,可按套餐分别设置 | 提供方预设 |
AI_BASE_URL | custom 时必需。可使用任何兼容 OpenAI 的端点 | 未设置 |
AI_FREE | 将 AI 使用视为不收费 | 派生值 |
SEARCH_PROVIDER、SEARCH_API_URL、SEARCH_API_KEY | 助手网页搜索(serper 或 searxng) | 未设置 |
MAILHOST_ISPDB | true 允许邮箱服务器识别功能在域名 MX 记录和自身自动配置文件均无法对应已知服务商时,向 Mozilla Thunderbird ISPDB(autoconfig.thunderbird.net)查询。只发送域名。参见数据控制 | 自托管时为 false,Warmbly Cloud 为 true |
MAILVENDOR_SANDBOX_URL | 仅限开发。将所有邮箱供应商客户端指向沙盒模拟供应商 API(<url>/inboxkit、<url>/mailforge),而非真实供应商。只有 APP_ENV 为 dev 时才生效 | 未设置 |
TYPESAFE_API_KEY | TypeSafe API 密钥。启用类型化判断:回复分类、顾问和编辑器中的文案评估、预热内容检查、退信分类、导入列映射及逐表单分流。收件箱标签还需要下述开关。各项发送内容见数据控制 | 未设置 |
INBOX_TAGGING_ENABLED | true 且同时设置 TYPESAFE_API_KEY 时,启用自动收件箱标签。邮件主题、当前正文和上一封发出邮件的正文会发送给 TypeSafe | false |
未指定服务提供方时仍会使用密钥
AI_PROVIDER 为空但设置了 AI_API_KEY 时,会回退到 api.openai.com,因此密钥将发送给 OpenAI。请两项都设置,或两项都不设置。
模型兼容性无须配置。不同后端要求的请求格式不同:较新的 OpenAI 模型使用 max_completion_tokens 而非 max_tokens,拒绝默认值以外的温度,部分模型还要求关闭推理后才能使用函数工具;多数兼容 OpenAI 的后端只支持旧格式。提供方会从每个参数首次被拒绝的响应中学习并记忆,因此首次调用会逐步调整到可用格式,后续调用直接使用该格式。
兼容 OpenAI 的智能体循环也会自动处理超过 128 个工具的目录。它提供只读工具发现,每次加载最多 20 个匹配工具,而不是在一个请求中发送整个目录。所有获准的内置和已连接 MCP 工具仍可被发现,原有权限及批准规则继续适用。升级后已有对话可以继续,无须数据库迁移或更改 AI 配置。
请在后端和消费者上设置这些变量。
自动收件箱标签配置由后端、消费者和 warmblyctl 读取。两个收件箱标签变量都必须设置;仅有密钥不会启动分类。参见自动收件箱标签。
任务与计费
无论使用哪种队列提供方,预热回复都使用同一个持久化 SQL 任务身份。它们在准确对应的父邮件回执之后至少等待 45 分钟,并在原生调度前重新检查邮箱时间窗口、角色、同意状态、健康状态、禁止发送状态和剩余额度。队列回调不授予发送权限。参见诊断内容生成与上线。
每邮箱的 test_mode、test_send_enabled 和 test_receive_enabled 将诊断发送与接收分开;新邮箱默认关闭,旧版 NULL 值保留原有行为。为正值的 shared_daily_limit 和 rolling_recipient_limit 可在独立的冷邮件/诊断额度之上,对全部出站通道施加共同限制。这些是存储在数据库中的邮箱 PATCH 字段,不是环境变量或全局管理员设置。协议 2 执行器会重新检查当前参与状态、日历、禁止发送规则、服务商暂停状态和持久化预留。参见默认值、修复证据与上线。
| 变量 | 作用 | 默认值 |
|---|---|---|
TASKS_PROVIDER | local(进程内 Postgres 轮询程序)或 gcloud(Cloud Tasks) | local |
TASKS_LOCAL_POLL_INTERVAL | 本地轮询程序查找到期任务的频率 | 1s |
SEND_PLAN_SNAPSHOT_INTERVAL | 后端重新遍历已存发送计划过期的各活跃营销活动的频率。每轮读取该活动的全部潜在客户,因此增大间隔可降低大型活动的数据库负载 | 1m |
SEND_PLAN_SNAPSHOT_MAX_AGE | 已存发送计划超过多久视为过期。后台计算新计划期间,仍返回旧计划,并附带 stale: true | SEND_PLAN_SNAPSHOT_INTERVAL 的三倍(3m) |
BILLING_PROVIDER | none(开放全部功能、试用不再到期;控制台将工作区显示为自托管,而非免费套餐,并隐藏计费界面)或 stripe | none |
STRIPE_SECRET_KEY、STRIPE_WEBHOOK_SECRET、STRIPE_PUBLISHABLE_KEY | BILLING_PROVIDER=stripe 时必须同时设置。缺少任意一项,后端会在启动时退出 | 未设置 |
本地任务分发器最多同时运行八个处理器,并按后端有效 Postgres 连接池上限的三分之一进一步下调,最少为一个,为 API 请求和协调工作预留容量。超出限制的任务继续在 Postgres 中等待,不尝试分发,也不标记失败。该上限跨轮询轮次生效,而非每批单独计算,不改变发送权限或未知结果保护。
Stripe Tax 还需在账户侧配置。在 税务 > 设置 中确认总部地址,将商用 SaaS 产品税码(txcd_10103001)设为默认,并选择价格是否含税,或在结账时另加税费。对每个订阅和积分包的产品及价格应用相同税码,并显式指定税务行为。价格一旦设为含税或不含税,之后不可更改;要改变选择,需创建新 Price,并更新对应套餐或 STRIPE_CREDIT_PACK_*_PRICE_ID 值。
企业完成向相应税务机关登记后,才应添加税务登记。对于没有适用有效登记的司法辖区,自动税务返回零税额及 not_collecting 原因。启用 Stripe Tax 不能代替税务登记或申报。
Stripe Checkout 始终请求计算税费所需的账单地址,并允许企业买家提供法定名称和企业税号。当前默认账单门户配置必须允许更新付款方式、账单邮箱、账单地址、名称和税号,并显示发票历史,以便客户在续费前更正税务所在地或登记信息。
将 Webhook 端点指向 /webhook/stripe,使用与 stripe-go v86 匹配的 API 版本 2026-08-26.dahlia。订阅 checkout.session.completed、checkout.session.async_payment_succeeded、checkout.session.expired、customer.subscription.created、customer.subscription.updated、customer.subscription.deleted、invoice.paid、invoice.payment_failed、invoice.finalization_failed、payment_intent.succeeded 和 charge.refunded。更换端点时,先将 STRIPE_WEBHOOK_SECRET 更新为新端点的签名密钥,再禁用旧端点。处理器在端点升级期间接受旧 Webhook 格式,但新端点应使用匹配版本。
开启 Stripe Tax 不会更新已有订阅。设置价格税务行为后,请在 税务 > 迁移 中检查并迁移。Stripe 从下个账单周期开始应用税务变更,不进行按比例调整。控制台迁移不适用于沙盒订阅,测试时需逐个更新。
延迟发送通过本地轮询程序执行,因此后端必须保持运行,计划任务才会触发。
可观测性
错误报告
Warmbly 的各运行时均可上报错误,但只有你指定目标后才会上报。目标由运维人员选择,并非软件要求,因此默认安装不向任何地方报告,也不联系任何主机。
支持两种后端,可以任选一种、两者都用或都不用:
- PostHog 是默认方案。一个项目密钥即可承载全部服务的错误跟踪,以及下述少量产品事件,因此只需一个账户,无须分别配置两个。
- Sentry 仍获完整支持。已向 Sentry 上报的实例可保持配置不变;设置
POSTHOG_ERROR_TRACKING=false后,可保留 PostHog 密钥用于分析,同时不向其发送异常。
同时设置两者时,每个错误都会发送到两处。每个变量由单个进程读取,因此为后端配置项目并不会自动让控制台也上报。
| 变量 | 读取方 | 作用 | 默认值 |
|---|---|---|---|
POSTHOG_KEY | backend、consumer、worker | 服务端错误跟踪,以及下述服务端产品事件 | 未设置 |
WARMBLY_POSTHOG_UI_HOST / PUBLIC_POSTHOG_UI_HOST | PostHog 应用自身的地址,用于工具栏和会话回放链接。仅在使用代理时与上述主机不同,且使用代理时必须不同 | https://us.posthog.com | 是(构建或容器启动时) |
POSTHOG_PROXY_INGEST | /ingest 反向代理转发采集流量的上游。各前端指向 https://<api host>/ingest,避免会拦截 posthog.com 请求的内容拦截器悄然移除分析和错误上报 | https://us.i.posthog.com | 是 |
POSTHOG_PROXY_ASSETS | /ingest/static/* 的上游。PostHog 提供浏览器资源包的主机与事件接收主机不同,混用会返回 404 | https://us-assets.i.posthog.com | 是 |
POSTHOG_KEY | 表单服务 | 表单服务自身的错误。与跟踪服务一样,读取自己的环境 | 未设置 |
POSTHOG_KEY | 跟踪服务 | Rust 错误和 panic,直接提交到采集端点 | 未设置 |
POSTHOG_KEY | 实时服务 | Elixir 异常,附带 rescue 位置捕获的堆栈跟踪 | 未设置 |
POSTHOG_HOST | 所有服务端服务 | 采集主机。可指向你运行的自托管 PostHog | https://us.i.posthog.com |
POSTHOG_ERROR_TRACKING | 所有服务端服务 | false 保留密钥用于产品分析,但不向 PostHog 上报异常 | true |
SENTRY_DSN | backend、consumer、worker | 服务端错误上报。在包括 prod 在内的所有环境中均可选 | 未设置 |
SENTRY_DSN | 表单服务 | 表单服务自身的错误 | 未设置 |
SENTRY_DSN | 跟踪服务 | Rust 错误和 panic。DSN 无效时记录日志并关闭上报,不会停止服务 | 未设置 |
SENTRY_DSN | 实时服务 | 有意将空字符串视为未设置,因为底层库对 "" 的拒绝会导致节点退出 | 未设置 |
WARMBLY_POSTHOG_KEY | Web、管理面板容器 | 浏览器错误跟踪,与其他 WARMBLY_* 值一样在容器启动时读取。同一密钥还用于下述产品分析和会话回放 | 未设置 |
WARMBLY_POSTHOG_KEY | 表单服务 | 注入公开表单页面,用于表单应用的页面浏览、Web 核心指标和浏览器错误。与表单服务自身的 POSTHOG_KEY 分开:一方是 Go 进程,另一方是访客加载的页面 | 未设置 |
WARMBLY_POSTHOG_HOST | Web、管理面板容器、表单服务 | 浏览器采集主机 | https://us.i.posthog.com |
WARMBLY_POSTHOG_ERROR_TRACKING | Web、管理面板容器、表单服务 | false 表示任何位置都不向 PostHog 上报浏览器异常,包括表单服务生成的页面。该密钥启用的其他功能继续运行 | true |
WARMBLY_POSTHOG_SESSION_REPLAY | Web、管理面板容器 | false 表示不录制任何会话,其他功能保持不变。表单页面无论此值为何都不录制 | true |
WARMBLY_COMPANY_LOGOS | Web 容器 | on 为使用公司域名邮箱的联系人显示公司标志,由浏览器向 DuckDuckGo 网站图标服务获取,只发送域名;off 显示姓名首字母。未设置时,自托管关闭,Warmbly Cloud 开启。参见对外调用 | 未设置 |
WARMBLY_SENTRY_DSN | Web、管理面板容器 | 同一功能的 Sentry 配置。未设置时绝不获取 SDK,也不联系任何主机 | 未设置 |
WARMBLY_SENTRY_DSN | 表单服务 | 注入公开表单页面,同时使用服务的 APP_ENV 作为环境标签 | 未设置 |
WARMBLY_SENTRY_ENVIRONMENT | Web、管理面板容器 | 浏览器事件携带的环境标签,与接收后端无关。默认为构建模式。表单页面不读取此项;表单服务会将自身的 APP_ENV 注入页面 | 未设置 |
APP_ENV | 所有服务端服务 | 同时作为该服务上报事件的环境标签。浏览器事件改用 WARMBLY_SENTRY_ENVIRONMENT,因为提供资源包的容器并不是上报错误的进程 | dev |
WARMBLY_RELEASE | tracking、realtime | 事件标注的构建版本。发布镜像从发布标签设置;Go 服务和前端则从自身构建标记读取相同值 | dev |
两种后端均未配置时,数据不会离开进程,也不会联系任何主机。控制台、管理面板和表单页面甚至完全不加载 SDK,因此连脚本都不存在;Go、Rust 和 Elixir 服务仍在自身日志中记录错误。
PostHog 密钥可对应 PostHog Cloud(美国或欧洲),也可通过 POSTHOG_HOST 对应你自己的 PostHog。Sentry DSN 可指向 Sentry Cloud、自托管 Sentry 或 GlitchTip 等兼容 Sentry 的服务器;Warmbly 不假设目标一定是 sentry.io。
服务端 PostHog 异常的 distinct ID 标识触发它的进程(warmbly-backend、warmbly-worker),每个事件都关闭个人档案,并禁用 IP 地理定位,因为地址属于服务器。浏览器异常则归属于应用已识别的登录用户,因此与该用户的会话回放关联显示。
异常仍会携带可搜索的上下文属性,便于排查:
| 属性 | 适用位置 | 用途 |
|---|---|---|
organization_id、user_id | 浏览器异常,以及请求范围内的服务端异常 | “显示此工作区遇到的全部错误。”这些是平铺属性,因此一个筛选条件也能找到服务端错误,即使服务端不识别个人 |
request_id | 服务端 panic,以及操作轨迹中包含失败调用的浏览器异常 | API 已在错误封装中返回的 ID,可在 PostHog 和后端日志中查找同一事件 |
http_route、http_method | 服务端 panic | 路由模式,绝非原始路径:按路由而非记录归并问题,针对 /wp-admin 的探测无法凭空生成问题条目 |
service、environment、release | 全部 | 对应哪个进程、部署和构建 |
浏览器异常还包含操作轨迹:此前访问的路由和失败的 API 调用,以及各自的方法、路径、状态和请求 ID。在控制台及管理面板中,开启 WARMBLY_POSTHOG_SESSION_REPLAY 时,异常还会关联到发生异常的会话回放;这两处录制内容见下述分析一节。
admin 面板和控制台还会为事件标注实际提供页面的构建版本。该值在镜像构建时固定,因为必须与上传源映射时的发布版本一致,之后无法通过容器变量更改。
源码映射(Source map)
没有源映射时,浏览器堆栈跟踪显示的是压缩代码。上传可选,只有构建获得凭据时才开启;因此分叉仓库、自托管构建和本地 pnpm build 均不生成源映射,发布的资源包保持相同。
| 构建变量 | 后端 | 作用 |
|---|---|---|
POSTHOG_CLI_API_KEY、POSTHOG_CLI_PROJECT_ID | PostHog | 同时设置两项后,构建会在 vite build 后运行 pnpm sourcemaps:posthog,注入代码块 ID、上传并删除已发送的 .map 文件。web、admin 和 forms 镜像都会执行,因此表单页面的堆栈跟踪也可读取 |
POSTHOG_CLI_HOST | PostHog | 上传目标实例,用于 PostHog EU 或自托管实例 |
SENTRY_AUTH_TOKEN、SENTRY_ORG、SENTRY_PROJECT | Sentry | 三项均设置后,构建会加入 Sentry Vite 插件 |
本仓库的发布工作流将两个项目 ID 保存为仓库变量,将两把密钥保存为仓库机密,并以构建机密形式传入镜像构建。应用中无须匹配发布名称:PostHog 通过 CLI 注入实际资源文件的代码块 ID,将堆栈帧与源映射配对,因此只要对应构建的源映射已上传,任何构建的事件都能解析。
静态托管使用 pnpm build:pages 而非镜像构建;在托管平台构建环境中设置相同两个变量后,会执行相同上传。此配置很关键:未上传源映射时,PostHog 会尝试从网站自身获取 <bundle>.js.map,静态主机却返回 SPA 后备页面,导致每个问题的所有堆栈帧都在压缩函数名旁显示 Invalid source map: bad json。
产品分析与会话回放
这里使用与上述错误跟踪相同的两把密钥,它们是为托管服务提供的。自托管实例未设置密钥时,不加载分析代码,也不发送使用数据:安装程序从不询问这些值,未设置任何密钥的构建也不包含需要拦截的分析脚本。设置密钥的实例会启用以下内容。
| 变量 | 读取方 | 作用 | 默认值 |
|---|---|---|---|
POSTHOG_KEY | 后端 | 服务端产品事件(注册完成、开始试用、开始订阅),使用事件对应用户的账户 ID,并将工作区作为分组 | 未设置 |
POSTHOG_HOST | 后端 | 采集主机。可指向你运行的自托管 PostHog | https://us.i.posthog.com |
WARMBLY_POSTHOG_KEY | Web、管理面板容器 | 浏览器分析和会话回放,在容器启动时读取。未设置时绝不获取 SDK 代码块 | 未设置 |
WARMBLY_POSTHOG_HOST | Web、管理面板容器 | 浏览器采集主机 | https://us.i.posthog.com |
WARMBLY_POSTHOG_SESSION_REPLAY | Web、管理面板容器 | false 保留分析功能,但不录制会话 | true |
PUBLIC_POSTHOG_KEY | site/ 构建 | 营销网站分析。构建时配置;未设置会生成完全没有分析代码的网站 | 未设置 |
PUBLIC_POSTHOG_HOST | site/ 构建 | 网站采集主机 | https://us.i.posthog.com |
设置密钥后,各界面发送以下内容:
| 界面 | 身份 | 采集内容 |
|---|---|---|
| 控制台 | 已登录账户(使用邮箱和姓名执行 identify),以及作为 organization 分组的工作区 | 页面访问和离开、全部点击自动采集、热力图、愤怒点击和无响应点击、Web 核心指标及网络耗时、包含控制台日志的会话回放、问卷、异常,以及命名事件 mailbox_connected 和 campaign_launched |
| 管理面板 | 已登录运维人员 | 相同采集内容,以相同方式查找运维界面中的缓慢页面或失败操作 |
| 营销网站 | 不识别任何人。无 Cookie:浏览器中不存储任何内容,也从不创建个人档案 | 包含滚动深度的页面访问和离开、自动采集、热力图、愤怒点击和无响应点击、Web 核心指标、异常。不进行会话回放:无 Cookie 模式下 SDK 没有会话,录制还需要同意横幅 |
| 公开表单页面 | 不识别任何人。针对填写客户表单的访客,使用无 Cookie 模式 | 页面访问、自动采集、热力图、Web 核心指标、异常,以及漏斗事件 form_viewed、form_started 和 form_submitted,每项携带表单公开 ID。绝不录制会话 |
控制台用户与服务端事件中的用户使用同一账户 ID,因此后端记录的注册、促成注册的会话,以及后续 Stripe Webhook 报告的订阅属于同一漏斗。营销网站带入注册链接的获客参数在注册时一次性写入该用户。国家信息由 PostHog 根据事件来源地址执行自身 GeoIP 解析;上述 /ingest 代理正是为此转发访客地址,服务端事件也会附带注册请求的地址。
会话回放会在发送前,先在浏览器内遮蔽密码字段输入。其他输入和全部屏幕都会录制,这使回放能准确还原发生的事情,也是托管服务隐私页面明确如此描述的原因。
推送通知
| 变量 | 作用 | 默认值 |
|---|---|---|
APNS_KEY 或 APNS_KEY_PATH,以及 APNS_KEY_ID、APNS_TEAM_ID、APNS_TOPIC | 后端和消费者的移动推送。APNS_KEY 是 PEM 或 base64 形式的 .p8 文件,APNS_TOPIC 是应用 Bundle ID。配置不完整时禁用推送并警告,绝不会导致崩溃 | 未设置 |
更新
| 变量 | 作用 | 默认值 | 是否需要重启 |
|---|---|---|---|
UPDATE_CHECK_ENABLED | 轮询 GitHub Releases 检查新版 Warmbly,并显示在管理面板顶栏及设置与健康页面 | true | 是 |
UPDATE_CHECK_INTERVAL | 发布检查频率,最短 5m | 30m | 是 |
UPDATE_CHANNEL | stable 跟随正式版本;dev 也提供预发布版本 | stable | 是 |
RELEASES_GITHUB_REPO | 将哪个 owner/repo 的发布视为 Warmbly 版本。分叉实例应指向自己的分叉仓库 | warmbly/warmbly | 是 |
RELEASES_GITHUB_TOKEN | 可选 GitHub 令牌,仅用于提高 API 限额 | 未设置 | 是 |
UPDATER_URL | 执行更新(拉取、重新构建、重启)的主机侧更新器。未设置或设为 none 时,面板只报告更新 | Compose 下为 http://updater:8095 | 是 |
UPDATER_TOKEN | 后端提交给更新器的 Bearer 令牌,更新器可拉取并重启实例。应使用独立值,避免跟踪和表单服务持有的令牌也能启动更新;安装程序会生成此值 | INTERNAL_API_TOKEN | 是 |
更新器自身变量(UPDATER_MODE、UPDATER_COMMAND、UPDATER_REPO_DIR 等)见更新;它是独立进程,其环境不会显示在配置页面。
表单服务
托管表单的公开服务(cmd/forms):在独立端口提供从 forms/ 构建的 React(TanStack)表单应用、各表单页面外壳、嵌入加载器及公开提交,使表单流量不接触 API 来源。它读取自身环境;以下项目除 FORM_IP_RATE_LIMIT 外均不显示在管理面板。
| 变量 | 作用 | 默认值 |
|---|---|---|
FORMS_PORT | 监听端口 | 8090 |
FORMS_STATIC_DIR | 构建后的表单应用(pnpm build 输出)。该位置缺少 index.html 时,服务在启动时退出 | forms/dist |
BACKEND_INTERNAL_URL | 服务解析表单和转发提交的地址,与跟踪服务使用相同变量。必需:缺少时服务在启动时退出 | 无 |
INTERNAL_API_TOKEN | 这些调用的 Bearer 令牌,必须与后端一致。必需:空值会导致服务在启动时退出 | 无 |
FORM_IP_RATE_LIMIT | 每个表单服务实例中,每个来源 IP 每 10 分钟允许的公开表单提交数 | 30 |
POSTHOG_KEY、POSTHOG_HOST、POSTHOG_ERROR_TRACKING | 服务自身的错误跟踪。未设置表示关闭 | 未设置 |
SENTRY_DSN | 通过 Sentry 实现相同功能,可与 PostHog 并用或替代它 | 未设置 |
WARMBLY_POSTHOG_KEY、WARMBLY_POSTHOG_HOST、WARMBLY_POSTHOG_ERROR_TRACKING | 注入表单页面,使浏览器应用也上报。未设置或关闭错误跟踪时,页面完全不加载上报 SDK | 未设置 |
WARMBLY_SENTRY_DSN | 同一页面注入功能的 Sentry 配置 | 未设置 |
TRUSTED_PROXIES | 服务信任其 X-Forwarded-For 的 CIDR 网段,与后端约定相同。留空则不信任任何代理,使用套接字对端地址;使用反向代理时应设置,否则提交限流器会限制代理地址,而非访客地址 | 空 |
跟踪服务
这是 Rust 打开和点击跟踪服务。它读取自己的环境,因此必须在该容器上设置这些变量;它们均不显示在管理面板。
请只运行一个副本。事件去重、按来源限流及点击票据缓存都保存在各进程内存中,没有共享存储。因此第二个副本不会将工作减半,而会让洪泛来源获得双倍额度,并允许同一次打开发布两次。一个容器即可轻松处理大型实例的像素与点击负载;应扩容工作节点。如果必须运行多个副本,请在前方按来源地址做粘性哈希,将同一来源固定到一个进程,以恢复去重和限流特性。
| 变量 | 作用 | 默认值 |
|---|---|---|
TRACKING_HOST、TRACKING_PORT | 监听地址 | 0.0.0.0、3000 |
BACKEND_INTERNAL_URL | 跟踪服务解析不透明 /c/<id> 点击票据的地址。必需:缺少时服务在启动时退出 | 无 |
INTERNAL_API_TOKEN | 查询所用的 Bearer 令牌。必需:空值会导致服务在启动时退出 | 无 |
TRACKING_RATE_LIMIT_PER_MIN | 每个来源每分钟计入统计的像素和点击请求数。超额后仍提供像素但不计数,点击重定向返回 429 | 300 |
TRACKING_PAGEHIT_RATE_LIMIT_PER_MIN | 每个来源每分钟允许的网站页面浏览数,在上述共享额度之外另行限制。超额后跟踪代码收到 429 | 60 |
TRACKING_TRUSTED_PROXIES | 跟踪服务接受转发客户端地址的 CIDR 网段。留空则不信任任何代理,使用套接字对端地址,适合直接暴露服务。使用反向代理时应设置,否则按来源限流及页面浏览保存的位置可被调用方控制。约定与后端的 TRUSTED_PROXIES 相同 | 空 |
TRACKING_IP_HASH_KEY | 用于生成跟踪事件中来源地址令牌的密钥。令牌标识一个来源,用于去重、限流和点击突发规则;使用密钥后,无法通过枚举还原地址 | INTERNAL_API_TOKEN |
TRACKING_CLIENT_IP_HEADER | 可信代理写入客户端地址的唯一标头。不读取其他标头,防止调用方通过 CF-Connecting-IP 绕过通用代理夹带地址。x-forwarded-for 使用代理追加的最后一项;Cloudflare 后方应设为 cf-connecting-ip | x-forwarded-for |
TRACKING_SCANNER_BUILTINS | 是否加载 Warmbly 自带的已知扫描器目录:Microsoft 365 Exchange Online Protection 网段和 Barracuda Email Gateway Defense 地址块。false 表示仅保留自定义条目 | true |
TRACKING_SCANNER_NETWORKS | 额外扫描器来源,以逗号分隔,每项为 CIDR 或 asn:<number>,可附加标签。适用于从不承载真实用户请求的来源,其像素抓取和点击都视为自动行为 | 空 |
TRACKING_SCANNER_CLICK_NETWORKS | 相同配置,但适用于也代理邮件客户端自身图片抓取的来源。仅将其点击票据视为自动行为,因为像素抓取是真实打开 | 空 |
TRACKING_SCANNER_ASN_DB | MaxMind GeoLite2-ASN 数据库路径,使边缘代理未写入 ASN 标头的实例也能匹配 asn: 条目。它与 GEODB_PATH 是不同文件;未设置或不可读时,启动时记录一行日志并关闭 ASN 匹配。它消除了边缘标头需求,但不能替代 TRACKING_TRUSTED_PROXIES:查询读取客户端地址,反向代理后方未设置该变量时,客户端地址就是代理地址 | 未设置 |
TRACKING_SCANNER_ASN_DB_URL | 上述路径未挂载文件时的数据库下载地址。结果仅保存在内存,从不写入磁盘,因此不需要可写路径或卷。MaxMind 永久链接中携带许可证密钥,因此被视为机密且不记录原值。参见 GeoIP | 未设置 |
TRACKING_SCANNER_ASN_HEADER | 可信代理写入来源 ASN 的标头,可替代上述数据库;两者均设置时优先使用标头。仅接受 TRACKING_TRUSTED_PROXIES 中列出的代理。在 Cloudflare 上,可通过转换规则写入 ip.src.asnum | 空 |
EVENTBUS_PROVIDER | nats 或 kafka。Kafka 需要 tracking:*-kafka 镜像,或使用 CARGO_FEATURES=kafka 构建 | nats |
NATS_URL、NATS_SUBJECT_PREFIX | JetStream 地址和主题前缀。发布主题为 <prefix>.<topic> | nats://localhost:4222、warmbly |
CODEC_PROVIDER | json 或 avro,必须与消费者一致。avro 缺少 SCHEMA_REGISTRY_URL 时会在启动时被拒绝 | json |
KAFKA_TRACKING_TOPIC | 事件主题,由 Rust 发布器以及 Go 订阅器读取 | tracking-events |
KAFKA_BOOTSTRAP_SERVERS、KAFKA_SASL_USERNAME、KAFKA_SASL_PASSWORD | EVENTBUS_PROVIDER=kafka 时使用的消息代理传输配置 | 未设置 |
SCHEMA_REGISTRY_URL、SCHEMA_REGISTRY_KEY、SCHEMA_REGISTRY_SECRET | Avro 编解码器使用的注册表 | 未设置 |
AWS_CONFIG_ENABLED | true 表示对环境中缺少的值回退到 AWS SSM 和 Secrets Manager | false |
APP_ENV | 日志和上报错误使用的环境标签 | dev |
POSTHOG_KEY、POSTHOG_HOST、POSTHOG_ERROR_TRACKING | 错误和 panic 上报。未设置表示关闭 | 未设置 |
SENTRY_DSN | 通过 Sentry 实现相同功能。无效值会记录日志并关闭上报,不会停止服务 | 未设置 |
WARMBLY_RELEASE | 上报错误标注的构建版本 | dev |
目录条目格式为 <source> <scope> <certainty> <label>。权限范围决定可判断哪些请求:all 用于从不承载真实用户请求的网络;clicks 用于也代理邮件客户端图片抓取的网络,其像素抓取属于真实打开。确定性决定匹配能得出多强的结论,默认为 certain:该来源只执行邮件过滤,因此一旦匹配即可确定结果。probable 表示来源也可能承载用户操作;邮件安全厂商的浏览器隔离会产生这种情况,因此匹配只会扩大消费者的机器行为判定窗口(tracking.machine_window_probable_seconds),而不直接定论。这两列相互独立。probable 条目只会将事件从按人工计数改为按自动行为计数,绝不会让扫描计入互动;窗口外则与未知来源完全相同地判断。代价是:收件人在窗口内真实点击也会被归为自动行为;另外,来自any已识别网络的点击都会在重定向时移除识别票据,因为边缘层没有调度时间,无法像消费者那样区分投递时扫描和隔离环境中的人工点击。因此,即使消费者后来将某次隔离点击计为人工,该联系人仍不会获得网站访问归因。
asn: 条目需要来源 ASN,可通过两种方式获取。TRACKING_SCANNER_ASN_DB 直接根据地址解析,无须 ASN 标头或转换规则,适用于 nginx、Caddy、云负载均衡器后方或直接公开的实例。TRACKING_SCANNER_ASN_HEADER 从边缘代理写入的标头读取,Cloudflare 上只需一条转换规则。两者均配置时,只要请求携带标头,就优先使用标头,因此添加数据库不会改变边缘已给出的判断;标头未覆盖的请求才回退到数据库。
在反向代理后方,两种方式都不能替代 TRACKING_TRUSTED_PROXIES,原因不同:标头只从其中列出的对端读取,因此变量为空时,标头不会解析出任何结果;数据库读取客户端地址,变量为空时该地址是代理地址,因此查到的是代理网络而非请求者网络。如果完全没有可用来源,目录的 ASN 部分就不生效,跟踪服务会在启动时明确提示,并列出两种来源。
默认启用哪些条目及采用何种方式,由上述区别决定。Microsoft 365 Exchange Online Protection 网段和 Barracuda Email Gateway Defense 地址块属于邮件过滤层本身,收件人不会在那里阅读邮件,因此标为 certain,匹配即可直接判定。Proofpoint、Mimecast 和 Cisco Secure Email 按 ASN 匹配并标为 probable:它们虽是专用邮件安全网络,但 Proofpoint Isolation 和 Mimecast Browser Isolation 会在厂商云端渲染点击页面并流式传给收件人,因此该来源的点击可能来自真实用户。隔离通常适用于尚未分类的 URL,而新的开发邮件域名正属于此类。较可能为机器行为的窗口使这些条目可以安全地默认启用:扫描发生在发送后几分钟内,隔离点击则发生在用户实际查看邮件时。
Microsoft 和 Google 的其余网络即使标为 probable 也保持注释,因为它们是整体云地址分配,不是专用邮件安全网络。Windows 365、Azure Virtual Desktop 或企业 NAT 网关后的收件人可能全天都使用这些网络,因此匹配几乎不能说明请求者身份。tracking/scanner-networks.txt 记录每个条目,也是修改条目的位置。
实时服务
这是 Elixir WebSocket 服务。只有发布版本以 prod 启动时才读取运行时配置,随附镜像正是以此方式运行。与跟踪服务一样,它读取自身环境,不会显示在管理面板中。
| 变量 | 作用 | 默认值 |
|---|---|---|
JWT_SECRET | 必须等于后端的 AUTH_SECRET。必需:缺少时服务拒绝启动 | 无 |
SECRET_KEY_BASE | Phoenix 会话签名。必需 | 无 |
DATABASE_URL | Postgres,用于验证 API 密钥。必需 | 无 |
REDIS_URL | 后端发布事件所用的 Redis 桥接 | redis://localhost:6379/0 |
PHX_HOST | 服务自身的主机名 | localhost |
PORT | 监听端口 | 4000 |
CHECK_ORIGIN_HOSTS | 浏览器允许发起套接字连接的来源,以逗号分隔,例如 https://app.example.com,https://admin.example.com。不是此服务自身的主机 | 未设置 |
CHECK_ORIGIN | 旧版基于主机名的形式,仅在 CHECK_ORIGIN_HOSTS 为空时读取。true 表示仅接受来自 PHX_HOST 的升级请求 | false |
PUBSUB_ENABLED | true 表示用 Google Pub/Sub 替代 Redis 桥接 | false |
GCP_PROJECT_ID | PUBSUB_ENABLED=true 时必需;缺少则服务拒绝启动 | 未设置 |
MAX_CONNECTIONS_PER_USER | 单个账户允许的并发套接字数。同时受调用者套餐上限约束,取较低值 | 10 |
MAX_CONNECTIONS_PER_IP | 单个地址的并发套接字数 | 50 |
MAX_CONNECTIONS_GLOBAL | 此节点的并发套接字数 | 100000 |
RATE_LIMIT_WS_MESSAGE | 每分钟 WebSocket 消息数 | 120 |
RATE_LIMIT_WS_CONNECT | 每分钟套接字握手数 | 30 |
RATE_LIMIT_WS_JOIN | 每分钟频道加入次数,在已打开套接字上按每次 phx_join 计数 | 30 |
RATE_LIMIT_WS_EVENT | 每分钟客户端事件数,用于限制在线状态更新 | 60 |
POSTHOG_KEY、POSTHOG_HOST、POSTHOG_ERROR_TRACKING | 错误上报。未设置表示关闭 | 未设置 |
SENTRY_DSN | 通过 Sentry 实现相同功能。有意将空字符串视为未设置,因为底层库对 "" 的拒绝会导致节点退出 | 未设置 |
WARMBLY_RELEASE | 上报错误标注的构建版本 | dev |
列出允许的来源后才会启用来源检查
两个变量都未设置时,接受任意来源的 WebSocket 升级。打开套接字仍需要短期票据,每次加入频道都会检查成员资格,因此攻击者无论如何都需要真实凭据。可从互联网访问的部署,应将 CHECK_ORIGIN_HOSTS 设置为控制台和管理面板的来源。
请列出浏览器发起连接时所在的页面来源,而非 WebSocket 主机。升级请求的 Origin 标头指定打开套接字的页面,也就是控制台。如果设置 CHECK_ORIGIN=true 并将 PHX_HOST 指向 WebSocket 主机名,会拒绝所有真实连接;此变量正是用于避免这个陷阱。安装程序会替你正确设置 CHECK_ORIGIN_HOSTS。
存储在数据库中的设置
以下是唯一能通过浏览器修改的设置,均不受环境变量控制。它们位于管理面板的 实例 > 配置 > 设置(/configuration?tab=settings)。各进程会缓存读取结果 30 秒,因此修改会在此时间窗口内于所有位置生效。
| 设置 | 类型 | 默认值 | 作用 |
|---|---|---|---|
invitations.ttl_hours | 整数,1 至 720 | 168 | 新邀请的有效期。写入邀请记录时读取,因此仅适用于修改后创建的邀请,不影响已有邀请 |
invitations.links_enabled | 布尔值 | true | 是否返回可复制的邀请链接。关闭后,GET /organization/invitations/:id/link 返回 404 及原因说明,使邀请只能通过邮件送达 |
access.allow_invited_signup | 布尔值 | true | 在 invite_only 模式下,持有有效邀请是否允许自行创建账户。关闭后,所有账户都必须由管理员通过 warmblyctl user create 创建 |
sync.backfill_days | 整数,1 至 730 | 90 | 新连接邮箱首次导入向前追溯的天数,按从新到旧顺序导入 |
sync.backfill_messages | 整数,1 至 100000 | 5000 | 每个邮箱在该次导入中最多存储的邮件数 |
sync.daily_messages_per_mailbox | 整数,1 至 100000 | 2000 | 每个邮箱在每个 UTC 日允许存储的新实时邮件数。超限后等到次日;对该邮箱自身发出邮件的回复使用独立、同等大小的额度 |
sync.daily_messages_per_org | 整数,1 至 2000000 | 25000 | 每个组织在每个 UTC 日允许存储的新邮件与导入邮件总数 |
retention.engagement_event_days | 整数,1 至 3650 | 365 | 逐事件打开和点击日志(客户端、设备、大致位置)的保留期限。营销活动进度自行保存更长期的汇总,因此计数、筛选和分支判断不受影响 |
retention.form_event_days | 整数,1 至 3650 | 180 | 表单漏斗事件(浏览、开始填写、字段级流失)的保留期限。漏斗报告最多覆盖 90 天,因此更短期限也会缩短报告范围 |
retention.audit_log_days | 整数,1 至 3650 | 90 | 审计记录的保留期限。记录包含 IP 地址、用户代理和变更内容,因此也是这些数据的保留期限 |
retention.warmup_mail_days | 整数,3 至 3650 | 30 | 预热邮件在各邮箱中保留多久后由 Warmbly 删除,同时删除已存正文副本;无论邮箱归档设置将邮件放在哪里都会处理(Gmail 移入垃圾箱)。邮箱可在抽屉的预热选项卡或通过 API 的 warmup_retention_days 设置自身期限;其余邮箱遵循此值。最短期限为互动及线程回复完成预留时间 |
retention.warmup_event_days | 整数,30 至 3650 | 365 | 逐邮件预热记录(令牌、回执、篡改事件、垃圾邮件报告)的保留期限。预热池健康等级读取最近 30 天,因此以此为下限。预热分析所用的每日收发数量单独保存,永不清理 |
tracking.machine_window_open_seconds | 整数,1 至 900 | 60 | 发送被调度后多久内的打开被记录为自动行为,而非人工。计时从发送交给工作节点时开始,因此还包含发信服务商队列和传输到收件人的时间,而非仅阅读时间。投递时扫描被计为打开时应增大;立即阅读的收件人被漏计时应减小。修改在一分钟内生效,只影响之后记录的事件 |
tracking.machine_window_click_seconds | 整数,1 至 900 | 30 | 点击票据的相同时间窗口。与打开分开设置,因为误判代价不同:误判打开会损失统计指标,误判点击则会错过有兴趣潜在客户触发的自动化 |
tracking.machine_window_probable_seconds | 整数,1 至 86400 | 600 | 跟踪服务将来源识别为也会为用户渲染点击页面的邮件安全网络时,使用此窗口替代前两项,例如通过浏览器隔离实现此功能的 Proofpoint、Mimecast 和 Cisco。这种匹配无法确定请求背后是否有人,因此只扩大窗口,不直接判定:窗口内视为投递时扫描,窗口后视为稍后查看邮件的收件人。实际使用时绝不短于对应事件类型的原窗口,因此命名网络只会捕获更多扫描。上限可达一天,因为厂商需要多久完成链接沙箱检测取决于厂商,而非实例 |
deliverability.enforce_domain_auth | 布尔值 | true | 发信域名未通过 SPF 或 DMARC 时,是否停止该域名所有邮箱的冷邮件营销活动发送和预热发送。关闭后仍执行检查,并显示状态和顾问卡片,只是不再阻止发送 |
deliverability.auth_grace_hours | 整数,1 至 720 | 72 | 域名必须持续失败多久才启用发送阻断。计时从后台检查首次发现失败开始,因此也是所有者获得的预警时间 |
warmup_generation | object | 现有已保存的提供方和模型配置,启用生成,并采用有界控制默认值 | 实际使用的提供方、频率、停止开关、每日预留上限、AI 选择比例、内容池及规范回复数量控制。编辑前请先查看全部支持字段和默认值;批次尚在处理时不要移除凭据 |
placement.tests_per_month_trial | 整数,1 至 100000 | 3 | 免费试用工作区每个自然月可在实例种子面板运行的免费投递位置测试次数。只有使用 DEPLOYMENT_MODE=cloud 的实例计量,自托管实例从不统计自身测试额度。使用工作区自有种子邮箱的测试始终不计数 |
placement.tests_per_month_paid | 整数,1 至 100000 | 40 | 付费套餐工作区的相同免费额度。用完后,每次测试消耗 placement.credits_per_test 积分 |
placement.seeds_per_test | 整数,1 至 100 | 20 | 一次测试最多发送到多少个种子邮箱。每份副本都占用发信邮箱每日发送额度,因此也决定单次测试的额度消耗。测试会按当日剩余额度缩减规模,少于五个种子时拒绝执行 |
placement.spacing_seconds | 整数,5 至 600 | 60 | 同一邮箱发送测试两份副本的平均间隔,向上或向下随机浮动最多三分之一。快速模式测试使用此值与 8 秒中的较小值 |
placement.credits_per_test | 整数,0 至 10000 | 25 | 付费工作区用完当月免费测试次数后,在实例面板执行一次测试的积分费用。每次付费测试前工作区需同意价格;一份副本都未投递的测试会退款。0 关闭付费测试。只有使用 DEPLOYMENT_MODE=cloud 的实例计量,自托管实例从不收费 |
placement.batch_senders_max | 整数,1 至 100000 | 10000 | 单个投递位置测试批次最多包含的发信邮箱数。仅为批次大小的基础设施保护;同时运行数量由下一项设置控制 |
placement.batch_sender_concurrency | 整数,1 至 500 | 20 | 一个工作区所有批次中,最多有多少邮箱可同时发送测试副本。某邮箱发完全部副本后,下一个邮箱才开始;各邮箱仍遵循自身每日限额和最小间隔 |
placement.batch_instance_concurrency | 整数,1 至 5000 | 200 | 所有工作区合计的相同限制。控制实例种子面板同时接收的批量测试邮件,避免种子邮箱一小时内收到过多邮件而触发会停用邮箱的同步洪泛规则 |
placement.batch_starts_per_minute | 整数,1 至 600 | 10 | 单个批次每分钟启动的邮箱数,使大型批次逐步提升负载,而非一次启动全部并发 |
四项 sync.* 值由后端在将邮箱加载到工作节点时读取,包括连接、重新分配,以及协调器定期重新发布。因此更改后无须重启,30 分钟内会覆盖所有邮箱(每个邮箱每 30 分钟重新发布一次,错开执行,避免工作节点同时收到全部邮箱)。相关固定节奏值(每五分钟突发量、每小时限额、历史回填速率、洪泛阈值和长期超额规则)是编译常量,列在 实例 > 配置 > 有效限制 下;额度行为见邮箱。四项 sync.* 还在 运维 > 同步 提供配套查看页面,展示各邮箱的历史回填进度及公平使用限流状态,并可清除限流或重新开始回填。
五项 retention.* 值在每轮清理时读取,因此缩短期限会在下次清理生效,无须等到重启。删除永久生效,没有宽限期;已超出新期限的数据会在该轮删除。预热邮件期限还会作用于客户邮箱:消费者终止各封邮件的保留状态,持有邮箱的工作节点在邮箱中删除。因此缩短期限后,整个实例的预热文件夹会在数小时内清理至新窗口。各日志内容及缩短期限的影响见数据控制。
无人值守安装可在任何人登录前,通过 WARMBLY_SETTINGS_BOOTSTRAP 初始化整份配置文档,其值为与管理 API 接受形式相同的部分 JSON:
WARMBLY_SETTINGS_BOOTSTRAP={"sync":{"backfill_days":30},"retention":{"audit_log_days":30}}只有设置记录从未写入过时才会应用,因此从第一次在面板保存开始,以面板为准,此变量不再产生效果。回答保留期限问题后,install.sh --wizard 就会写入该变量。
两项 deliverability.* 值在每轮调度和每次预热发送时读取,因此关闭阻断会在 30 秒缓存窗口内释放被阻止的邮箱。重新开启不会追溯性地立即停止发送:域名仍需先持续失败整个宽限期。只有持续失败才会阻断,因此状态为 unknown 的域名(从未检查、DNS 无响应,或无法解析的特殊用途域名)始终可发送。手动发送和统一收件箱回复从不受此阻断,详见域名身份验证。
placement.* 的额度、种子数量和间隔值在测试开始时读取,因此更改只影响下一次测试,不影响已开始发送的测试。四项 placement.batch_* 值在批次执行器每轮运行时读取,约每 30 秒一次,因此修改一分钟内影响运行中的批次;降低并发不会停止已经发送的邮箱,只延迟下一个邮箱启动。零或负值会解析为默认值,超出范围的值会限制到上限。哪些邮箱组成实例种子面板并非配置项,而由管理员在管理面板中标记。
更改会写入审计记录;每个值在服务端写入和读取时都会验证并限制范围,因此旧版本写入的记录仍能正确解析。
名称与实际作用不完全一致的变量
| 变量 | 实际行为 |
|---|---|
KAFKA_CLUSTER | 无效果。存在加载器,但没有调用方。可以删除 |
SENTRY_DSN_API | 同样无效果。已被 SENTRY_DSN 取代 |
CAPTCHA_PROVIDER | 会读取,但未设置时采用派生值,而非固定默认值。参见人机验证 |