Slack Bot Architecture
System-level architecture for the Yappa Knowledge Hub Slack bot.
Last Updated: 2026-05-15
System Diagram
Slack Users
│
▼
┌─────────────────────────────────────────┐
│ Node.js Bot (Socket Mode) │
│ Port 4000 — Bolt SDK │
│ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ Bolt App │ │ Express API │ │
│ │ (events, │ │ Port 4001 │ │
│ │ actions, │ │ POST /api/slack/ │ │
│ │ shortcuts, │ │ send-digest │ │
│ │ submissions) │ │ │ │
│ └──────┬───────┘ └────────┬─────────┘ │
│ │ │ │
└─────────┼───────────────────┼────────────┘
│ │
▼ │
┌──────────────────┐ │
│ Symfony Backend │◄────────┘
│ Port 8001 │
│ │
│ ┌─────────────┐ │
│ │ REST API │ │
│ │ /api/* │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ SQLite DB │ │
│ └─────────────┘ │
│ ┌─────────────┐ │
│ │ Notion API │ │
│ └─────────────┘ │
└──────────────────┘Communication
| From | To | Protocol | Auth |
|---|---|---|---|
| Slack bot | Symfony backend | HTTP (Axios) | Bearer token (BACKEND_API_TOKEN) |
| Symfony backend | Express API | HTTP (POST) | Bearer token (BACKEND_API_TOKEN) |
| Slack API | Bolt app | WebSocket (Socket Mode) | SLACK_APP_TOKEN (xapp-) |
Process Flow
Knowledge Creation
User right-clicks message
→ "Add to YapHub"
→ shortcuts.ts (handleSaveMessageShortcut)
→ Modal opens (saveMessage.ts builder)
→ User fills form + submits
→ submissions.ts (SAVE_MESSAGE callback)
→ services/knowledge.ts → POST /api/knowledge
→ Symfony creates item in SQLite + Notion
→ Confirmation DM to user
→ Home tab refreshedDigest Generation
User clicks "Digest" on category
→ actions.ts → opens modal (generateDigest.ts)
→ User selects options + submits
→ submissions.ts (GENERATE_DIGEST callback)
→ submissions/digestHandler.ts (async)
→ services/digest.ts → POST /api/digests/generate
→ Symfony generates AI summaries
→ Digest DM sent to userDigest Distribution (Backend → Slack)
Symfony scheduled task triggers
→ POST /api/slack/send-digest (Express API)
→ handlers/api/sendDigest.ts
→ Slack client sends DM to subscribersHandler Architecture
src/app.ts
├── app.event('app_home_opened') → handlers/events.ts → handlers/home/dynamic.ts
├── app.view(/.*/) → handlers/submissions.ts
│ ├── ADD_LIST (inline)
│ ├── ADD_KNOWLEDGE (inline)
│ ├── SAVE_MESSAGE (inline)
│ ├── GENERATE_DIGEST → submissions/digestHandler.ts
│ ├── DISTRIBUTE_DIGEST → actions/distribution.ts
│ └── CATEGORY_SETTINGS → inline (calls services/categorySettings.ts)
├── app.action(/.*/) → handlers/actions.ts
│ ├── Direct actions (switch)
│ └── Prefix-based (category_overflow_*)
└── app.shortcut(...) → handlers/shortcuts.ts
├── add_to_hub (message shortcut)
└── quick_add_knowledge (global shortcut)Services Layer
All services use a shared Axios client (utils/api.ts) with:
- Bearer token authentication
- 60-second timeout
- 3 retry attempts with 1-second delay
| Service | Key Functions |
|---|---|
categories.ts | listCategories(), addCategory() |
knowledge.ts | addKnowledge(), listKnowledge() |
digest.ts | generateDigest(), distributeDigest(), getDigestsByCategory() |
subscriptions.ts | getUserSubscriptions(), subscribeUserToCategory(), unsubscribeUserFromCategory() |
categorySettings.ts | getDigestConfig(), updateDigestConfig() |
Constants Architecture
All IDs and strings are centralized to prevent magic strings:
constants/
├── action_ids.ts # Button/action IDs (HOME_ADD_KNOWLEDGE, etc.)
│ # + ACTION_ID_PREFIXES (CATEGORY_OVERFLOW, etc.)
│ # + OVERFLOW_ACTIONS (edit, delete, share, etc.)
├── block_ids.ts # Block IDs + INPUT_ACTION_IDS for form fields
├── view_ids.ts # Modal callback IDs (ADD_KNOWLEDGE, etc.)
├── homeTab.ts # Home tab text (Dutch), modes, pagination config
├── api.ts # API paths, HTTP methods, status codes
├── messages.ts # User-facing message strings
├── slack_types.ts # Slack type constants (block types, button styles)
├── modalOptions.ts # Dropdown/option defaults
└── index.ts # Re-exports everythingConfiguration
Environment variables (in slack/.env):
| Variable | Required | Default | Description |
|---|---|---|---|
SLACK_BOT_TOKEN | Yes | — | Bot OAuth token (xoxb-) |
SLACK_SIGNING_SECRET | Yes | — | Signing secret from Basic Information |
SLACK_APP_TOKEN | Yes | — | App-level token (xapp-) for Socket Mode |
PORT | No | 4000 | Bolt Socket Mode port |
API_PORT | No | 4001 | Express API port |
API_BASE_URL | Yes | http://localhost:8001/api | Backend API base URL |
BACKEND_API_TOKEN | Yes | — | Shared secret for backend auth |
NODE_ENV | No | development | Node.js environment mode |
JIRA_BASE_URL | No | — | Base URL of the Jira instance (e.g., https://yappajira.atlassian.net) |
JIRA_PROJECT_KEY | No | — | Key of the Jira project (e.g., MSP2) |
JIRA_EMAIL | No | — | Jira user email |
JIRA_API_TOKEN | No | — | Jira REST API token |
OPENAI_API_KEY | No | — | OpenAI API key for LLM services |
ANTHROPIC_API_KEY | No | — | Anthropic API key for Claude integration |
Startup Sequence
- Load environment variables from
.env - Initialize Bolt app with Socket Mode
- Register all handlers (events, views, actions, shortcuts)
- Set up Express API routes
- Pre-cache categories from backend (verifies connectivity)
- Start Bolt app on
PORT(4000) - Start Express API on
API_PORT(4001) - Log:
⚡ Slack bot is running (Socket Mode)