Konfiguration
Postbox nutzt sechs Custom Config-Dateien und eine angepasste Laravel-Queue-Config neben den Laravel-Standard-Konfigurationen. Alle projektspezifischen Werte sind ueber .env-Variablen steuerbar.
Location: config/
Config-Dateien Uebersicht
| Datei | Zweck |
|---|---|
postbox.php | Zentrale App-Konfiguration (Admin, Rotation, Health, Sanitizer, Retry, Tags, Mail, Notifications) |
queue.php | Laravel Queue mit angepasstem retry_after (900s) fuer langlebige Jobs |
collector.php | Collector-Job-Settings (Lease TTL) |
feedback.php | Feedback-Feature (Empfaenger, Rate-Limit) |
google_api_usage.php | Google API Quota Monitoring |
server-monitoring.php | Server-Alert-Schwellwerte und Metriken |
vantage.php | Vantage Queue Monitoring Dashboard |
Doppelt gesetzte Schlüssel in der .env
Eine Variable, die zweimal in derselben .env steht, ist syntaktisch gültig und erzeugt keine Warnung. Es gilt der zuletzt gesetzte Wert; die früheren Zeilen sind wirkungslos.
Das ist am echten Code-Pfad gemessen, nicht aus der Bibliothek abgeleitet: phpdotenv schützt nur Variablen, die schon vor dem Laden in der echten Umgebung standen — innerhalb derselben Datei überschreibt eine spätere Zeile eine frühere.
Warum das eine eigene Prüfung braucht
Der Anlassfall stand monatelang unbemerkt in der Produktions-.env:
DB_QUEUE_RETRY_AFTER=3600
DB_QUEUE_RETRY_AFTER=5400
Es ging gut aus — die gewollte Zeile stand zufällig unten. Hätten sie andersherum gestanden, wäre eine Einstellung dagewesen, hätte nicht gegolten, und nichts hätte darauf hingewiesen.
Dubletten entstehen durch Anhängen statt Ändern — die naheliegendste Art, eine .env zu pflegen, besonders wenn eine Anleitung sagt „setze X auf Y".
Die Prüfung
# Die .env dieser Installation
php artisan env:lint
# Eine bestimmte Datei
php artisan env:lint /pfad/zur/.env
# Für den Einsatz im Deploy: schweigt, solange nichts zu melden ist
php artisan env:lint --quiet-when-clean
| Exit-Code | Bedeutung |
|---|---|
0 | keine Dublette |
1 | mindestens ein Schlüssel mehrfach gesetzt |
2 | Datei nicht lesbar — ausdrücklich NICHT als „sauber" zu lesen |
Der dritte Code ist der wichtigste. „Nichts gefunden" und „nicht nachgesehen" erzeugen dieselbe Ausgabe, wenn man sie nicht trennt — und wer den Befehl in einen Deploy hängt, verlässt sich darauf, dass ein grüner Ausgang etwas bedeutet.
Wo die Prüfung von selbst läuft
| Ort | Was |
|---|---|
| Testsuite | Ein Test hält .env.example frei von Dubletten — sie ist die Vorlage, aus der jede neue Umgebung entsteht, und eine Dublette darin wird in jede kopiert. |
| Zeitplan | env:lint --quiet-when-clean täglich um 06:45 UTC, mit dem Heartbeat env_lint. Ein Fund endet mit Exit 1, der Heartbeat wird als fehlgeschlagen gebucht, und der Fund selbst geht ins Log, wo der Admin-Bereich ihn anzeigt. |
⚠️ Was ins Log geht, ist redigiert — und das ist Absicht. Die Konsolen-Ausgabe nennt die
Werte, weil jemand, der auf dem Server vor dem Befund steht, wissen muss, welcher Wert gilt. Ins
Log gehen nur Schlüsselname, Zeilennummern und das Urteil „gleiche" oder „verschiedene
Werte". Eine .env besteht aus Zugangsdaten, und ein Log hat ein Nachleben — es wird
durchsucht, exportiert und an einen Fehler-Tracker weitergereicht.
Bis 2026-09-04 ging der Fund gar nicht ins Log. Der geplante Lauf hinterliess damit nur
Scheduled command [...] failed with exit code [1] im Fehler-Tracker — 38-mal seit Juni, ohne
ein einziges Mal zu sagen, welcher Schlüssel doppelt stand.
| Deploy (optional, serverseitig) | Der Befehl mit --quiet-when-clean in das Deploy-Skript, damit ein Deploy an einer Dublette scheitert statt sie mitzunehmen. |
Was der Detektor NICHT für eine Dublette hält
Ein Falsch-Positiv wäre hier schlimmer als eine Lücke — wer eine Warnung über eine korrekte Datei bekommt, schaltet die Prüfung ab. Deshalb sind diese Fälle ausdrücklich behandelt und einzeln getestet:
- Auskommentierte Zeilen (
# KEY=alt) — der häufigste Grund, warum ein Schlüssel mehrfach dasteht und nur einmal gilt. - Das
export-Präfix gehört nicht zum Schlüsselnamen. - Werte mit Gleichheitszeichen (
DSN=host=db;user=app) — getrennt wird am ersten=. - Mehrzeilige gequotete Werte — ein eingebettetes Zertifikat enthält Zeilen, die wie Zuweisungen aussehen.
- Prosa mit Gleichheitszeichen in Kommentaren.
Ein leerer Wert (KEY=) zählt dagegen mit: Er setzt den Schlüssel auf leer und überschreibt damit eine vorige Zeile.
postbox.php
Zentrale Konfiguration mit allen projektspezifischen Einstellungen.
Location: config/postbox.php
Admin
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
admin_emails | POSTBOX_ADMIN_EMAILS | '' | Kommaseparierte Admin-E-Mail-Adressen. Wird normalisiert (Lowercase, Trimming). |
Legal Pages
Pflicht-Checkboxen fuer Datenschutzerklaerung und Nutzungsbedingungen auf der Registrierungsseite. Die Checkboxen verlinken auf CMS-Seiten, deren Inhalt in Alpine.js-Modals angezeigt wird.
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
legal.privacy_page_id | LEGAL_PRIVACY_PAGE_ID | 0 | CMS Page-ID fuer Datenschutzerklaerung (0 = kein Modal-Link) |
legal.terms_page_id | LEGAL_TERMS_PAGE_ID | 0 | CMS Page-ID fuer Nutzungsbedingungen (0 = kein Modal-Link) |
YouTube
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
youtube_custom_url_search_fallback | POSTBOX_YOUTUBE_CUSTOM_URL_SEARCH_FALLBACK | false | Fallback ueber search.list fuer Custom-URL-Aufloesung (Quota-intensiv) |
youtube_rotation_days | POSTBOX_YOUTUBE_ROTATION_DAYS | 3 | Rotation-Kadenz fuer taegliche YouTube-Scrapes (Hard-Cap: max 4 Tage). 1 = taeglich. |
leaderboard_candidate_limit | POSTBOX_LEADERBOARD_CANDIDATE_LIMIT | 200 | Kandidaten-Limit fuer Priority-Scraping (Top/Flop + Rising Stars) |
Low-Priority-Rotation
Profile unter dem Follower-Threshold werden im langsameren Zyklus gescrapt. Gilt plattformuebergreifend fuer Instagram und YouTube. Favorites und PRO-Profile sind ausgenommen.
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
low_priority_follower_count | POSTBOX_LOW_PRIORITY_FOLLOWER_COUNT | 100 | Follower-Schwelle fuer Low-Priority (Minimum: 100). Profile darunter werden seltener gescrapt. Watched/favorited/PRO Profile sind ausgenommen. |
low_priority_rotation_days | POSTBOX_LOW_PRIORITY_ROTATION_DAYS | 7 | Rotation-Kadenz fuer Low-Priority-Profile (Hard-Cap: max 7 Tage) |
Instagram
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
instagram_rotation_days | POSTBOX_INSTAGRAM_ROTATION_DAYS | 3 | Rotation-Kadenz fuer taegliche Instagram-Scrapes (Hard-Cap: max 4 Tage) |
instagram_priority_default | POSTBOX_INSTAGRAM_PRIORITY_DEFAULT | 0 | Basis-Prioritaet fuer Rotation-Scrapes |
instagram_priority_low_priority_new_profile | POSTBOX_INSTAGRAM_PRIORITY_LOW_PRIORITY_NEW_PROFILE | 5 | Prioritaet fuer nie gescrapte Profile mit < 100 Followers (low-prio Imports) |
instagram_priority_catch_up | POSTBOX_INSTAGRAM_PRIORITY_CATCH_UP | 10 | Prioritaet fuer verpasste Scrapes |
instagram_priority_favorite | POSTBOX_INSTAGRAM_PRIORITY_FAVORITE | 20 | Prioritaet fuer Favoriten-Profile |
instagram_priority_candidate | POSTBOX_INSTAGRAM_PRIORITY_CANDIDATE | 40 | Prioritaet fuer Leaderboard-Kandidaten |
instagram_priority_top_leader | POSTBOX_INSTAGRAM_PRIORITY_TOP_LEADER | 50 | Prioritaet fuer Top-Leader |
instagram_priority_new_profile | POSTBOX_INSTAGRAM_PRIORITY_NEW_PROFILE | 60 | Hoechste Prioritaet fuer nie gescrapte Profile mit >= 100 Followers (regulaere Imports) |
Health Monitoring
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
health.token | HEALTH_TOKEN | null | Token fuer /up_system-Endpoint (X-Health-Token Header) |
health.failed_jobs_threshold | HEALTH_FAILED_JOBS_THRESHOLD | 100 | Warnschwelle fuer Failed Jobs |
health.collector_heartbeat_minutes | HEALTH_COLLECTOR_HEARTBEAT_MINUTES | 30 | Heartbeat-Timeout fuer Collector-Clients |
health.queue_quiet_start | HEALTH_QUEUE_QUIET_START | 4 | Queue Quiet Hours Start (UTC-Stunde, 0-23) |
health.queue_quiet_end | HEALTH_QUEUE_QUIET_END | 6 | Queue Quiet Hours Ende (UTC-Stunde, 0-23) |
Cron-Heartbeat-Monitoring (health.cron_heartbeats):
| Heartbeat | Max Minutes | Label |
|---|---|---|
instagram_scrape | 180 | Instagram Scrape (2h) |
youtube_scrape | 180 | YouTube Scrape (2h) |
explore_metrics | 1560 | Explore Metrics (daily) |
scores_calculate | 1560 | Score Calculation (daily) |
google_api_sync | 150 | Google API Sync (1h) |
queue_metrics | 45 | Queue Metrics (15min) |
server_alerts | 15 | Server Alerts (5min) |
cross_platform_auto_fill | 15 | Cross-Platform Auto-Fill (5min) |
Cross-Platform
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
cross_platform.stale_days | CROSS_PLATFORM_STALE_DAYS | 90 | Tage bis Re-Berechnung faellig wird |
Profile Retry
Steuerung der Retry-Strategie fuer deaktivierte Profile (Fail-Streak).
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
profile_retry.fail_streak_threshold | POSTBOX_FAIL_STREAK_THRESHOLD | 14 | Tage konsekutiver Fehler bis zur Auto-Deaktivierung |
profile_retry.fail_streak_cooldown_after | POSTBOX_FAIL_STREAK_COOLDOWN_AFTER | 3 | Ab welchem Fail-Streak der Cooldown greift |
profile_retry.fail_streak_cooldown_days | POSTBOX_FAIL_STREAK_COOLDOWN_DAYS | 3 | Tage zwischen Retry-Versuchen im Cooldown |
profile_retry.initial_retry_interval_days | POSTBOX_INITIAL_RETRY_INTERVAL_DAYS | 14 | Tage zwischen Retries (Phase 1: intensiv) |
profile_retry.initial_retry_count | POSTBOX_INITIAL_RETRY_COUNT | 3 | Anzahl Retries in der intensiven Phase |
profile_retry.monthly_retry_interval_days | POSTBOX_MONTHLY_RETRY_INTERVAL_DAYS | 30 | Tage zwischen Retries nach Phase 1 (monatlich) |
profile_retry.archive_after_months | POSTBOX_ARCHIVE_AFTER_MONTHS | 6 | Monate bis zur permanenten Archivierung |
profile_retry.queue | POSTBOX_RETRY_QUEUE | profile-retry | Queue-Name fuer Retry-Jobs |
Profile Sanitizer
Automatische Deaktivierung von Low-Value-Profilen.
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
sanitizer.min_age_days | POSTBOX_SANITIZER_MIN_AGE_DAYS | 7 | Mindestalter (Tage) vor Sanitization |
sanitizer.min_data_points | POSTBOX_SANITIZER_MIN_DATA_POINTS | 3 | Mindest-Metriken vor Sanitization |
sanitizer.youtube_min_subscribers | POSTBOX_SANITIZER_YT_MIN_SUBSCRIBERS | 100 | YouTube: Mindest-Subscriber |
sanitizer.youtube_min_videos | POSTBOX_SANITIZER_YT_MIN_VIDEOS | 1 | YouTube: Mindest-Videos |
sanitizer.instagram_min_followers | POSTBOX_SANITIZER_IG_MIN_FOLLOWERS | 100 | Instagram: Mindest-Follower |
sanitizer.instagram_min_posts | POSTBOX_SANITIZER_IG_MIN_POSTS | 1 | Instagram: Mindest-Posts |
sanitizer.recheck_months | POSTBOX_SANITIZER_RECHECK_MONTHS | 3 | Monate bis Re-Check bereits sanitizter Profile |
Tag Consolidation
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
tag_consolidation.chunk_size | POSTBOX_TAG_CHUNK_SIZE | 50 | Tags pro Gemini-Chunk |
tag_consolidation.max_chunks | POSTBOX_TAG_MAX_CHUNKS | 100 | Max. Chunks pro Lauf (Safety Limit) |
tag_consolidation.rate_per_minute | POSTBOX_TAG_RATE_PER_MINUTE | 10 | API-Call-Limit pro Minute |
tag_consolidation.established_threshold | POSTBOX_TAG_ESTABLISHED_THRESHOLD | 5 | Min. Profile fuer "established" Tag |
tag_consolidation.auto_execute_high_confidence | POSTBOX_TAG_AUTO_EXECUTE | true | High-Confidence Merges auto-ausfuehren |
tag_consolidation.min_tag_length | POSTBOX_TAG_MIN_LENGTH | 3 | Minimale Tag-Laenge |
tag_consolidation.consolidation_cooldown_days | POSTBOX_TAG_COOLDOWN_DAYS | 90 | Cooldown pro Tag (Tage) |
Mail Flood Guard
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
mail_flood.cache_store | MAIL_FLOOD_CACHE_STORE | Cache Default | Cache-Store fuer Flood Guard Counter |
mail_flood.threshold_per_hour | MAIL_FLOOD_THRESHOLD_HOUR | 200 | Max. E-Mails pro Stunde |
mail_flood.threshold_per_minute | MAIL_FLOOD_THRESHOLD_MINUTE | 50 | Max. E-Mails pro Minute |
mail_flood.auto_pause | MAIL_FLOOD_AUTO_PAUSE | true | Automatisch pausieren bei Schwelle |
mail_flood.admin_notification | MAIL_FLOOD_ADMIN_NOTIFICATION | true | Admin-Alert bei Pause |
Notification Types
Die Konfiguration unter notification_types definiert alle Benachrichtigungstypen mit Labels, Defaults und Kategorien. Jeder Typ hat:
label/description: Anzeige in der Settings-UIdefault_app/default_email: Default-Kanal (all,important,none)supports_workspace: Workspace-bezogene Filterungcategory: Gruppierung (profiles,data,discovery,system)force_app(optional): Kann nicht deaktiviert werden
queue.php (Laravel Queue — angepasst)
Location: config/queue.php
Standard-Laravel-Konfiguration mit einem wichtigen angepassten Wert:
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
connections.database.retry_after | DB_QUEUE_RETRY_AFTER | 3600 | Reservierungs-Timeout in Sekunden. Muss hoeher sein als der laengste Job-$timeout (aktuell 2400s fuer Leaderboard-Pipeline-Jobs). |
Warum 3600s statt Laravel-Default 90s?
Der Database-Queue-Driver markiert Jobs als "abandoned" nach retry_after Sekunden. Wenn ein Job laenger laeuft als retry_after, gibt der Driver den Job frei und ein zweiter Worker nimmt ihn auf — das fuehrt zu MaxAttemptsExceededException oder doppelter Ausfuehrung. Die Leaderboard-Pipeline-Jobs haben $timeout = 2400 (wachsende Datenmengen erfordern Spielraum), daher muss retry_after deutlich hoeher liegen. 3600s bietet einen komfortablen Puffer.
Tradeoff: Wenn ein Worker abstuerzt waehrend er einen Job verarbeitet, wird der Job erst nach 60 Minuten erneut verfuegbar (statt 90 Sekunden). Da Postbox mehrere Worker-Prozesse parallel laeuft, ist dieser Tradeoff akzeptabel.
collector.php
Location: config/collector.php
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
lease_ttl_seconds | COLLECTOR_LEASE_TTL_SECONDS | 300 | Lease-Dauer fuer Collector-Jobs (Sekunden). Nach Ablauf wird der Job frei fuer andere Clients. |
feedback.php
Location: config/feedback.php
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
recipient | FEEDBACK_RECIPIENT | MAIL_FROM_ADDRESS | E-Mail-Empfaenger fuer Feedback-Reports |
from_address | FEEDBACK_FROM_ADDRESS | hello@postbox.so | Absender-Adresse (sollte vom Empfaenger abweichen) |
from_name | FEEDBACK_FROM_NAME | Postbox | Absender-Name |
rate_limit | FEEDBACK_RATE_LIMIT | 30 | Max. Feedbacks pro User pro Stunde |
google_api_usage.php
Location: config/google_api_usage.php
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
projects | GOOGLE_API_USAGE_PROJECTS | '' | Kommaseparierte Google Cloud Projekt-IDs |
service | GOOGLE_API_USAGE_SERVICE | youtube.googleapis.com | API Service Name fuer Monitoring |
sync_interval_minutes | GOOGLE_API_USAGE_SYNC_INTERVAL | 60 | Sync-Intervall in Minuten |
credentials_path | GOOGLE_APPLICATION_CREDENTIALS | null | Pfad zu Service-Account Credentials JSON |
Fester Scope: https://www.googleapis.com/auth/cloud-platform
server-monitoring.php
Location: config/server-monitoring.php
Alert-Schwellwerte
| Metrik | Warning | Critical | Logik |
|---|---|---|---|
cpu_percent | 75 | 90 | Über dem Wert = Alarm |
ram_percent | 80 | 90 | Über dem Wert = Alarm |
disk_percent | 80 | 90 | Über dem Wert = Alarm |
pg_connections | 500 | 750 | Über dem Wert = Alarm (Gesamtzahl der Client-Verbindungen) |
pg_cache_hit | = Critical | 85 | Unter dem Wert = Alarm (invertiert). Beide Werte per .env, siehe unten |
swap_percent wird erhoben, alarmiert aber nicht: Der Füllstand kann den Normalfall nicht vom Ernstfall trennen (Begründung in config/server-monitoring.php).
Für pg_cache_hit gibt es per Default keine eigene Warnstufe: Ohne SERVER_ALERT_PG_CACHE_HIT_WARNING fällt sie auf die kritische Schwelle. Eine Warnung ist nur zwischen Normalbetrieb und „kritisch“ sinnvoll, und geprüft wird ein Fensterwert über rund 15 Sekunden, der im laufenden Betrieb um 90 % liegen kann. Warnungen werden genauso gemailt wie kritische Alarme. Dieselben beiden Werte gelten für die Empfehlungen im DB-Monitoring (db_monitoring.thresholds.cache_hit_warning / cache_hit_critical).
Weitere Einstellungen
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
alerts.pg_cache_hit.critical_below | SERVER_ALERT_PG_CACHE_HIT_CRITICAL | 85 | Kritische Schwelle der Cache-Trefferquote (%). Leer oder keine Zahl = Default, 0 schaltet die Stufe ab |
alerts.pg_cache_hit.warning_below | SERVER_ALERT_PG_CACHE_HIT_WARNING | = Critical | Warnschwelle (%). Ohne eigenen Wert keine eigene Warnstufe |
sustained_alert_cooldown_minutes | SERVER_ALERT_SUSTAINED_COOLDOWN | 240 | Cooldown je Alarmstufe (Minuten). Eine Stufe meldet sich höchstens einmal pro Fenster, auch wenn sie zwischendurch verlassen und wieder betreten wird. Eine Eskalation auf eine Stufe, die im Fenster noch nicht gemeldet hat, kommt sofort. |
active_users_minutes | SERVER_ACTIVE_USERS_MINUTES | 5 | Schwelle für „aktive“ Session (Minuten) |
pg_max_connections | SERVER_PG_MAX_CONNECTIONS | 1000 | PostgreSQL max_connections für die Prozent-Berechnung. Fallback: Gelesen wird zuerst SHOW max_connections |
vantage.php
Location: config/vantage.php
| Key | Env-Variable | Default | Beschreibung |
|---|---|---|---|
enabled | VANTAGE_ENABLED | true | Master-Switch fuer das gesamte Package |
store_payload | VANTAGE_STORE_PAYLOAD | true | Job-Payloads fuer Debugging speichern |
redact_keys | -- | [password, token, secret, ...] | Sensitive Keys im Payload werden redacted |
retention_days | VANTAGE_RETENTION_DAYS | 14 | Aufbewahrungsdauer fuer Job-History (Tage) |
routes | VANTAGE_ROUTES | true | Dashboard-Routen registrieren |
route_prefix | VANTAGE_ROUTE_PREFIX | vantage | URL-Prefix fuer das Dashboard |
auth.enabled | VANTAGE_AUTH_ENABLED | true | Gate-basierte Authentifizierung aktivieren |
telemetry.enabled | VANTAGE_TELEMETRY_ENABLED | true | Performance-Telemetrie sammeln |
telemetry.sample_rate | VANTAGE_TELEMETRY_SAMPLE_RATE | 1.0 | Sample Rate (0.0 - 1.0) |
telemetry.capture_cpu | VANTAGE_TELEMETRY_CPU | true | CPU-Metriken erfassen |
database_connection | VANTAGE_DATABASE_CONNECTION | null | Separate DB-Connection (null = Default) |
logging.enabled | VANTAGE_LOGGING_ENABLED | true | Package-Logging aktivieren |
notify.email | VANTAGE_NOTIFY_EMAIL | null | E-Mail fuer Failure-Notifications (deaktiviert, Flare/Nightwatch wird genutzt) |
notify.slack_webhook | VANTAGE_SLACK_WEBHOOK | null | Slack Webhook fuer Notifications |
Vollstaendige .env-Referenz (Custom Variables)
Authentifizierung
WORKOS_CLIENT_ID=client_...
WORKOS_API_KEY=sk_...
WORKOS_REDIRECT_URL=https://app.postbox.so/authenticate
POSTBOX_ADMIN_EMAILS="admin@example.com, second@example.com"
Legal Pages
LEGAL_PRIVACY_PAGE_ID=0
LEGAL_TERMS_PAGE_ID=0
YouTube API
YOUTUBE_API_KEY=AIza...
YOUTUBE_API_KEYS=key1,key2,key3
YOUTUBE_API_VIDEO_KEYS=key4,key5
YOUTUBE_API_EXTENDED_KEYS=key6,key7
YOUTUBE_API_RESEARCH_KEYS=key8
POSTBOX_YOUTUBE_CUSTOM_URL_SEARCH_FALLBACK=false
POSTBOX_YOUTUBE_ROTATION_DAYS=3
POSTBOX_LEADERBOARD_CANDIDATE_LIMIT=200
Instagram
POSTBOX_INSTAGRAM_ROTATION_DAYS=3
POSTBOX_INSTAGRAM_PRIORITY_DEFAULT=0
POSTBOX_INSTAGRAM_PRIORITY_CATCH_UP=10
POSTBOX_INSTAGRAM_PRIORITY_FAVORITE=20
POSTBOX_INSTAGRAM_PRIORITY_NEW_PROFILE=30
POSTBOX_INSTAGRAM_PRIORITY_CANDIDATE=40
POSTBOX_INSTAGRAM_PRIORITY_TOP_LEADER=50
Health Monitoring
HEALTH_TOKEN=your-secret-health-token
HEALTH_FAILED_JOBS_THRESHOLD=100
HEALTH_COLLECTOR_HEARTBEAT_MINUTES=30
HEALTH_QUEUE_QUIET_START=4
HEALTH_QUEUE_QUIET_END=6
Queue
DB_QUEUE_RETRY_AFTER=3600
Reverb (WebSockets)
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=postbox
REVERB_APP_KEY=your-random-key
REVERB_APP_SECRET=your-random-secret
REVERB_SERVER_HOST=127.0.0.1
REVERB_SERVER_PORT=8081
REVERB_HOST=app.postbox.so
REVERB_PORT=443
REVERB_SCHEME=https
Google API
GOOGLE_API_USAGE_PROJECTS=project-1,project-2
GOOGLE_API_USAGE_SERVICE=youtube.googleapis.com
GOOGLE_API_USAGE_SYNC_INTERVAL=60
GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
AI Enhancement
AI_ENHANCER_HOURLY_LIMIT=100
AI_ENHANCER_RATE_PER_MINUTE=15
AI_ENHANCER_COOLDOWN_DAYS=30
Profile Retry
POSTBOX_FAIL_STREAK_THRESHOLD=14
POSTBOX_INITIAL_RETRY_INTERVAL_DAYS=14
POSTBOX_INITIAL_RETRY_COUNT=3
POSTBOX_MONTHLY_RETRY_INTERVAL_DAYS=30
POSTBOX_ARCHIVE_AFTER_MONTHS=6
POSTBOX_RETRY_QUEUE=profile-retry
Profile Sanitizer
POSTBOX_SANITIZER_MIN_AGE_DAYS=7
POSTBOX_SANITIZER_MIN_DATA_POINTS=3
POSTBOX_SANITIZER_YT_MIN_SUBSCRIBERS=100
POSTBOX_SANITIZER_YT_MIN_VIDEOS=1
POSTBOX_SANITIZER_IG_MIN_FOLLOWERS=100
POSTBOX_SANITIZER_IG_MIN_POSTS=1
POSTBOX_SANITIZER_RECHECK_MONTHS=3
Tag Consolidation
POSTBOX_TAG_CHUNK_SIZE=50
POSTBOX_TAG_MAX_CHUNKS=100
POSTBOX_TAG_RATE_PER_MINUTE=10
POSTBOX_TAG_ESTABLISHED_THRESHOLD=5
POSTBOX_TAG_AUTO_EXECUTE=true
POSTBOX_TAG_MIN_LENGTH=3
POSTBOX_TAG_COOLDOWN_DAYS=90
Mail Flood Guard
MAIL_FLOOD_CACHE_STORE=redis
MAIL_FLOOD_THRESHOLD_HOUR=200
MAIL_FLOOD_THRESHOLD_MINUTE=50
MAIL_FLOOD_AUTO_PAUSE=true
MAIL_FLOOD_ADMIN_NOTIFICATION=true
Server Monitoring
SERVER_ALERT_SUSTAINED_COOLDOWN=240
SERVER_ACTIVE_USERS_MINUTES=5
SERVER_PG_MAX_CONNECTIONS=200
Collector
COLLECTOR_LEASE_TTL_SECONDS=300
Feedback
FEEDBACK_RECIPIENT=feedback@example.com
FEEDBACK_FROM_ADDRESS=hello@postbox.so
FEEDBACK_FROM_NAME=Postbox
FEEDBACK_RATE_LIMIT=30
Monitoring (Flare, Matomo, Nightwatch)
FLARE_KEY=your-flare-key
MATOMO_URL=https://analytics.example.com
MATOMO_SITE_ID=1
MATOMO_TOKEN=your-token
MATOMO_ENABLED=true
NIGHTWATCH_TOKEN=your-token
Vantage
VANTAGE_ENABLED=true
VANTAGE_STORE_PAYLOAD=true
VANTAGE_RETENTION_DAYS=14
VANTAGE_AUTH_ENABLED=true
VANTAGE_TELEMETRY_ENABLED=true
VANTAGE_TELEMETRY_SAMPLE_RATE=1.0