Implements proactive messaging to a Teams conversation using the Redis conversation-reference pattern in `server/api/msgController.js`. Covers storing `convref:{conversationId}` keys in Redis via `ioredis`, retrieving them, and calling `sendProactiveMessage()` via `BotFrameworkAdapter.continueConversation`. Use when user says 'send proactive message', 'notify channel', 'push message to Teams', or modifies `server/api/msgController.js`. Do NOT use for reactive bot replies (those belong in `server/bot/botActivityHandler.js`).
Implements proactive messaging to a Teams conversation using the Redis conversation-reference pattern in `server/api/msgController.js`. Covers storing `convref:{conversationId}` keys in Redis via `ioredis`, retrieving them, and calling `sendProactiveMessage()` via `BotFrameworkAdapter.continueConversation`. Use when user says 'send proactive message', 'notify channel', 'push message to Teams', or modifies `server/api/msgController.js`. Do NOT use for reactive bot replies (those belong in `server/bot/botActivityHandler.js`).
Proactive Message
Critical
The bot MUST have already received a message from the target conversation before a proactive message can be sent. convref:{conversationId} is written to Redis in server/bot/botActivityHandler.js inside this.onMessage — if this key is missing, the lookup returns null and you get a 404.
Never skip MicrosoftAppCredentials.trustServiceUrl(conversationReference.serviceUrl) on auth-error retries — without it the next attempt will also fail.
Redis key format is always convref:{conversationId} (e.g. convref:19:0c93975aae904b7db892891da3065c33@thread.v2).
Instructions
Store the conversation reference on every incoming message in server/bot/botActivityHandler.js inside this.onMessage:
Verify: run redis-cli get 'convref:<id>' on dragonfly.mailbaby.net:6379 and confirm JSON is stored.
Define sendProactiveMessage in server/api/msgController.js using BotFrameworkAdapter.continueConversation with retry logic (mirrors the existing pattern):
Verify: POST /api/message is already registered in server/api/index.js — do not add another route.
Test end-to-end:
curl -X POST http://localhost:3978/api/message \
-H 'Content-Type: application/json' \
-d '{"message": "Hello from proactive!"}'# Expected: {"message":"sent"}# If 404: the bot hasn't received a message in that channel yet — send any message to the bot first.
Examples
User says: "Add a proactive endpoint that pings the int-dev-private channel when a deploy completes."
Actions taken:
Confirm the conversation reference is stored by sending a test message in the channel and verifying the Redis key exists.
In server/api/msgController.js, set targetConversationId to the int-dev-private channel's conversation ID.
POST /api/message with { "message": "Deploy complete ✅" } → bot posts in channel.
Result: Channel receives the message without any user @mention trigger.
Common Issues
404 no conversation reference found: The bot has not yet received a message in that conversation. Send any message to the bot in the target channel first, then retry.
401 / authorization has been denied: MicrosoftAppId or MicrosoftAppPassword in .env is wrong, or the service URL is not trusted. The retry loop calls MicrosoftAppCredentials.trustServiceUrl(conversationReference.serviceUrl) automatically — if it persists, verify the Azure Bot registration credentials.
ECONNRESET / socket hang up: Transient network error. The sendProactiveMessage retry loop (max 2 attempts, 1 s delay) handles this automatically. If it persists, check connectivity to botframework.com.
Redis ECONNREFUSED: Confirm the Redis host dragonfly.mailbaby.net:6379 is reachable: redis-cli -h dragonfly.mailbaby.net -p 6379 ping should return PONG.
Cannot read properties of null (reading 'serviceUrl'): JSON.parse(stored) returned a malformed reference. Delete the key with redis-cli del 'convref:<id>' and let the bot re-store it on the next incoming message.