Appearance
Backend configuration
The backend is a Hono + Drizzle service (@aw-chat/server). It runs the TypeScript source via tsx (Hono/Drizzle are ESM-first).
Environment variables
Copy apps/server/.env.example to apps/server/.env and fill in:
Required = the server won't start without it; everything else is optional with a safe default.
| Var | Required | Description |
|---|---|---|
DATABASE_URL | ✅ | PostgreSQL connection string. Run db:migrate after setting. |
JWT_SECRET | ✅ | Validates client session tokens (HS256). Must match what your app signs with. |
INTERCOM_ACCESS_TOKEN | ✅ | Intercom access token (server-side only). |
INTERCOM_CLIENT_SECRET | ✅ | Verifies webhook HMAC-SHA1 signatures (the app's Client Secret). |
INTERCOM_TICKET_TYPE_ID | ✅ | Ticket type id used when creating tickets. |
PORT | — | Listen port (default 3100). |
RATE_LIMIT_PER_MIN | — | Per-user /api limit (default 600). |
CORS_ORIGINS | — | Comma-separated browser origins. Empty = reflect any. Set in prod. |
INTERCOM_CLIENT_ID | — | OAuth client id (unused today). |
ADMIN_USER / ADMIN_PASS | — | HTTP Basic creds for /admin. Either unset → admin panel disabled. |
INTERCOM_AUTOREPLY_ADMIN_ID | — | Teammate id for the first-message auto-reply. Empty = off. |
INTERCOM_AUTOREPLY_TEXT_RU / _EN | — | Auto-reply text per locale (built-in defaults). |
FCM_SERVICE_ACCOUNT | — | Path to the Firebase service-account JSON (mobile push). |
TELEGRAM_BOT_TOKEN | — | Telegram bot token (TMA push). |
TELEGRAM_BOT_USERNAME | — | Bot username, for deep links t.me/<username>/app. |
S3_ENDPOINT / S3_BUCKET / S3_ACCESS_KEY / S3_SECRET_KEY | — | S3-compatible file storage. |
S3_REGION | — | S3 region (default us-east-1; set for Garage/MinIO). |
UPLOAD_DIR | — | Local upload dir when S3 isn't configured (use a persistent volume). |
MAX_FILE_MB | — | Max single upload size (default 20). |
MAX_FILES_PER_MSG | — | Max attachments per message (default 10). |
ENABLE_DEVTOOLS_RESET_ALL | — | 1 enables a destructive bulk contact-wipe. Leave empty in prod. |
TEST_DATABASE_URL | — | Postgres for integration tests (default postgresql://localhost/awchat_test). |
Push (FCM/Telegram), storage (S3), the admin panel and the auto-reply are all optional: if unset, those features no-op gracefully so you can run core chat/tickets without them.
The contact's country and IP come from edge headers, not env: country from CF-IPCountry (or X-Country), IP from X-Forwarded-For / X-Real-IP. Make sure your reverse proxy / Cloudflare forwards them.
Database
bash
pnpm --filter @aw-chat/server db:generate # generate SQL migrations from the schema
pnpm --filter @aw-chat/server db:migrate # apply migrationsTables: user_contacts, devices, csat_ratings, complaints, processed_events, notification_settings, conversation_reads (unread badges), reasons (admin-managed nested request topics / complaint lists), conversation_durations (close-to-close timing for the admin panel).
Run
db:migrateon every deploy — recent migrations addconversation_reads,reasons.parent_id(nested topics) andconversation_durations.
Run
bash
pnpm --filter @aw-chat/server dev # watch mode
pnpm --filter @aw-chat/server start # production (tsx)Intercom setup notes
- The backend auto-creates the custom contact attributes it writes on first contact creation (
app_version,hardware_id,os_version,device_lang,browser,browser_version,ip) — Intercom rejects undefined custom attributes, so this is done idempotently for you. Name, email and country are set as standard contact fields. - It maps each
userId↔ Intercom contact id and caches it inuser_contacts. - Each support request is its own Intercom conversation; the picked topic becomes an internal note + tags (platform, reason, country) on creation.