Module 6: Real-World Integrations — Slack/Discord
Project: Slack/Discord Bot Design Document
Overview
We close the module with the capstone project: a complete Slack/Discord Bot Design Document. It's the document a Tech Lead presents to their team before starting to implement, and the integration layer section of the Capstone Architecture Design (M8).
It's not code — it's concrete design. But the design must be so specific that an engineer can implement it by following it. Diagrams, schemas, flows, justified decisions.
By the end of the project you'll have:
- A complete Bot Design Document (~8-15 pages)
- An architecture diagram showing the end-to-end flow
- A multi-tenant installations schema
- A documented OAuth flow
- Explicit constraint compliance (rate limits, timeouts)
- An Implementation checklist that becomes a roadmap
The case to design
System: an AI-Powered Knowledge Assistant for B2B SaaS companies.
Context:
- 5 pilot clients initially, expecting 50 in 12 months
- Client size: 50-500 employee companies
- Each client uploads its knowledge base (internal docs, wikis)
- Users (the client's employees) ask the bot in their Slack
- The system uses RAG + LLM to respond with citations to internal sources
Stack already decided (from M3, M4, M5):
- API: FastAPI on 3 instances
- Queue: Redis Streams
- LLM: OpenAI gpt-4o-mini, fallback Claude 3 Haiku
- Vector DB: Pinecone (managed)
- Auth tokens: PostgreSQL encrypted with Fernet
Channel decision (from M6-07): Slack only for the MVP. Discord addable later.
Document template
Your deliverable is a Markdown with this structure.
1. Executive summary (½ page)
# Slack Bot Design — AI Knowledge Assistant
## Summary
This document describes the design of the Slack integration for the AI Knowledge
Assistant. It covers multi-tenant OAuth, event handling, response formatting
with citations, and compliance with Slack's constraints (3s timeout, rate limit
1 msg/sec per channel).
Designed to initially support 5 pilot clients, scalable to 50 in
12 months without architectural changes.
Key decisions:
- Slack only (Discord on the Q3 roadmap)
- Multi-tenant with OAuth v2 + token rotation
- Async processing via a Redis queue
- Block Kit for responses with citations
2. Architecture overview (1 page)
A diagram of the components:
┌──────────────┐
│ Slack User │ writes "@bot how do I configure X?"
└──────┬───────┘
│
▼
┌──────────────┐
│ Slack API │
└──────┬───────┘
│ POST event
▼
┌──────────────────────┐
│ API Gateway (AWS │ ← signature verification
│ ALB → 3 instances │ ← rate limit check
│ FastAPI) │
└──────┬───────────────┘
│ ACK 200 OK
│ Enqueue job
▼
┌──────────────────┐
│ Redis Queue │
└──────┬───────────┘
│
▼
┌──────────────────┐
│ 4 Workers │
│ (FastAPI proc.) │
└──────┬───────────┘
│
├─→ Pinecone (RAG retrieval)
├─→ OpenAI (LLM)
├─→ PostgreSQL (installations lookup, results store)
│
▼
┌──────────────────┐
│ Slack Web API │ POST chat.postMessage
└──────┬───────────┘
│
▼
Slack User sees response
For each component, one line of purpose.
3. OAuth Flow (1 page)
3.1 Install URL
https://slack.com/oauth/v2/authorize
?client_id=YOUR_CLIENT_ID
&scope=app_mentions:read,chat:write,commands,users:read
&user_scope=
Requested scopes:
| Scope | Why |
|---|---|
app_mentions:read | Receive when @bot |
chat:write | Send messages |
commands | Support /ask |
users:read | Look up user info for personalization |
3.2 Callback handler
Pseudocode for the /slack/oauth/callback endpoint:
1. Receive the `code` query param
2. POST to slack.com/api/oauth.v2.access with client_id, secret, code
3. Get access_token, team_id, team_name, bot_user_id, scope
4. Encrypt access_token with Fernet
5. UPSERT into the `installations` table
6. Redirect to a success page
3.3 installations schema
CREATE TABLE installations (
team_id VARCHAR PRIMARY KEY,
team_name VARCHAR NOT NULL,
encrypted_access_token BYTEA NOT NULL,
bot_user_id VARCHAR NOT NULL,
scopes TEXT NOT NULL,
installer_user_id VARCHAR,
installed_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
revoked_at TIMESTAMP,
knowledge_base_id VARCHAR, -- FK to the knowledge_bases table
metadata JSONB
);
CREATE INDEX idx_installations_active ON installations(team_id)
WHERE revoked_at IS NULL;
4. Event Handling (1 page)
4.1 Endpoints
| Endpoint | For what |
|---|---|
POST /slack/events | Receives Events API (app_mention, url_verification, tokens_revoked) |
POST /slack/interactivity | Receives button clicks, modal submits |
POST /slack/command/ask | Receives the /ask slash command |
GET /slack/oauth/callback | OAuth callback |
POST /slack/events | (same endpoint) processes tokens_revoked |
4.2 Detailed flow: @mention
1. User writes "@bot how X?" in #support
2. Slack POSTs to /slack/events with type=event_callback, event.type=app_mention
3. Endpoint:
a. Verify HMAC signature (X-Slack-Signature, X-Slack-Request-Timestamp)
b. If url_verification challenge → respond with the challenge
c. Idempotency: check event_id in Redis (`event:slack:<event_id>` SET NX TTL 5min)
d. If duplicate → return 200 OK silent
e. Look up installation by team_id
f. If it doesn't exist → return 200 OK silent + log
g. Enqueue a job with {team_id, channel, ts, user_id, text} in a Redis stream
h. Return 200 OK (meets the 3s budget)
4. Worker:
a. Pop the job from the stream
b. Strip @bot from the text → extract the question
c. RAG retrieval against the team_id's knowledge base (Pinecone)
d. LLM call with context + question
e. Format the response with Block Kit (includes sources, feedback buttons)
f. Get the decrypted access_token for team_id
g. POST chat.postMessage with thread_ts=event.ts (respond in a thread)
h. If rate limited (429) → respect Retry-After + retry max 3
i. Store the interaction in the DB for analytics
4.3 Idempotency
Key: slack:event:<event_id> with a 5min TTL in Redis.
async def is_duplicate_event(event_id: str) -> bool:
return not await redis.set(f"slack:event:{event_id}", "1", nx=True, ex=300)
5. Constraints Compliance (1 page)
5.1 3s Timeout
Pattern: immediate ACK + queue.
| Path | Strategy |
|---|---|
/slack/events | Return 200 OK <500ms, enqueue for the worker |
/slack/interactivity | Return JSON acknowledgement <500ms, enqueue follow-up |
/slack/command/ask | Return JSON "Processing..." <500ms, enqueue, response_url for follow-up |
5.2 Rate limit 1 msg/sec per channel
Pattern: a centralized rate limiter in Redis, per (team_id, channel).
async def acquire_send_slot(team_id: str, channel: str):
key = f"slack:rl:{team_id}:{channel}"
while True:
acquired = await redis.set(key, "1", nx=True, ex=1)
if acquired:
return
await asyncio.sleep(0.1)
If we have 4 workers, Redis serves as serialization: only one gets the lock per second per channel.
5.3 Retry policies
| Error | Retry? | Strategy |
|---|---|---|
ratelimited (429) | Yes | Respect Retry-After, max 3 retries |
5xx HTTP | Yes | Exponential backoff with jitter, max 3 |
invalid_auth | No | Mark the installation as revoked |
channel_not_found | No | Log + skip (channel deleted) |
not_in_channel | No | Log + skip (bot removed from the channel) |
6. Message Format (1 page)
6.1 Response template
Each response follows this Block Kit template:
[
{"type": "header", "text": {"type": "plain_text", "text": "💡 Answer"}},
{"type": "section", "text": {"type": "mrkdwn", "text": f">{question}"}},
{"type": "divider"},
{"type": "section", "text": {"type": "mrkdwn", "text": answer}},
# Confidence indicator if <80%
{"type": "context", "elements": [
{"type": "mrkdwn", "text": f"⚠️ Confidence: {confidence}%"}
]} if confidence < 80 else None,
# Sources
*[{
"type": "context",
"elements": [{"type": "mrkdwn", "text": f"📚 {i}. <{s.url}|{s.title}>"}]
} for i, s in enumerate(sources[:3], 1)],
{"type": "divider"},
# Feedback buttons
{"type": "actions", "elements": [
{"type": "button", "text": {"type": "plain_text", "text": "👍 Helpful"},
"action_id": "feedback_helpful", "value": interaction_id},
{"type": "button", "text": {"type": "plain_text", "text": "👎 Not helpful"},
"action_id": "feedback_not_helpful", "value": interaction_id,
"style": "danger"},
]},
{"type": "context", "elements": [
{"type": "mrkdwn", "text": f"_AI Assistant • {len(sources)} sources_"}
]}
]
6.2 Fallback text
Critical for mobile notifications:
fallback_text = f"Answer to '{question[:50]}': {answer[:150]}..."
6.3 Special cases
| Case | Behavior |
|---|---|
| LLM timeout | Degraded message: "We're experiencing slowness, try again" |
| Zero sources found | Explicit notice: "I didn't find info about this in your knowledge base" |
| Confidence <50% | Don't respond with the answer; suggest rephrasing |
| Content filter triggered | Neutral message, without details about the filter |
7. Multi-tenant Considerations (½ page)
| Aspect | Implementation |
|---|---|
| Token storage | Fernet-encrypted in installations.encrypted_access_token |
| Token retrieval | The worker decrypts only at the moment of sending the message, not before |
| Rate limit | Per (team_id, channel), never global |
| Isolated knowledge base | knowledge_base_id linked to installations.team_id. RAG retrieval always filters by knowledge_base_id |
| Revocation handling | tokens_revoked event → UPDATE revoked_at, cancel pending jobs |
8. Monitoring (½ page)
Mandatory metrics (exported to Prometheus):
slack_events_received_total{event_type, team_id_anonymized}
slack_events_processed_total{status="ok|skipped|failed"}
slack_messages_sent_total{team_id_anonymized, status}
slack_rate_limits_hit_total{endpoint}
slack_oauth_installations_total
slack_oauth_revocations_total
slack_processing_duration_seconds (histogram)
slack_response_format_errors_total{type}
Alerts:
- Rate limit hits >10/hour → investigate
- Processing latency P95 >15s → investigate workers / LLM provider
- OAuth failures >5/hour → check config or a Slack outage
- Any installation with >100 errors in 24h → possible specific problem
9. Implementation Checklist (1 page)
A sequential roadmap:
Sprint 1: foundation (2 weeks)
- Create the Slack app, configure OAuth scopes
- DB migrations: the
installationstable -
/slack/oauth/callbackendpoint: token exchange + encryption + storage - Multi-tenant test: install the bot in 2 test workspaces
Sprint 2: events (2 weeks)
-
/slack/eventsendpoint: signature verification + URL challenge - Idempotency with Redis
- Tokens revoked event handler
- Test signing with the real Slack signing secret
Sprint 3: processing (2 weeks)
- Worker that consumes the Redis queue
- Integration with the RAG service (M8)
- Integration with the LLM service (M5 reliability)
- End-to-end test: @bot question → visible response
Sprint 4: messaging (2 weeks)
- Block Kit response template
- Web API client with a per-channel rate limiter
- Retry policy with error types
- Test 100 simulated msgs without losing any
Sprint 5: interactivity (1 week)
-
/slack/interactivityendpoint - Feedback buttons (helpful/not_helpful)
- Modal for detailed feedback
Sprint 6: production (2 weeks)
- Monitoring + alerts in Prometheus
- Runbook for 5 common scenarios
- Load test (100 simultaneous across 50 workspaces)
- Slack Marketplace submission (if applicable)
Total: ~11 weeks with 1.5 engineers FTE.
10. Decision Records (1 page)
Document key decisions:
## ADR-001: Slack only for the MVP
**Status**: Accepted (2026-05-11)
**Context**:
- 90% of pilot clients have Slack as their corporate channel
- Discord isn't a priority in the B2B segment
- A team of 3 engineers, 6 months MVP
**Decision**: Launch with Slack only. Discord on the Q3 roadmap.
**Consequences**:
+ Faster time to market
+ Focus on quality vs breadth
+ Target audience covered
- Some clients on Discord won't be able to use it
- If we pivot to B2C, we'll need Discord
**Revisit**: Q3 2026 with demand data
(Similar for other decisions: token rotation, queue tech, rate limiter, etc.)
11. Appendices
- A: Slack app manifest YAML
- B: Block Kit examples (visual screenshots)
- C: Test plan (with specific cases)
- D: Detailed runbook
How to work through this project
I suggest this order:
- Skim the template (5 min)
- Diagram in tldraw or similar (20 min): the end-to-end flow
- Schema and OAuth (30 min): the
installationstable, callback flow - Detailed event handling (45 min): each endpoint, idempotency, flows
- Constraint compliance (30 min): 3s timeout, per-channel rate limit
- Message format (30 min): Block Kit template, fallbacks
- Implementation checklist (30 min): sprints, definitions of done
- ADRs (20 min): document 3-5 critical decisions
- Final coherence (15 min): check that everything closes
Total: ~3.75 hours.
Evaluation criteria
Self-assess your document:
- The architecture diagram is clear and shows all the components
- The DB schema has all the fields needed for multi-tenant
- The OAuth flow is complete (install → callback → storage → retrieval)
- The constraints (3s timeout, rate limits) have concrete patterns
- The Block Kit template has fallback text
- Idempotency is handled in events
- Token revocation is covered
- The implementation checklist is sequential with estimated sprints
- There are at least 3 ADRs documenting key decisions
- A new engineer could implement by following this doc
Evidence of success by the end of M6
You'll know you finished well if you can:
- ✅ Defend why Slack only (vs Discord) with numbers
- ✅ Explain the flow from "@bot" to "visible response" passing through all the components
- ✅ Show the DB schema and justify each field
- ✅ Describe what happens when 50 workspaces send 10 simultaneous messages each
- ✅ Identify where each constraint is met (3s budget, 1 msg/sec)
Connection with M8 (Capstone)
This document is the integration layer section of the Capstone Architecture Design (M8). When you reach M8, you take this doc and integrate it with:
- Reliability design (M5)
- Scaling strategy (M3)
- Trade-off decisions (M7)
And you get the complete Capstone Architecture Design.
Module 6 completed
You now have designed the external pieces of the system:
- How it connects with Slack (events, web API, interactivity)
- How it handles multi-tenant (OAuth, tokens, isolation)
- How it respects the platform's constraints (3s, rate limits, retry)
- How it formats professional responses (Block Kit, fallbacks)
- How it chooses one platform vs both with a framework
Next module
Module 7 — Performance vs Cost Trade-offs opens Phase 3. You take all the decisions from M1-M6 and add a systematic framework to resolve "it depends": when cache vs compute, managed vs self-hosted, serverless vs containers. What you need before the final Capstone in M8.
Resources
- Slack App Manifest — config as YAML.
- Bolt for Python — a framework that implements many of this doc's patterns.
- Slack OAuth v2 — official reference.
- Block Kit Builder — visual designer.
- Architecture Decision Records (ADR) template.