improve: propagate send errors back to agent for retry on SMTP failures

linuztx committed Mar 16, 2026 at 09:08 UTC 3dcffe662c36f8702c8968909ecb46ad0323742a
5 files changed +32 -12
plugins/_email_integration/extensions/python/process_chain_end/_55_email_reply.py
+11 -2
@@ -3,7 +3,7 @@
3 import asyncio
4 from helpers.extension import Extension
5 from helpers.print_style import PrintStyle
6 -from agent import AgentContext, LoopData
6 +from agent import AgentContext, LoopData, UserMessage
7 from plugins._email_integration.helpers.dispatcher import CTX_EMAIL_HANDLER, CTX_EMAIL_ATTACHMENTS
8
9
@@ -30,7 +30,9 @@ class EmailAutoReply(Extension):
30 self, context: AgentContext, response_text: str, attachments: list[str],
31 ):
32 from plugins._email_integration.helpers.handler import send_email_reply
33 - await send_email_reply(context, response_text, attachments)
33 + error = await send_email_reply(context, response_text, attachments)
34 + if error:
35 + _notify_agent_of_failure(context, error)
36
37
38 # ------------------------------------------------------------------
@@ -46,3 +48,10 @@ def _extract_last_response(context: AgentContext) -> str:
48 if item.type == "response":
49 return item.content or ""
50 return ""
51 +
52 +
53 +def _notify_agent_of_failure(context: AgentContext, error: str):
54 + from plugins._email_integration.helpers.handler import _read_fw
55 + msg = _read_fw("fw.email.send_failed.md", error=error)
56 + context.log.log(type="error", heading="Email send failed", content=error)
57 + context.communicate(UserMessage(message="", system_message=[msg]))
plugins/_email_integration/helpers/handler.py
+4 -5
@@ -333,15 +333,14 @@ async def send_email_reply(
333 context: AgentContext,
334 response_text: str,
335 attachments: list[str] | None = None,
336 -):
336 +) -> str | None:
337 handler_name = context.data.get(disp.CTX_EMAIL_HANDLER)
338 if not handler_name:
339 - return
339 + return "No email handler configured"
340
341 cfg = _get_handler_config(handler_name)
342 if not cfg:
343 - PrintStyle.error(f"Email: handler config not found for '{handler_name}'")
344 - return
343 + return f"Handler config not found for '{handler_name}'"
344
345 sender = context.data.get(disp.CTX_EMAIL_SENDER, "")
346 original_subject = context.data.get(disp.CTX_EMAIL_SUBJECT, "")
@@ -361,7 +360,7 @@ async def send_email_reply(
360 # Read attachment files via RFC (they live in the execution runtime)
361 attachment_data = await _read_attachments_via_rfc(attachments)
362
364 - await send_reply(
363 + return await send_reply(
364 config=smtp_cfg,
365 to=sender,
366 subject=subject,
plugins/_email_integration/helpers/smtp_client.py
+5 -4
@@ -41,7 +41,7 @@ async def send_reply(
41 in_reply_to: str = "",
42 references: str = "",
43 attachments: list[tuple[str, bytes]] | None = None,
44 -) -> bool:
44 +) -> str | None:
45 loop = asyncio.get_event_loop()
46
47 def _sync_send():
@@ -82,7 +82,8 @@ async def send_reply(
82 try:
83 await loop.run_in_executor(None, _sync_send)
84 PrintStyle.success(f"Email sent to {to}: {subject}")
85 - return True
85 + return None
86 except Exception as e:
87 - PrintStyle.error(f"Email send failed: {format_error(e)}")
88 - return False
87 + error = format_error(e)
88 + PrintStyle.error(f"Email send failed: {error}")
89 + return error
plugins/_email_integration/prompts/fw.email.send_failed.md new
+3
@@ -0,0 +1,3 @@
1 +email send failed: {{error}}
2 +previous response was NOT delivered to user
3 +retry without problematic content or inform user via alternative means
plugins/_email_integration/tools/email_update.py
+9 -1
@@ -28,7 +28,15 @@ class EmailUpdate(Tool):
28 attachments = list(self.args.get("attachments", []))
29
30 from plugins._email_integration.helpers.handler import send_email_reply
31 - await send_email_reply(self.agent.context, text, attachments or None)
31 + error = await send_email_reply(self.agent.context, text, attachments or None)
32 +
33 + if error:
34 + return Response(
35 + message=self.agent.read_prompt(
36 + "fw.email.update_error.md", error=error,
37 + ),
38 + break_loop=False,
39 + )
40
41 return Response(
42 message=self.agent.read_prompt("fw.email.update_ok.md"),