Skip to content

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.

VarRequiredDescription
DATABASE_URLPostgreSQL connection string. Run db:migrate after setting.
JWT_SECRETValidates client session tokens (HS256). Must match what your app signs with.
INTERCOM_ACCESS_TOKENIntercom access token (server-side only).
INTERCOM_CLIENT_SECRETVerifies webhook HMAC-SHA1 signatures (the app's Client Secret).
INTERCOM_TICKET_TYPE_IDTicket type id used when creating tickets.
PORTListen port (default 3100).
RATE_LIMIT_PER_MINPer-user /api limit (default 600).
CORS_ORIGINSComma-separated browser origins. Empty = reflect any. Set in prod.
INTERCOM_CLIENT_IDOAuth client id (unused today).
ADMIN_USER / ADMIN_PASSHTTP Basic creds for /admin. Either unset → admin panel disabled.
INTERCOM_AUTOREPLY_ADMIN_IDTeammate id for the first-message auto-reply. Empty = off.
INTERCOM_AUTOREPLY_TEXT_RU / _ENAuto-reply text per locale (built-in defaults).
FCM_SERVICE_ACCOUNTPath to the Firebase service-account JSON (mobile push).
TELEGRAM_BOT_TOKENTelegram bot token (TMA push).
TELEGRAM_BOT_USERNAMEBot username, for deep links t.me/<username>/app.
S3_ENDPOINT / S3_BUCKET / S3_ACCESS_KEY / S3_SECRET_KEYS3-compatible file storage.
S3_REGIONS3 region (default us-east-1; set for Garage/MinIO).
UPLOAD_DIRLocal upload dir when S3 isn't configured (use a persistent volume).
MAX_FILE_MBMax single upload size (default 20).
MAX_FILES_PER_MSGMax attachments per message (default 10).
ENABLE_DEVTOOLS_RESET_ALL1 enables a destructive bulk contact-wipe. Leave empty in prod.
TEST_DATABASE_URLPostgres 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 migrations

Tables: 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:migrate on every deploy — recent migrations add conversation_reads, reasons.parent_id (nested topics) and conversation_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 in user_contacts.
  • Each support request is its own Intercom conversation; the picked topic becomes an internal note + tags (platform, reason, country) on creation.

AW Chat SDK — internal integration docs.