Skip to content

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 ​

FromToProtocolAuth
Slack botSymfony backendHTTP (Axios)Bearer token (BACKEND_API_TOKEN)
Symfony backendExpress APIHTTP (POST)Bearer token (BACKEND_API_TOKEN)
Slack APIBolt appWebSocket (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 refreshed

Digest 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 user

Digest Distribution (Backend → Slack) ​

Symfony scheduled task triggers
    → POST /api/slack/send-digest (Express API)
    → handlers/api/sendDigest.ts
    → Slack client sends DM to subscribers

Handler 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
ServiceKey Functions
categories.tslistCategories(), addCategory()
knowledge.tsaddKnowledge(), listKnowledge()
digest.tsgenerateDigest(), distributeDigest(), getDigestsByCategory()
subscriptions.tsgetUserSubscriptions(), subscribeUserToCategory(), unsubscribeUserFromCategory()
categorySettings.tsgetDigestConfig(), 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 everything

Configuration ​

Environment variables (in slack/.env):

VariableRequiredDefaultDescription
SLACK_BOT_TOKENYes—Bot OAuth token (xoxb-)
SLACK_SIGNING_SECRETYes—Signing secret from Basic Information
SLACK_APP_TOKENYes—App-level token (xapp-) for Socket Mode
PORTNo4000Bolt Socket Mode port
API_PORTNo4001Express API port
API_BASE_URLYeshttp://localhost:8001/apiBackend API base URL
BACKEND_API_TOKENYes—Shared secret for backend auth
NODE_ENVNodevelopmentNode.js environment mode
JIRA_BASE_URLNo—Base URL of the Jira instance (e.g., https://yappajira.atlassian.net)
JIRA_PROJECT_KEYNo—Key of the Jira project (e.g., MSP2)
JIRA_EMAILNo—Jira user email
JIRA_API_TOKENNo—Jira REST API token
OPENAI_API_KEYNo—OpenAI API key for LLM services
ANTHROPIC_API_KEYNo—Anthropic API key for Claude integration

Startup Sequence ​

  1. Load environment variables from .env
  2. Initialize Bolt app with Socket Mode
  3. Register all handlers (events, views, actions, shortcuts)
  4. Set up Express API routes
  5. Pre-cache categories from backend (verifies connectivity)
  6. Start Bolt app on PORT (4000)
  7. Start Express API on API_PORT (4001)
  8. Log: ⚡ Slack bot is running (Socket Mode)