@daisy-workflow/plugin-microsoft-teams
v0.1.3
Published
Daisy external plugin — Microsoft Teams connector. One node, many operations.
Readme
Microsoft Teams plugin for Daisy-workflow
One Daisy node that talks to Microsoft Teams via the Microsoft Graph API. The action is selected per-node via the operation dropdown.
Operations
| operation | What it does |
|--------------------------|--------------------------------------------------------------------|
| team.list | List teams the app can see. |
| channel.list | List channels in a team. |
| channel.create | Create a new channel (standard, private, or shared). |
| channel.message.send | Post a message to a channel. Plain text or HTML, optional subject. |
| channel.message.list | List recent messages in a channel. |
| chat.list | List chats (1:1 and group). |
| chat.message.send | Send a message to a chat. |
| chat.message.list | List recent messages in a chat. |
Configure auth (Azure AD app)
The plugin authenticates as an app (client credentials grant) — no end-user sign-in, no callback URLs. The customer registers an application in Azure Active Directory (Microsoft Entra ID), grants it Microsoft Graph permissions, and creates a client secret.
One-time setup:
Open the Microsoft Entra Admin Center → Applications → App registrations → New registration.
Pick a name (e.g.
daisy-workflow). Leave redirect URI blank. Click Register. Note the Directory (tenant) ID and Application (client) ID from the Overview page.Certificates & secrets → New client secret. Pick an expiry, copy the secret value the moment it's shown — it won't be retrievable later.
API permissions → Add a permission → Microsoft Graph → Application permissions. Add at least:
| Permission | Needed for | |-----------------------------|-----------------------------------------------------| |
Team.ReadBasic.All|team.list| |Channel.ReadBasic.All|channel.list| |Channel.Create|channel.create| |ChannelMessage.Read.All|channel.message.list| |ChannelMessage.Send* |channel.message.send| |Chat.ReadBasic.All|chat.list| |Chat.Read.All|chat.message.list| |ChatMessage.Send* |chat.message.send|* Sending messages as an app is a protected API on Microsoft Graph. After granting the permission you may also need to request access via the Protected APIs request form or use Resource-Specific Consent (RSC) via a Teams app manifest. Until that's set up, message-send calls return 403. List / read operations work as soon as admin consent is granted.
Grant admin consent for [tenant] at the top of the permissions list. Without this, every call returns 401.
In Daisy:
Configurations page → New config → generic → name
microsoft-teams.Add three keys:
| Key | Example | |----------------|------------------------------------------| |
tenantId|00000000-0000-0000-0000-000000000000| |clientId|11111111-1111-1111-1111-111111111111| |clientSecret| The secret value from step 3. |A node can override the config name per-call via the
configinput — useful if a workspace talks to multiple tenants.
Install
docker compose -f docker-compose.yml -f docker-compose.plugins.yml \
--profile microsoft-teams up -d
npm run install-plugin -- --endpoint http://daisy-microsoft-teams:8080Per-operation inputs
The manifest declares every input as optional except operation; each
handler checks its own required fields and returns a clear error if
they're missing. Quick reference:
team.list—top,filterchannel.list—teamId(required),top,filterchannel.create—teamId(required),displayName(required),description,membershipType(standard / private / shared)channel.message.send—teamId(required),channelId(required),message(required),contentType(text / html),subject,importance(normal / high / urgent)channel.message.list—teamId(required),channelId(required),top,filterchat.list—top,filterchat.message.send—chatId(required),message(required),contentType,importancechat.message.list—chatId(required),top,filter
Output envelope
{
"ok": true,
"operation": "channel.message.send",
"status": 201,
"result": { "id": "1700000000000", "webUrl": "https://teams.microsoft.com/l/message/...", "from": { ... } },
"url": "https://teams.microsoft.com/l/message/..."
}result is operation-specific:
team.list/channel.list/chat.list→{ <collection>: [...], nextLink }channel.message.list/chat.message.list→{ messages: [...], nextLink }channel.create/channel.message.send/chat.message.send→ the created resource (id, webUrl, etc.)
Finding the IDs
Microsoft Teams resource IDs are awkward to track down — they're not visible in the standard Teams UI. The easiest paths:
- Tenant ID: Entra admin center → Overview.
- Team ID: open the team in Teams → ... → Get link to team →
the ID is the
groupIdquery parameter on the resulting URL. - Channel ID: open the channel in Teams → ... → Get link to
channel → the ID looks like
19:[email protected]and is in the URL. - Chat ID: easier to discover via
chat.listfrom the plugin itself, then pin the id you need.
You can also list everything programmatically with team.list →
channel.list once auth is working.
Caveats worth flagging
- Application permissions are powerful. The Azure AD app you register can read and act across the entire tenant (depending on the permissions you grant). Treat the client secret like a master key — rotate periodically, store via Daisy's KMS-encrypted config.
- Message-send needs protected-API approval. As of writing, sending
channel or chat messages with an app-only token requires either RSC
(Teams app manifest installed in the tenant) or approval via
Microsoft's Protected APIs request flow. The walk-through is in
Microsoft's docs.
Without it, the plugin still works for all read operations, plus
channel.create— only the two*.message.sendops are restricted. - Rate limits. Microsoft Graph applies per-app and per-tenant throttling. Bursts of more than ~30 calls/sec to the same endpoint start returning 429s; the plugin surfaces the error verbatim so the workflow can retry or back off.
Files
plugins-external/microsoft-teams/
├── manifest.json # node schema (inputs + outputs)
├── index.js # servePlugin entry, dispatches by operation
├── lib/
│ ├── client.js # client-credentials auth + token cache + fetch
│ └── actions.js # one async handler per operation
├── package.json
├── Dockerfile
└── README.md