Track email opens and clicks on recipient status.
Deploy Beta / unit-tests (push) Successful in 13s
Deploy Beta / docker (push) Successful in 17s
Deploy Beta / deploy-beta (push) Successful in 1m39s

SMTP2GO open/click webhooks now advance Message to opened/clicked, and the campaign engagement chart uses live stats instead of placeholder bars.
This commit is contained in:
2026-08-10 10:24:26 -05:00
parent 8680c082fe
commit f7f3174e1c
8 changed files with 252 additions and 33 deletions
@@ -92,7 +92,8 @@
<p class="hint-block" style="margin-top:0">
Unique recipients: <strong data-stat="opens">{{ stats.opens }}</strong> opened ·
<strong data-stat="clicks">{{ stats.clicks }}</strong> clicked
({{ stats.open_events }} open events / {{ stats.click_events }} click events from SMTP2GO).
(<span data-stat="open_events">{{ stats.open_events }}</span> open events /
<span data-stat="click_events">{{ stats.click_events }}</span> click events from SMTP2GO).
</p>
{% elif campaign.channel == "sms" %}
<p class="hint-block" style="margin-top:0">
@@ -103,12 +104,24 @@
Postcard status updates from PCM Integrations webhooks.
</p>
{% endif %}
<div class="chart-placeholder" aria-hidden="true">
<div class="bar" style="height:55%"></div>
<div class="bar" style="height:70%"></div>
<div class="bar" style="height:40%"></div>
<div class="bar" style="height:30%"></div>
<div class="bar" style="height:20%"></div>
<div class="chart-placeholder" id="engagement-chart" role="img"
aria-label="Campaign engagement chart">
{% for bar in stats.chart_bars %}
<div class="chart-bar-col">
<div class="bar" style="height:{{ bar.pct }}%"
title="{{ bar.label }}: {{ bar.value }}"
data-bar-label="{{ bar.label }}"></div>
<div class="chart-bar-meta">
<span class="chart-bar-value" data-bar-value="{{ bar.label }}">{{ bar.value }}</span>
<span class="chart-bar-label">{{ bar.label }}</span>
</div>
</div>
{% empty %}
<div class="chart-bar-col">
<div class="bar" style="height:12%"></div>
<div class="chart-bar-meta"><span class="chart-bar-label"></span></div>
</div>
{% endfor %}
</div>
</div>
</div>
@@ -217,10 +230,24 @@
badge.className = "badge badge-" + data.status;
}
Object.keys(data.stats || {}).forEach(function (key) {
if (key === "chart_bars") return;
document.querySelectorAll('[data-stat="' + key + '"]').forEach(function (el) {
el.textContent = data.stats[key];
});
});
var chart = document.getElementById("engagement-chart");
if (chart && Array.isArray(data.stats && data.stats.chart_bars)) {
chart.innerHTML = data.stats.chart_bars.map(function (bar) {
return '<div class="chart-bar-col">' +
'<div class="bar" style="height:' + esc(bar.pct) + '%" title="' +
esc(bar.label) + ': ' + esc(bar.value) + '" data-bar-label="' +
esc(bar.label) + '"></div>' +
'<div class="chart-bar-meta">' +
'<span class="chart-bar-value">' + esc(bar.value) + '</span>' +
'<span class="chart-bar-label">' + esc(bar.label) + '</span>' +
'</div></div>';
}).join("");
}
var body = document.getElementById("recipients-body");
if (body && data.messages) {
if (!data.messages.length) {