Your own Microsoft (Teams / Entra) apps
Around thirty minutes, most of it in the Azure portal. At the end your people can sign in to TeamHUD with their Microsoft work account — no separate TeamHUD password — and your server can tell their apps the moment a Teams message arrives (the “doorbell”), without the message ever passing through your server.
Read INTEGRATIONS.md first: it covers the hostname requirement and the desktop setting without
which none of this is used. Throughout, https://teamhud.example.com stands for your PUBLIC_BASE_URL.
What works today, and what does not
Section titled “What works today, and what does not”| Part | State | Needs |
|---|---|---|
| Sign in with Microsoft (part 1) | ✅ works, verified against a real Entra tenant | one app registration, GOTRUE_EXTERNAL_AZURE_*, AZURE_TENANT |
| Live updates / doorbell — server side (part 2) | ✅ built and tested against a simulated Graph; not yet run against a real tenant | a second app registration with admin consent, MSTEAMS_*, a public HTTPS address |
| Teams chats and channels inside TeamHUD | ⬜ not built — there is no Teams connector, and the desktop app does not yet listen for the doorbell | — |
Be honest with your users: signing in with Microsoft works; reading Teams inside TeamHUD does not yet.
Part 1 — Sign in with Microsoft
Section titled “Part 1 — Sign in with Microsoft”TeamHUD accounts made this way have no TeamHUD password — Microsoft is their only way in. (forgot password
sends them nothing, and the reset route refuses them.)
1. Create the app registration
Section titled “1. Create the app registration”Azure portal → Microsoft Entra ID → App registrations → New registration.
- Name: something your users recognise, for example Acme TeamHUD. They see it on Microsoft’s consent screen.
- Supported account types: Accounts in this organizational directory only (single tenant). Your server is for your organisation; a multi-tenant app would let other organisations’ staff reach your consent screen.
- Redirect URI: platform Web,
https://teamhud.example.com/auth/v1/callback— yourPUBLIC_BASE_URLfollowed by/auth/v1/callback, character for character. That is your server’s sign-in service, not the desktop app, and it is the address your server sends Microsoft unless you overrideGOTRUE_EXTERNAL_AZURE_REDIRECT_URI. Microsoft requireshttpsexcept forlocalhost. (Trying it on a local stack first? See On a local stack below.)
Then, on the new app:
- Overview → copy the Application (client) ID and the Directory (tenant) ID.
- Certificates & secrets → New client secret → copy the Value immediately (it is hidden afterwards). Put a reminder in your calendar: secrets expire (24 months at most) and sign-in stops the day it does.
- Token configuration → Add optional claim → ID token →
email. TeamHUD needs an email address for every account, and Microsoft does not always send one. If the portal offers to turn on the Microsoft Graphemailpermission with it, accept. - Every person who will sign in needs an Email set on their user (Entra → Users → the user → Edit properties →
Contact information). A user with no mailbox otherwise has no
emailclaim and sign-in fails with a vague error.
2. Put it in .env
Section titled “2. Put it in .env”GOTRUE_EXTERNAL_AZURE_ENABLED=trueGOTRUE_EXTERNAL_AZURE_CLIENT_ID=<application (client) id>GOTRUE_EXTERNAL_AZURE_SECRET=<client secret value>AZURE_TENANT=<directory (tenant) id>SOCIAL_PROVIDERS=azure # or slack_oidc,azureSet AZURE_TENANT. It pins sign-in to your organisation: the server sends people to your tenant’s own Microsoft
sign-in page. Use the directory (tenant) id or a verified domain (contoso.onmicrosoft.com). firstboot.sh asks for
it and refuses to continue without one. Unset, the server uses Microsoft’s shared common address instead. A
single-tenant app like the one above is refused there (AADSTS50194); an app switched to multi-tenant would let
any Microsoft account in the world through. (organizations, any work or school account, also needs a
multi-tenant app.)
No /v2.0 on the end. The authority is https://login.microsoftonline.com/<tenant> and nothing more. The
sign-in service appends /oauth2/v2.0/authorize itself, and Microsoft answers the doubled path with a 404 (checked
again 2026-10-16). This matters only if you set the whole authority yourself in GOTRUE_EXTERNAL_AZURE_URL, which
replaces AZURE_TENANT; most installs should not.
docker compose up -dcurl -s https://teamhud.example.com/auth/providers # should list "azure"./verify-stack.sh # step 13 fails if a provider in SOCIAL_PROVIDERS is not offeredMicrosoft sent no display name in our 2026-10-08 test, so that person’s first organization was named from their email
address (tech@… became Tech’s Team).
3. Choose who may create an account
Section titled “3. Choose who may create an account”SIGNUP_MODE=sso_only makes Microsoft the only way to a new account (no self-service passwords); an invitation
still lets someone in. open also allows email + password. closed accepts nobody new — including by Microsoft.
See SIGN-IN-AND-CHANNELS.md.
On a local stack (no public hostname)
Section titled “On a local stack (no public hostname)”To try sign-in before your server has its real address. Use a separate test registration, or remove the
localhost redirect from your real one afterwards.
- Register the redirect
http://localhost:8000/auth/v1/callback. Port 8000 is the gateway that carries the sign-in service, published on this machine only (GATEWAY_PORTin.env; use yours if you changed it). Not:3001: that is the API server, and Microsoft’s redirect would never reach sign-in. Not127.0.0.1: in our 2026-10-08 test Microsoft would not match it to the registeredlocalhostaddress. - Add the two overrides below to the settings from step 2, then
docker compose up -dagain. The first points the browser at the gateway; the second sends Microsoft the redirect you just registered. Without them both are built fromPUBLIC_BASE_URL, which on a local stack is the API server.
AUTH_PUBLIC_URL=http://localhost:8000GOTRUE_EXTERNAL_AZURE_REDIRECT_URI=http://localhost:8000/auth/v1/callbackRemove both when you move to the real hostname. After Microsoft, sign-in returns to <APP_URL>/auth/callback. The
default local .env points APP_URL at the API server (port 3001), so a full round trip from the dashboard in a
browser also needs APP_URL set to the dashboard’s own address.
If sign-in fails
Section titled “If sign-in fails”| You see | Meaning |
|---|---|
azure missing from /auth/providers |
azure is not in SOCIAL_PROVIDERS, or the sign-in service has no or wrong credentials for it. In the second case ./verify-stack.sh step 13 fails and names it |
| Microsoft shows a 404 page right after the click | the authority ends in /v2.0 — see step 2 |
Microsoft says the app is not multi-tenant (AADSTS50194) |
AZURE_TENANT is unset — see step 2 |
Microsoft says the redirect URI does not match (AADSTS50011) |
the registered redirect is not exactly <PUBLIC_BASE_URL>/auth/v1/callback (or your override) |
Microsoft says the account does not exist in the tenant (AADSTS50020) |
the person is not in the tenant you pinned. This is the pin working as intended |
| Back on the start page, not signed in, no error | the sign-in service dropped the return address: <APP_URL>/auth/callback must match GOTRUE_URI_ALLOW_LIST (default <PUBLIC_BASE_URL>/** plus the desktop app’s http://localhost:1420/**), which matters when APP_URL is a different host |
Part 2 — Live updates (the doorbell)
Section titled “Part 2 — Live updates (the doorbell)”Microsoft calls your server when a message arrives in any Teams chat or channel in your tenant; your server works out which of your people are in that conversation and rings their TeamHUD app; their app fetches the message itself with its own sign-in. Your server never receives message text — the subscriptions are created without resource data.
This needs a second app registration, because it acts as the server, not as a person.
1. Create the server’s app registration
Section titled “1. Create the server’s app registration”Same place, New registration, name it Acme TeamHUD server, single tenant, no redirect URI.
-
API permissions → Add a permission → Microsoft Graph → Application permissions, add exactly these three:
ChannelMessage.Read.All,Chat.Read.All,ChannelMember.Read.All. They are the narrowest Microsoft offers for this: it accepts nothing smaller for being told about every chat and channel, and the third is only for finding who is in a channel. (ChatMember.Read.Allis not needed;Chat.Read.Allalready covers who is in a chat.) -
Grant admin consent for
. This needs a tenant administrator, and it is the step most often missed: without it, the server’s status page showsInsufficient privileges.What you are consenting to, plainly: these permissions let this server app read every Teams chat and channel message in your organisation, and who is in each chat and channel, without anyone signed in. Microsoft names them Read all channel messages, Read all chat messages and Read the members of all channels. TeamHUD uses far less: it asks Microsoft to say only that a message arrived and where, never for the message itself, and it looks up the email addresses of the people in that conversation to know whose app to ring. That restraint is TeamHUD’s code, not a limit Microsoft enforces, so treat the app’s client secret like an administrator password. The doorbell uses no one’s Microsoft sign-in, only this app’s own secret. Delete the secret or this app registration and the doorbell stops; sign-in (part 1, a separate app) and everything else keep working.
-
Certificates & secrets → a client secret, as above.
No billing setup, no access request (Microsoft’s documentation, read 2026-10-16). Teams message change notifications are not billed: “Starting August 25, 2025, the Teams APIs listed in this article are no longer metered, and no billing configuration is required to use these APIs. … the
modelquery parameter is no longer required and is ignored when supplied.” So you do not link an Azure subscription to this app, and TeamHUD sends no payment model. Microsoft removed its Teams protected APIs page (the access-request form) in 2023, and the current subscription and Teams notification pages name no access request. Neither is confirmed in a real tenant yet: if the status page below shows402 Payment Requiredor a Microsoft error about API access, that is why.
2. Put it in .env
Section titled “2. Put it in .env”ENT_SECRET=<openssl rand -hex 32> # if you do not already have one; it also derives the webhook secretMSTEAMS_TENANT_ID=<directory (tenant) id>MSTEAMS_CLIENT_ID=<the SERVER app's application (client) id>MSTEAMS_CLIENT_SECRET=<the SERVER app's secret value>All four, or the doorbell stays off. Leaving them empty is fine: everything else works, and apps simply poll.
docker compose up -d3. Confirm Microsoft can reach you
Section titled “3. Confirm Microsoft can reach you”Microsoft must be able to call, over HTTPS with a valid certificate (not a raw IP, not a self-signed certificate):
https://teamhud.example.com/webhooks/msteams/notificationshttps://teamhud.example.com/webhooks/msteams/lifecycle
Both answer Microsoft’s validation handshake even before you finish configuring, so you can test reachability first.
As a platform admin, open GET https://teamhud.example.com/msteams/doorbell/status:
| You see | Meaning |
|---|---|
{"enabled": false, "missing": [...]} |
the named settings are not set (names only, never values) |
enabled: true with two subscriptions and lastError: null |
working: Microsoft holds two subscriptions for you and your server renews them every 30 minutes |
lastError: "...Insufficient privileges..." |
admin consent was not granted, or a permission is missing |
lastError: "...(401): AADSTS7000215..." |
wrong or expired client secret |
lastError: "...(402)..." or Payment Required |
Microsoft is billing these APIs again (not expected: unmetered since 2025-08-25) — check Microsoft’s current Teams API licensing page |
lastError: "...Subscription validation request failed..." |
Microsoft could not reach the notification URL — check DNS, TLS and any firewall (Microsoft’s IP ranges are listed under Additional endpoints for Microsoft 365) |
How it protects people
Section titled “How it protects people”- A person can only listen on their own doorbell, and only if Microsoft itself put them in your tenant at sign-in. An account made with a password and a look-alike email hears nothing.
- A notification is accepted only if it carries the secret we set when creating the subscription, for your tenant.
- Anything resembling message text in a notification is never read.
- If Microsoft reports lost notifications, every connected app is told to re-check.
Renewal and secrets
Section titled “Renewal and secrets”Subscriptions live up to three days and are renewed automatically. The client secrets (both apps) expire on the
date you chose — rotate them before then, update .env, and docker compose up -d. Changing ENT_SECRET changes
the webhook secret: the server recreates its subscriptions on the next 30-minute tick, and the old ones lapse.
Outlook and other IMAP mail
Section titled “Outlook and other IMAP mail”IMAP mail is in the alpha (owner, corrected 2026-10-27), as a Pro preview with app-password providers;
ImapDataSourcePluginis wired into the desktop app (plugin-entries.ts). XOAUTH2 for Microsoft still waits on the Entra client ID.
This part exists today, and needs no server setup at all. TeamHUD’s mail connector speaks IMAP/SMTP straight from each user’s desktop. Credentials are stored only on that machine, encrypted with the OS keychain, and never sent to your server.
| Mailbox | How users connect | Notes |
|---|---|---|
| Any IMAP provider with password or app-password login (Fastmail, iCloud, personal Outlook.com with 2-step verification, most hosts) | host + username + app password | Available, early: so far tested only against simulated mail servers. Replying from TeamHUD is still being finished |
| Gmail over IMAP | app password (personal accounts with 2-Step Verification), or the user’s own Google OAuth client | Workspace accounts usually cannot create app passwords. Prefer GOOGLE.md |
| Microsoft 365 / Exchange Online | Microsoft sign-in (OAuth) | Not available yet. Microsoft has turned off password login for IMAP on business tenants, and TeamHUD has not yet registered the app it needs. The code is written but has never run against a real Microsoft mailbox |
Mail over IMAP updates through the desktop’s own IMAP connection, so it needs no Gmail push or similar server-side setup.
Status tracking
Section titled “Status tracking”The design and remaining work live in the TeamHUD repository under
plans/1.1.0/MS_TEAMS_PLAN.md (Teams, Sign in with Microsoft) and
plans/1.1.0/IMAP_MAIL_CONNECTOR_PLAN.md (Microsoft 365 mail).