diff --git a/docs/operations.md b/docs/operations.md index 2b22e4f..4017675 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -1198,6 +1198,21 @@ destinations, production availability, and measured alert policies remain deployment decisions. In Kubernetes, put the metrics token in `existingSecret`; configure the external scraper to send it as a Bearer token. +The provisioned Grafana overview also shows one-hour transactional-email +attempts grouped only by the fixed application-owned purpose and outcome. It +does not expose recipient addresses, subjects, message bodies, support +references, or user identifiers. The panel is intended to answer which bounded +workflow is creating delivery volume or failures; it is not a user-activity +log. + +Support conversations deliberately do not send one email per staff message. +The requester receives an email for the first staff response and for later +public status changes; additional messages while the status is unchanged stay +in the private support conversation and the in-product notification flow. +Anonymous submissions receive the address-confirmation message before they +enter the operator queue. Operator email alerts are disabled unless +`SUPPORT_OPERATOR_EMAIL_MODE=immediate` is explicitly configured. + ## Production release without pushing the frozen repository The post-submission workflow keeps the public Git repository and diff --git a/ops/observability/grafana/dashboards/who-need-help-overview.json b/ops/observability/grafana/dashboards/who-need-help-overview.json index a9ac15a..988cab6 100644 --- a/ops/observability/grafana/dashboards/who-need-help-overview.json +++ b/ops/observability/grafana/dashboards/who-need-help-overview.json @@ -369,6 +369,48 @@ ], "title": "BEAM scheduler run queue by replica", "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "wnh-prometheus" + }, + "fieldConfig": { + "defaults": { + "decimals": 0, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 40 + }, + "id": 11, + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "editorMode": "code", + "expr": "sum by (kind, status) (increase(who_need_help_email_by_kind_deliveries_total[1h]))", + "legendFormat": "{{kind}} {{status}}", + "range": true, + "refId": "A" + } + ], + "title": "Transactional email attempts by purpose and outcome (1h)", + "type": "timeseries" } ], "refresh": "5s", diff --git a/scripts/observability-run.sh b/scripts/observability-run.sh index 71f4165..f319188 100755 --- a/scripts/observability-run.sh +++ b/scripts/observability-run.sh @@ -549,7 +549,7 @@ curl --fail --silent --show-error \ if ! jq -e ' .dashboard.uid == "wnh-overview" and .meta.provisioned == true and - (.dashboard.panels | length) == 10 + (.dashboard.panels | length) == 11 ' "$output_dir/grafana-dashboard.json" >/dev/null; then echo "The provisioned Grafana dashboard did not match the tracked dashboard." >&2 exit 1 diff --git a/scripts/quality.sh b/scripts/quality.sh index 73e43ee..76c0542 100755 --- a/scripts/quality.sh +++ b/scripts/quality.sh @@ -1561,7 +1561,7 @@ docker run --rm \ "$PYTHON_IMAGE" python -c \ 'import py_compile; py_compile.compile("/src/mock_server.py", cfile="/tmp/mock_server.pyc", doraise=True)' jq --exit-status \ - 'type == "object" and .uid == "wnh-overview" and (.panels | length) == 10' \ + 'type == "object" and .uid == "wnh-overview" and (.panels | length) == 11' \ ops/observability/grafana/dashboards/who-need-help-overview.json \ >/dev/null