From f7b11300187111fa8cc84be6f519dc75d48696b8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 08:29:25 +0000 Subject: [PATCH 1/4] Truncate oversized Teams webhook payloads to prevent HTTP 413 errors When an adaptive card payload exceeds the Teams webhook size limit (~28KB), progressively remove body items from the card and append a truncation notice. This prevents the entire alert flow from failing when a single message is too large for the Teams endpoint. Co-Authored-By: Michael Myaskovsky --- .../messaging_integrations/teams_webhook.py | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/elementary/messages/messaging_integrations/teams_webhook.py b/elementary/messages/messaging_integrations/teams_webhook.py index 6c4fa7feb..fbf90b26f 100644 --- a/elementary/messages/messaging_integrations/teams_webhook.py +++ b/elementary/messages/messaging_integrations/teams_webhook.py @@ -1,6 +1,7 @@ +import json from datetime import datetime, timezone from http import HTTPStatus -from typing import Any, Optional +from typing import Any, Dict, List, Optional import requests from ratelimit import limits, sleep_and_retry @@ -25,6 +26,10 @@ Channel: TypeAlias = Optional[str] ONE_SECOND = 1 +# Teams webhook payload size limit in bytes. The actual limit is 28KB for +# Adaptive Cards, but we use a slightly lower threshold to leave room for +# the envelope (message wrapper, attachment metadata, etc.). +TEAMS_PAYLOAD_SIZE_LIMIT = 27 * 1024 class TeamsWebhookHttpError(MessagingIntegrationError): @@ -36,8 +41,8 @@ def __init__(self, response: requests.Response): ) -def send_adaptive_card(webhook_url: str, card: dict) -> requests.Response: - payload = { +def _build_payload(card: dict) -> dict: + return { "type": "message", "attachments": [ { @@ -48,6 +53,50 @@ def send_adaptive_card(webhook_url: str, card: dict) -> requests.Response: ], } + +def _truncation_notice_item() -> Dict[str, Any]: + return { + "type": "TextBlock", + "text": "... Content truncated due to message size limits. " + "View full details in Elementary Cloud.", + "wrap": True, + "isSubtle": True, + "italic": True, + } + + +def _truncate_card(card: dict) -> dict: + """Progressively remove body items from the card until the payload fits + within the Teams size limit. A truncation notice is appended so the + recipient knows content was removed.""" + body: List[Dict[str, Any]] = list(card.get("body", [])) + if not body: + return card + + while len(body) > 1: + payload = _build_payload({**card, "body": body + [_truncation_notice_item()]}) + if len(json.dumps(payload)) <= TEAMS_PAYLOAD_SIZE_LIMIT: + break + body.pop() # remove the last body item + + return {**card, "body": body + [_truncation_notice_item()]} + + +def send_adaptive_card(webhook_url: str, card: dict) -> requests.Response: + payload = _build_payload(card) + + # Proactively truncate if the payload exceeds the Teams size limit. + payload_json = json.dumps(payload) + if len(payload_json) > TEAMS_PAYLOAD_SIZE_LIMIT: + logger.warning( + "Teams webhook payload size (%d bytes) exceeds limit (%d bytes), " + "truncating card body", + len(payload_json), + TEAMS_PAYLOAD_SIZE_LIMIT, + ) + card = _truncate_card(card) + payload = _build_payload(card) + response = requests.post( webhook_url, json=payload, From 1d7dbdcf4f534cda8f17d493961d9cf473e6cc91 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 08:30:23 +0000 Subject: [PATCH 2/4] Fix edge cases: handle single oversized body item, use valid TextBlock markdown for italic Co-Authored-By: Michael Myaskovsky --- .../messaging_integrations/teams_webhook.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/elementary/messages/messaging_integrations/teams_webhook.py b/elementary/messages/messaging_integrations/teams_webhook.py index fbf90b26f..b019801b0 100644 --- a/elementary/messages/messaging_integrations/teams_webhook.py +++ b/elementary/messages/messaging_integrations/teams_webhook.py @@ -57,11 +57,27 @@ def _build_payload(card: dict) -> dict: def _truncation_notice_item() -> Dict[str, Any]: return { "type": "TextBlock", - "text": "... Content truncated due to message size limits. " - "View full details in Elementary Cloud.", + "text": "_... Content truncated due to message size limits. " + "View full details in Elementary Cloud._", "wrap": True, "isSubtle": True, - "italic": True, + } + + +def _minimal_card(card: dict) -> dict: + """Return a minimal card with just a truncation notice when even a single + body item is too large.""" + return { + **card, + "body": [ + { + "type": "TextBlock", + "text": "Alert content too large to display in Teams. " + "View full details in Elementary Cloud.", + "wrap": True, + "weight": "bolder", + } + ], } @@ -79,7 +95,12 @@ def _truncate_card(card: dict) -> dict: break body.pop() # remove the last body item - return {**card, "body": body + [_truncation_notice_item()]} + truncated = {**card, "body": body + [_truncation_notice_item()]} + # If even a single body item plus the notice is too large, fall back to + # a minimal card. + if len(json.dumps(_build_payload(truncated))) > TEAMS_PAYLOAD_SIZE_LIMIT: + return _minimal_card(card) + return truncated def send_adaptive_card(webhook_url: str, card: dict) -> requests.Response: From 59c6b7be8ef1a5887e4f1dbf7ca005eeddf51c97 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 08:33:39 +0000 Subject: [PATCH 3/4] Remove comments and docstrings from new code Co-Authored-By: Michael Myaskovsky --- .../messaging_integrations/teams_webhook.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/elementary/messages/messaging_integrations/teams_webhook.py b/elementary/messages/messaging_integrations/teams_webhook.py index b019801b0..08516d2a7 100644 --- a/elementary/messages/messaging_integrations/teams_webhook.py +++ b/elementary/messages/messaging_integrations/teams_webhook.py @@ -26,9 +26,6 @@ Channel: TypeAlias = Optional[str] ONE_SECOND = 1 -# Teams webhook payload size limit in bytes. The actual limit is 28KB for -# Adaptive Cards, but we use a slightly lower threshold to leave room for -# the envelope (message wrapper, attachment metadata, etc.). TEAMS_PAYLOAD_SIZE_LIMIT = 27 * 1024 @@ -65,8 +62,6 @@ def _truncation_notice_item() -> Dict[str, Any]: def _minimal_card(card: dict) -> dict: - """Return a minimal card with just a truncation notice when even a single - body item is too large.""" return { **card, "body": [ @@ -82,9 +77,6 @@ def _minimal_card(card: dict) -> dict: def _truncate_card(card: dict) -> dict: - """Progressively remove body items from the card until the payload fits - within the Teams size limit. A truncation notice is appended so the - recipient knows content was removed.""" body: List[Dict[str, Any]] = list(card.get("body", [])) if not body: return card @@ -93,11 +85,9 @@ def _truncate_card(card: dict) -> dict: payload = _build_payload({**card, "body": body + [_truncation_notice_item()]}) if len(json.dumps(payload)) <= TEAMS_PAYLOAD_SIZE_LIMIT: break - body.pop() # remove the last body item + body.pop() truncated = {**card, "body": body + [_truncation_notice_item()]} - # If even a single body item plus the notice is too large, fall back to - # a minimal card. if len(json.dumps(_build_payload(truncated))) > TEAMS_PAYLOAD_SIZE_LIMIT: return _minimal_card(card) return truncated @@ -105,8 +95,6 @@ def _truncate_card(card: dict) -> dict: def send_adaptive_card(webhook_url: str, card: dict) -> requests.Response: payload = _build_payload(card) - - # Proactively truncate if the payload exceeds the Teams size limit. payload_json = json.dumps(payload) if len(payload_json) > TEAMS_PAYLOAD_SIZE_LIMIT: logger.warning( From 97195a2ebc411459fa822269370591d651adc39c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 08:39:49 +0000 Subject: [PATCH 4/4] Remove Elementary Cloud references from truncation messages Co-Authored-By: Michael Myaskovsky --- elementary/messages/messaging_integrations/teams_webhook.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/elementary/messages/messaging_integrations/teams_webhook.py b/elementary/messages/messaging_integrations/teams_webhook.py index 08516d2a7..533fe82a1 100644 --- a/elementary/messages/messaging_integrations/teams_webhook.py +++ b/elementary/messages/messaging_integrations/teams_webhook.py @@ -54,8 +54,7 @@ def _build_payload(card: dict) -> dict: def _truncation_notice_item() -> Dict[str, Any]: return { "type": "TextBlock", - "text": "_... Content truncated due to message size limits. " - "View full details in Elementary Cloud._", + "text": "_... Content truncated due to message size limits._", "wrap": True, "isSubtle": True, } @@ -67,8 +66,7 @@ def _minimal_card(card: dict) -> dict: "body": [ { "type": "TextBlock", - "text": "Alert content too large to display in Teams. " - "View full details in Elementary Cloud.", + "text": "Alert content too large to display in Teams.", "wrap": True, "weight": "bolder", }