┌─────────────────────────────────────────────────────────────────┐
│ Slack Workspace │
│ │
│ User: "@MyBot what is the weather?" │
└────────────────────────┬────────────────────────────────────────┘
│ Socket Mode (WebSocket)
│
┌────────────────────────▼────────────────────────────────────────┐
│ SlackServer (.NET) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ SlackListenerService │ │
│ │ • Receives Slack events via Socket Mode │ │
│ │ • Matches messages against bot patterns │ │
│ │ • Finds matching bots │ │
│ └────────────┬─────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────▼─────────────────────────────────────────────┐ │
│ │ BotExecutor │ │
│ │ • Spawns bot process (pwsh/python/dotnet/etc) │ │
│ │ • Sets environment variables (channel, user, message) │ │
│ │ • Streams stdout line-by-line │ │
│ │ • Captures stderr for logging │ │
│ └────────────┬─────────────────────────────────────────────┘ │
│ │ JSON Lines │
│ ┌────────────▼─────────────────────────────────────────────┐ │
│ │ ActionProcessor │ │
│ │ • Parses JSON from bot stdout │ │
│ │ • Validates action types │ │
│ │ • Calls Slack API │ │
│ │ • Stores message references (for updates) │ │
│ └────────────┬─────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────▼─────────────────────────────────────────────┐ │
│ │ MessageReferenceStore │ │
│ │ • msg1 → ts:1234567890.123456 │ │
│ │ • progress → ts:1234567891.234567 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────┬────────────────────────────────────────┘
│ Slack Web API
│
┌────────────────┼────────────────┐
│ │ │
┌───────▼──────┐ ┌─────▼──────┐ ┌─────▼──────┐
│ PowerShell │ │ Python │ │ C# CLI │
│ Bot Script │ │ Bot │ │ Tool │
│ │ │ │ │ │
│ Outputs JSON │ │ Prints JSON│ │ WriteLine │
└──────────────┘ └────────────┘ └────────────┘
1. Slack User sends message:
"@MyBot what is the weather?"
2. Slack sends app_mention event via Socket Mode
↓
3. SlackListenerService receives event:
{
channel: "C01234",
user: "U56789",
text: "@MyBot what is the weather?",
ts: "1234567890.123456"
}
4. Match against bot patterns:
✅ "Question Bot" matches: <@\w+>\s+.*
5. BotExecutor starts process:
pwsh ./scripts/question-bot.ps1
Environment:
- TRIGGER_MESSAGE="@MyBot what is the weather?"
- TRIGGER_CHANNEL="C01234"
- TRIGGER_TS="1234567890.123456"
- TRIGGER_USER="U56789"
- TRIGGER_USER_NAME="john_doe"
6. Bot script processes and outputs JSON:
{"action":"post_message","channel":"C01234","text":"Checking...","store_as":"msg1"}
{"action":"update_message","channel":"C01234","message_ref":"msg1","text":"Weather is sunny!"}
7. ActionProcessor parses each line:
Line 1: Post message → Store ts as "msg1"
Line 2: Update message → Get ts from "msg1" → Update
8. Slack API calls:
POST /chat.postMessage
POST /chat.update
9. User sees in Slack:
"Weather is sunny!"
┌─────────────────────────────────────────────────────────────┐
│ 1. Message Received │
│ • Parse event data │
│ • Extract: channel, user, text, timestamp │
└────────────┬────────────────────────────────────────────────┘
│
┌────────────▼────────────────────────────────────────────────┐
│ 2. Pattern Matching │
│ • Loop through all bots │
│ • Check bot.type (channel/bot_dm/all) │
│ • Test bot.messagePattern regex │
│ • Collect matching bots │
└────────────┬────────────────────────────────────────────────┘
│
┌────────────▼────────────────────────────────────────────────┐
│ 3. Bot Execution (for each matching bot) │
│ • Create MessageReferenceStore instance │
│ • Create ActionProcessor instance │
│ • Create BotExecutor instance │
│ • Start process with environment variables │
└────────────┬────────────────────────────────────────────────┘
│
┌────────────▼────────────────────────────────────────────────┐
│ 4. Stream Processing │
│ • Read stdout line-by-line │
│ • For each line: │
│ - Parse JSON │
│ - Validate action │
│ - Execute Slack API call │
│ - Store/retrieve message references │
│ • Capture stderr for logging │
└────────────┬────────────────────────────────────────────────┘
│
┌────────────▼────────────────────────────────────────────────┐
│ 5. Completion │
│ • Wait for process exit │
│ • Log execution summary: │
│ - Exit code │
│ - Actions processed count │
│ - Elapsed time │
│ - Timeout status │
│ • Clean up resources │
└─────────────────────────────────────────────────────────────┘
Purpose: Main entry point and Slack connection
Responsibilities:
- Load config.yaml
- Connect to Slack via Socket Mode
- Register event handlers
- Route messages to BotExecutor
- Manage bot lifecycle
Key Methods:
ExecuteAsync()- Main service loopHandle(MessageEvent)- Process incoming messages
Purpose: Execute bot scripts and manage processes
Responsibilities:
- Spawn bot process
- Set environment variables
- Stream stdout/stderr
- Handle timeouts
- Process cancellation
Key Methods:
ExecuteAsync()- Execute bot and return resultCreateProcessStartInfo()- Configure process
Purpose: Parse JSON and execute Slack actions
Responsibilities:
- Parse JSON from stdout
- Validate action schema
- Execute Slack API calls
- Handle errors gracefully
- Log action execution
Key Methods:
ProcessActionAsync()- Parse and execute single actionHandlePostMessageAsync()- Post message to SlackHandleUpdateMessageAsync()- Update existing messageHandlePostBlocksAsync()- Post rich formatted message
Purpose: Store message timestamps for updates
Responsibilities:
- Store message ID → timestamp mapping
- Retrieve timestamps by reference name
- Clear references after bot execution
Key Methods:
Store(name, timestamp)- Store referenceGet(name)- Retrieve timestampClear()- Clear all references
class BotDefinition {
string Name; // Bot display name
string? Description; // Optional description
string Type; // channel | bot_dm | all
string? ChannelId; // Required for type: channel
string MessagePattern; // Regex pattern
string Script; // Command to execute
string? WorkingDirectory; // Optional working dir
int Timeout = 30; // Seconds
Dictionary<string, string>? Environment; // Custom env vars
}// Base action
class BotAction {
string Action; // Action type
}
// Post message
class PostMessageAction : BotAction {
string Channel;
string Text;
string? ThreadTs;
string? StoreAs;
}
// Update message
class UpdateMessageAction : BotAction {
string Channel;
string MessageRef;
string Text;
}
// Post blocks
class PostBlocksAction : BotAction {
string Channel;
string Text;
object[] Blocks;
string? ThreadTs;
string? StoreAs;
}- Never expose sensitive data in environment variables
- Bot scripts have access to all server environment variables
- Use secrets management for sensitive configuration
- Each bot runs in separate process
- Processes are killed on timeout
- Scripts can't access other bot's data directly
- All Slack input is treated as untrusted
- Bot patterns use regex with timeout protection
- JSON parsing has error handling
- Tokens stored in config.yaml (should be gitignored)
- Consider using environment variables for tokens
- Rotate tokens regularly
- Multiple bots can match and execute simultaneously
- Each bot execution is independent
- Thread-safe MessageReferenceStore per execution
- One process per bot execution
- Streaming stdout (low memory)
- Timeouts prevent hung processes
- Automatic process cleanup
- Socket Mode: 1 connection per SlackServer instance
- Can handle multiple channels
- Bot executions are asynchronous
- Limited by system process limits
Debug - Detailed action execution, JSON parsing
Information - Bot matching, execution start/end
Warning - Failed actions, timeouts, API errors
Error - Fatal errors, connection failures
[2026-03-26 08:30:15] info: Loaded 3 bot(s) from config.yaml
[2026-03-26 08:30:15] info: ✅ Connected to Slack!
[2026-03-26 08:30:20] info: Bot 'Question Bot' matched message in C01234
[2026-03-26 08:30:20] info: [Question Bot] Executing: pwsh ./scripts/question-bot.ps1
[2026-03-26 08:30:20] debug: [Question Bot] Processing action: post_message
[2026-03-26 08:30:21] info: [Question Bot] Posted message to C01234
[2026-03-26 08:30:21] info: [Question Bot] Execution finished: 2 actions in 1.2s
- ActionProcessor JSON parsing
- MessageReferenceStore operations
- Pattern matching logic
- Full bot execution flow
- Slack API mocking
- Timeout handling
- Error scenarios
- Test bots (PowerShell, C#)
- Real Slack workspace testing
- Various message patterns
- Error conditions
dotnet runsc.exe create SlackServer binPath="C:\SlackServer\SlackServer.exe"[Service]
Type=notify
ExecStart=/usr/bin/dotnet /opt/slackserver/SlackServer.dllFROM mcr.microsoft.com/dotnet/runtime:10.0
COPY publish/ /app
WORKDIR /app
ENTRYPOINT ["dotnet", "SlackServer.dll"]Add new action types in ActionProcessor:
case "custom_action":
await HandleCustomActionAsync(jsonLine, botName);
break;Extend SlackListenerService to handle more events:
.RegisterEventHandler<ReactionAddedEvent>(_ => new ReactionHandler(...))Implement IBotStateStore interface:
interface IBotStateStore {
Task<string?> GetStateAsync(string botName, string key);
Task SetStateAsync(string botName, string key, string value);
}Add bot execution middleware:
interface IBotMiddleware {
Task<BotExecutionResult> ExecuteAsync(
BotDefinition bot,
BotContext context,
Func<Task<BotExecutionResult>> next);
}