feat(email): delivery you can see: each alert's email in history, outgoing email on Settings -> System (#5509)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m10s
CI / unit (push) Successful in 1m25s
CI / publish (push) Successful in 1m1s

#5509 "Delivery you can see: each alert's email state in history, and
outgoing email on Settings -> System" (milestone 501).

- Alerts -> History: an alert's email reads from the outbox (#5506):
  "gathering" with when it sends, "sending", "retrying" with the attempt,
  next try and the server's last answer, "sent", or "failed" naming each
  reader it didn't reach and why (outbox.delivery / email_states). Events
  from before the outbox keep what they recorded. The Notified column
  shows once an alert has a queued email.
- Settings -> System gets "Outgoing email": every email of the last 7
  days not yet sent, failures first, then the longest retrying, with
  recipient, kind, state and last error, and a count of what was sent.
  Failures and anything still retrying after 5 minutes lead the page's
  verdict (outbox.outgoing).
- get_alert_history (MCP) adds `email`: the same delivery state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 16:12:40 -04:00
co-authored by Claude Opus 5.5
parent 249d22e41f
commit defad2e98b
14 changed files with 375 additions and 26 deletions
+13 -4
View File
@@ -48,11 +48,20 @@
<span class="badge badge-yellow" title="A maintenance window muted this notification">muted · {{ ep.muted_by }}</span>
{% if ep.released %}<div class="cell-note">notified when the window ended</div>{% endif %}
{% endif %}
{% for label, sent, error in ep.deliveries %}
{# Each channel's outcome; an email through the outbox (#5509) also
says when it sends, or how its retries stand, or why it failed. #}
{% for d in ep.deliveries %}
<div>
{% if sent %}<span class="badge badge-green">{{ label }} sent</span>
{% else %}<span class="badge badge-red">{{ label }} failed</span>
{% if error %}<div class="cell-note" style="max-width:28rem;overflow-wrap:anywhere;">{{ error }}</div>{% endif %}
{% if d.state == "sent" %}<span class="badge badge-green">{{ d.label }} sent</span>
{%- if d.at %} <span class="tone-dim">{{ d.at|when }}</span>{% endif %}
{% elif d.state == "held" %}<span class="badge badge-yellow">{{ d.label }} gathering</span>
<div class="cell-note">waiting for other alerts; sends {{ d.at|when }}</div>
{% elif d.state == "sending" %}<span class="badge badge-yellow">{{ d.label }} sending</span>
{% elif d.state == "retrying" %}<span class="badge badge-yellow">{{ d.label }} retrying</span>
<div class="cell-note" style="max-width:28rem;overflow-wrap:anywhere;">attempt {{ d.attempts }}
{%- if d.at %}, next {{ d.at|when }}{% endif %}{% if d.error %}: {{ d.error }}{% endif %}</div>
{% else %}<span class="badge badge-red">{{ d.label }} failed</span>
{% if d.error %}<div class="cell-note" style="max-width:28rem;overflow-wrap:anywhere;">{{ d.error }}</div>{% endif %}
{% endif %}
</div>
{% endfor %}