Creating Goosey: AI-Powered Microsoft Teams Bot

Today

Table of Contents

  1. Introduction
  2. The Problem
  3. Solution Overview
  4. Technical Architecture
  5. Teams Capabilities & Commands
  6. AI Integration
  7. Graph & Proactive Messaging
  8. Core Features Deep Dive
  9. Security & Permissions
  10. Deployment & Operations
  11. Challenges & Optimisations
  12. Future Roadmap
  13. Conclusion

Introduction

Goosey is a pragmatic, AI-powered Microsoft Teams bot that helps small-to-mid teams move faster. It turns noisy channels into concise summaries, sends automated morning meeting digests, answers everyday questions, and adds a pinch of personality to lighten the mood.

The Problem

Solution Overview

Technical Architecture

Stack

Server & Adapter

// Adapter and error handling (excerpt)
const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication(process.env);
const adapter = new CloudAdapter(botFrameworkAuthentication);
 
const onTurnErrorHandler = async (context, error) => {
  console.error("[onTurnError]", error);
  await context.sendActivity("The bot encountered an error.");
};
adapter.onTurnError = onTurnErrorHandler;
 
// Messages endpoint
server.post("/api/messages", async (req, res) => {
  await adapter.process(req, res, async (context) => {
    if (context.activity.type === "message") {
      await sendTypingIndicator(context);
    }
    await bot.run(context);
  });
});

Teams Capabilities & Commands

Capabilities

Representative Commands

// Intent detection and routing (excerpt)
const intentResult = await detectIntent(messageText, conversationId, userId, botId, context);
switch (intentResult.intent) {
  case "help":
    return handleHelpCommand(context);
  case "summarise":
    return handleSummariseCommand(context, intentResult);
  case "who_is_off":
    return handleWhoIsOffCommand(context);
  // ... more intents
}

AI Integration

// Intent classification (JSON mode)
const messages = [
  { role: "system", content: systemPrompt },
  { role: "user", content: userPrompt }
];
const completion = await openai.chat.completions.create({
  model: "gpt-4o",
  messages,
  response_format: { type: "json_object" }
});
const parsed = JSON.parse(completion.choices[0].message.content);

Graph & Proactive Messaging

Graph Access

// Acquire Graph token (client credentials)
const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
const params = new URLSearchParams({
  client_id: appId,
  client_secret: appPassword,
  scope: "https://graph.microsoft.com/.default",
  grant_type: "client_credentials"
});
const { data } = await axios.post(tokenUrl, params);
cachedToken = data.access_token;
// Paginate channel messages (excerpt)
let all = [];
let next = null;
let url = `${baseUrl}?$top=${pageSize}&$orderby=createdDateTime desc`;
while (all.length < limit && (next || url)) {
  const { data } = await axios.get(next || url, { headers: { Authorization: `Bearer ${token}` } });
  all = all.concat(data.value);
  next = data["@odata.nextLink"];
  url = null;
}

Proactive Messaging API

// POST /api/send-message (excerpt)
if ((req.headers["x-api-key"] || req.headers["authorization"]) !== process.env.GOOSEY_API_KEY) {
  return res.send(401, { success: false, error: "Unauthorized" });
}
const { name, message } = req.body || {};
// ...look up conversation ref by display name...
await adapter.continueConversationAsync(process.env.MicrosoftAppId, ref, async (ctx) => {
  await ctx.sendActivity(message);
});
res.send(200, { success: true });

Core Features Deep Dive

1. Chat & Thread Summaries

Goosey fetches recent messages (Graph when permitted; otherwise in-memory history), removes noise, and produces short, actionable summaries with clear bullets and next steps.

// Summarise command (excerpt)
const messages = await fetchRecentMessages(context, { limit });
const prompt = buildSummaryPrompt(messages);
const { content } = await callOpenAI(prompt);
await context.sendActivity(formatAsCard("Summary", content));

2. Daily Meeting Digests

Every weekday at 09:00, Goosey posts today’s meetings into a configured channel. Users can opt-in/out individually.

// Scheduler initialisation (excerpt)
if (process.env.MEETINGS_TEAM_ID && process.env.MEETINGS_CHANNEL_ID) {
  scheduleDailyMeetingSummary(process.env.MEETINGS_TEAM_ID, process.env.MEETINGS_CHANNEL_ID, adapter, botId);
}

3. Timesheet & People Intelligence

// People bio lookup (excerpt)
const bio = await getCachedOrScrapedBio(displayName);
return renderAdaptiveCard({ title: displayName, body: bio.summary, links: bio.links });

4. GIFs, Docs, and Image Analysis

Security & Permissions

Deployment & Operations

# Deploy (excerpt)
steps:
  - name: Deploy Goosey
    run: |
      ssh $SSH_HOST << 'EOF'
        cd /srv/goosey
        git pull --ff-only
        npm install --omit=dev
        ./node_modules/.bin/pm2 restart goosey || ./node_modules/.bin/pm2 start index.js --name goosey
      EOF

Challenges & Optimisations

Future Roadmap

Conclusion

Goosey shows how far a focused Teams bot can go with a small, modern stack. By blending Microsoft Graph data with OpenAI and a touch of personality, it removes daily friction for teams, summarising the noise, surfacing what matters, and automating routine updates.