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_TOKENYesBot OAuth token (xoxb-)
SLACK_SIGNING_SECRETYesSigning secret from Basic Information
SLACK_APP_TOKENYesApp-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_TOKENYesShared secret for backend auth
NODE_ENVNodevelopmentNode.js environment mode
JIRA_BASE_URLNoBase URL of the Jira instance (e.g., https://yappajira.atlassian.net)
JIRA_PROJECT_KEYNoKey of the Jira project (e.g., MSP2)
JIRA_EMAILNoJira user email
JIRA_API_TOKENNoJira REST API token
OPENAI_API_KEYNoOpenAI API key for LLM services
ANTHROPIC_API_KEYNoAnthropic 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)