@sendinel/mcp-server 1.0.147 → 1.0.148
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/tools/analytics.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSentRow } from "@sendinel/domain";
|
|
1
2
|
import { readPeriodCampaigns, campaignPeriodFields, filterPlanContacts } from "@sendinel/domain";
|
|
2
3
|
import { isTrueConversionEvent, TRUE_CONVERSION_DEFINITION } from "@sendinel/domain";
|
|
3
4
|
import { percentRate, groupMetricRows, readRevenue, isComplainedRow, isOpenedRow, countPeople, isClickedRow, summarizeEmailRows, countEmailRows, isDeliveredRow, isBouncedRow, fetchAllRows, readAllRows } from "@sendinel/domain";
|
|
@@ -42,7 +43,7 @@ export function computeSenderHealthScore(metrics, trackingEnabled) {
|
|
|
42
43
|
function sendVolumeByDay(rows) {
|
|
43
44
|
const byDay = new Map();
|
|
44
45
|
for (const row of rows) {
|
|
45
|
-
if (!row.sent_at)
|
|
46
|
+
if (!row.sent_at || !isSentRow(row))
|
|
46
47
|
continue;
|
|
47
48
|
const date = new Date(row.sent_at).toISOString().slice(0, 10);
|
|
48
49
|
byDay.set(date, (byDay.get(date) ?? 0) + 1);
|
|
@@ -133,12 +134,12 @@ function distinctEventCount(events, eventType, messageIds) {
|
|
|
133
134
|
.map(event => event.provider_message_id)).size;
|
|
134
135
|
}
|
|
135
136
|
function buildTrueConversionAnalytics(params) {
|
|
136
|
-
const sent = params.logs.
|
|
137
|
+
const sent = countEmailRows(params.logs).sent;
|
|
137
138
|
const delivered = params.logs.filter(isDeliveredRow).length;
|
|
138
139
|
const trueRows = params.attributionRows.filter(row => isTrueConversionEvent(row.conversion_event));
|
|
139
140
|
const campaignDenominators = new Map();
|
|
140
141
|
for (const row of params.logs) {
|
|
141
|
-
if (typeof row.campaign_id !== 'string')
|
|
142
|
+
if (typeof row.campaign_id !== 'string' || !isSentRow(row))
|
|
142
143
|
continue;
|
|
143
144
|
campaignDenominators.set(row.campaign_id, (campaignDenominators.get(row.campaign_id) ?? 0) + 1);
|
|
144
145
|
}
|
|
@@ -815,7 +816,7 @@ export function registerAnalyticsTools(server) {
|
|
|
815
816
|
.eq('site_id', site_id)
|
|
816
817
|
.gte('sent_at', sevenDaysAgo);
|
|
817
818
|
const logs = recentLogs ?? [];
|
|
818
|
-
const totalSent = logs.
|
|
819
|
+
const totalSent = countEmailRows(logs).sent;
|
|
819
820
|
const bounced = logs.filter((l) => isBouncedRow(l)).length;
|
|
820
821
|
const complained = logs.filter(isComplainedRow).length;
|
|
821
822
|
const bounceRate = percentRate(bounced, totalSent, 8) ?? 0;
|
|
@@ -1012,7 +1013,7 @@ export function registerAnalyticsTools(server) {
|
|
|
1012
1013
|
if (firstHalf.length > 0 && secondHalf.length > 0) {
|
|
1013
1014
|
trend.open_rate = `${(parseRate(secondMetrics.open_rate) - parseRate(firstMetrics.open_rate) >= 0 ? '+' : '')}${(parseRate(secondMetrics.open_rate) - parseRate(firstMetrics.open_rate)).toFixed(1)}pp`;
|
|
1014
1015
|
trend.click_rate = `${(parseRate(secondMetrics.click_rate) - parseRate(firstMetrics.click_rate) >= 0 ? '+' : '')}${(parseRate(secondMetrics.click_rate) - parseRate(firstMetrics.click_rate)).toFixed(1)}pp`;
|
|
1015
|
-
trend.volume = `${
|
|
1016
|
+
trend.volume = `${firstMetrics.total_sent} → ${secondMetrics.total_sent} emails`;
|
|
1016
1017
|
}
|
|
1017
1018
|
return j({
|
|
1018
1019
|
period: `Last ${days} days`,
|
|
@@ -1077,7 +1078,7 @@ export function registerAnalyticsTools(server) {
|
|
|
1077
1078
|
.eq('project_id', proj.id)
|
|
1078
1079
|
.gte('sent_at', since);
|
|
1079
1080
|
const rows = logs ?? [];
|
|
1080
|
-
const sent = rows.
|
|
1081
|
+
const sent = countEmailRows(rows).sent;
|
|
1081
1082
|
const delivered = rows.filter(isDeliveredRow).length;
|
|
1082
1083
|
const opened = countPeople(rows, isOpenedRow);
|
|
1083
1084
|
const clicked = countPeople(rows, isClickedRow);
|
package/build/tools/campaigns.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readCampaignMetricGroups, SENT_STATUSES } from "@sendinel/domain";
|
|
1
2
|
import { resolveCampaignReentry, readCampaignPeriods, campaignPeriodFields } from "@sendinel/domain";
|
|
2
3
|
import { CAMPAIGN_BRANCH_CONDITIONS, campaignBranchConditionValue, isContactPropertyConditionList, STEP_PROPERTY_CONDITIONS_ERROR, validateStepPropertyRules } from "@sendinel/domain";
|
|
3
4
|
import { z } from 'zod';
|
|
@@ -39,7 +40,6 @@ const reentryFields = {
|
|
|
39
40
|
reentry_mode: z.enum(['once_ever', 'per_event_period']).optional().describe(REENTRY_DESCRIPTION),
|
|
40
41
|
period_key: z.string().min(1).max(64).nullable().optional().describe('Flat event-data key naming the period (starts with a letter; letters, digits and underscores only). Key alone selects per_event_period; null clears to once ever. ' + REENTRY_DESCRIPTION),
|
|
41
42
|
};
|
|
42
|
-
const SENT_EMAIL_STATUSES = ['sent', 'delivered', 'opened', 'clicked', 'bounced', 'complained', 'unsubscribed'];
|
|
43
43
|
function stripHtml(value) {
|
|
44
44
|
return value
|
|
45
45
|
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
|
@@ -758,7 +758,7 @@ export function registerCampaignTools(server) {
|
|
|
758
758
|
fires_on: buildCampaignFiresOn(row),
|
|
759
759
|
trigger_diagnostics: buildCampaignTriggerDiagnostics(row),
|
|
760
760
|
...campaignPeriodFields(periods, period),
|
|
761
|
-
|
|
761
|
+
total_sent: periods?.totals?.sent ?? (engagement.ok ? engagement.sent.get(row.id) ?? 0 : null),
|
|
762
762
|
unique_opened: periods?.totals?.opened ?? (engagement.ok ? engagement.opened.get(row.id) ?? 0 : null),
|
|
763
763
|
unique_clicked: periods?.totals?.clicked ?? (engagement.ok ? engagement.clicked.get(row.id) ?? 0 : null),
|
|
764
764
|
};
|
|
@@ -1157,7 +1157,7 @@ export function registerCampaignTools(server) {
|
|
|
1157
1157
|
.eq('project_id', projectId)
|
|
1158
1158
|
.eq('campaign_id', campaign_id)
|
|
1159
1159
|
.eq('contact_id', contact_id)
|
|
1160
|
-
.
|
|
1160
|
+
.in('status', SENT_STATUSES),
|
|
1161
1161
|
enrollment.current_step == null
|
|
1162
1162
|
? Promise.resolve({ data: null, error: null })
|
|
1163
1163
|
: db()
|
|
@@ -3746,7 +3746,7 @@ Return ONLY valid JSON with this exact shape:
|
|
|
3746
3746
|
.select('id', { count: 'exact', head: true })
|
|
3747
3747
|
.eq('project_id', projectId)
|
|
3748
3748
|
.eq('campaign_id', campaign_id)
|
|
3749
|
-
.in('status',
|
|
3749
|
+
.in('status', SENT_STATUSES),
|
|
3750
3750
|
db()
|
|
3751
3751
|
.from('email_log')
|
|
3752
3752
|
.select('id', { count: 'exact', head: true })
|
|
@@ -3775,6 +3775,9 @@ Return ONLY valid JSON with this exact shape:
|
|
|
3775
3775
|
const visibilityError = stepsError ?? enrollError ?? sentError ?? queuedEmailError ?? failedSendError ?? queueError ?? dlqError;
|
|
3776
3776
|
if (visibilityError)
|
|
3777
3777
|
return j({ ...dbToolError(visibilityError), code: 'CAMPAIGN_VISIBILITY_QUERY_FAILED' });
|
|
3778
|
+
const stepMetrics = await readCampaignMetricGroups(db(), projectId, (steps ?? []).map((step) => step.id), "step_id");
|
|
3779
|
+
if (!stepMetrics.ok)
|
|
3780
|
+
return j({ ...dbToolError(stepMetrics.error), code: 'CAMPAIGN_VISIBILITY_QUERY_FAILED' });
|
|
3778
3781
|
const periods = await readCampaignPeriods(db(), projectId, campaign);
|
|
3779
3782
|
const outstandingRows = queueRows ?? [];
|
|
3780
3783
|
const pendingOrRetryRows = outstandingRows.filter((row) => row.status !== 'processing');
|
|
@@ -3795,7 +3798,7 @@ Return ONLY valid JSON with this exact shape:
|
|
|
3795
3798
|
total_sent: periods?.totals?.sent ?? sentCount ?? 0,
|
|
3796
3799
|
...(periods?.totals ? { unique_opened: periods.totals.opened, unique_clicked: periods.totals.clicked } : {}),
|
|
3797
3800
|
...campaignPeriodFields(periods, period),
|
|
3798
|
-
steps: steps ?? [],
|
|
3801
|
+
steps: (steps ?? []).map((step) => ({ ...step, total_sent: stepMetrics.sent.get(step.id) ?? 0 })),
|
|
3799
3802
|
active_enrollments: enrollCount ?? 0,
|
|
3800
3803
|
send_incomplete: queuedCount > 0 || failedCount > 0 || outstandingRows.length > 0 || unresolvedCount > 0,
|
|
3801
3804
|
queued_email_count: queuedCount,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendinel/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.148",
|
|
4
4
|
"description": "AI-controlled email operations control plane for campaigns, contacts, segments, analytics, and multi-provider sending via MCP.",
|
|
5
5
|
"mcpName": "ai.sendinel/mcp-server",
|
|
6
6
|
"type": "module",
|