Teams and email¶
A Teams tool says chat_id is required¶
Entrabot has no default chat. Pass an explicit chat_id from:
create_chat- an inbound channel notification's metadata
- the
watched_chatslist in the configured storage backend - Agent User chat auto-discovery
The Teams poll runs every 5 seconds once at least one watched chat exists.
Agent User mode also discovers /me/chats every 120 seconds. Newly discovered
chats are saved to that list and join the next polling cycle.
A message was sent but the reply did not appear¶
Delivery depends on the MCP host:
- Claude Code with channel push enabled returns from
send_teams_messageimmediately. Start it with:
The reply arrives later through notifications/claude/channel.
- Copilot CLI and other non-channel hosts auto-wait after
send_teams_message and return a verified sponsor reply inline as
sponsor_reply. Use the reply's returned chat_id; it can differ from the
chat that was just messaged.
Do not poll watch_teams_replies in a loop. wait_for_sponsor_dm is only for
an operator's explicit request to block until a reply arrives.
See Teams and Chat Workflows and Clients.
Sending is rejected for a missing thinking placeholder¶
Substantive Teams DMs require a recent post_thinking_placeholder for the same
chat_id. Post the placeholder, perform the work, then update or resolve it.
This is a delivery discipline check, not an authentication failure.
Graph returns 401, 403, 404, or 429¶
| Status | Meaning | Safe recovery |
|---|---|---|
| 401 | Token expired, revoked, or wrong for the resource | Entrabot refreshes and retries once. If it repeats, inspect whoami, consent, and the named auth hop. |
| 403 | Missing delegated permission, missing Teams/Exchange license, or the current identity is not a member/author | Check Agent User licensing, per-principal consent, chat membership, and whether the operation targets the agent's own message. |
| 404 | Chat or message ID does not exist for this identity, or is inaccessible | Copy the exact current chat_id or Graph message ID from tool output; confirm the active identity belongs to the resource. |
| 429 | Microsoft Graph throttled the request | Honor Retry-After; do not immediately loop. Entrabot surfaces the retry delay. |
The message body limit is 28,000 characters. Split longer content into multiple messages or put the content in a file and share it.
A newly added chat is not being polled¶
Auto-discovery runs only in Agent User mode and can take up to 120 seconds.
Delegated sessions do not run the discovery task. Register the chat by using
create_chat, or add its ID to the normal watched-chat flow.
The list lives in the configured storage backend under the key watched_chats.
With a local backend, that is ENTRABOT_DATA_DIR/watched_chats. With Blob Storage,
machines sharing a container share one list, and any local copy is ignored. Each
instance reads the list at startup and picks up later additions through its own
discovery sweep.
Discovery reads up to 20 pages of /me/chats, or 1,000 chats, and logs a warning
if it stops at that bound.
A discovered chat's message did not surface¶
A chat first seen through discovery or the shared list surfaces its newest message
only if it is under 24 hours old. Older messages are deliberately not pushed, so
that finding a chat for the first time can't replay old instructions as if they
were new. Later messages in that chat are pushed normally. Read earlier history with
read_teams_messages.
Messages repeat, disappear, or stop after a cursor error¶
Per-chat delivery cursors use MemoryBackend; they can be local or Blob-backed.
Entrabot uses a timestamp, a bounded message-ID tail, and ETag compare-and-swap
claims so concurrent pollers do not deliver the same message twice.
Cursor reads and claims fail closed. If a cursor is corrupt, unavailable, or cannot be updated after bounded ETag retries, Entrabot pushes nothing rather than risk replaying messages. Check storage health before deleting cursor state. For upgrade-related replay prevention, see Migrations and upgrades.
Email notifications do not arrive¶
The background email poll:
- Runs every 60 seconds.
- Runs only in Agent User mode.
- Reads
/me/messages. - Initializes a missing cursor to the current time, so first startup does not replay historical mail.
- Stores its cursor in the configured storage backend at
poll_state/email_cursor.txt, shared by every instance on a cloud backend. - Claims each window of mail by advancing that cursor before pushing, so only one instance pushes a given email.
Check whoami for auth_mode: agent_user, then confirm the Agent User has a
mailbox, mail permissions, and completed license provisioning.
If logs repeat Email cursor unresolved, the cursor can't be read or isn't a
timestamp, and nothing is pushed until it can be. Check storage health first; see
Storage troubleshooting. If the storage is healthy and the stored
value is corrupt, delete poll_state/email_cursor.txt. The next cycle restarts the
cursor at the current time, and any mail in between stays readable through
read_email. In local mode, delete a legacy email_cursor.txt too, or that value
gets adopted instead.
The cursor starts at the current time only when its key is first created: the first
boot on a container, or after you delete the key. Mail from before then isn't
pushed; read it with read_email. Once the key exists, a restart reuses it, and
mail that arrived while no instance was running is fetched and pushed on the next
poll (a poll fetches the 50 newest).
The poll filters Teams/Microsoft 365 notification domains, common no-reply senders, and messages sent by the Agent User from Sent Items. Filtered mail still advances the cursor.
A Purview-protected email has no readable body¶
Entrabot detects a message.rpmsg attachment and marks the message as
encrypted. Decrypting Microsoft Purview-protected RPMSG content is not
supported. Open the message in an authorized Microsoft 365 client or ask the
sender for an accessible copy.
read_email cannot find a message¶
message_id is the Microsoft Graph resource ID returned by the poll, channel
notification, or another Graph listing. It is not the RFC Message-ID header.
Use it with the same mailbox in which it was obtained. Moving or deleting the
message can invalidate a previously returned ID because Entrabot does not
request immutable Graph IDs.
For replies, pass the original Graph ID as reply_to_message_id; Graph then
preserves thread headers and takes the subject from the original message.