mailery 0.3.0 → 0.3.2
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/dist/admin/spa/index-B1Mp8lSz.js +41 -0
- package/dist/admin/spa/index-B1Mp8lSz.js.map +1 -0
- package/dist/admin/spa/index.html +1 -1
- package/dist/admin/spa/{template-editor-BuYSO1FP.js → template-editor-dMzrGcva.js} +66 -66
- package/dist/admin/spa/{template-editor-BuYSO1FP.js.map → template-editor-dMzrGcva.js.map} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +130 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +130 -18
- package/dist/index.js.map +1 -1
- package/dist/{null-DaisDvB_.d.cts → null-CGaNDDlQ.d.cts} +4 -0
- package/dist/{null-DaisDvB_.d.ts → null-CGaNDDlQ.d.ts} +4 -0
- package/dist/testing.cjs +5 -0
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +5 -0
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/dist/admin/spa/index-csgOs39s.js +0 -41
- package/dist/admin/spa/index-csgOs39s.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, c as Mailer, T as TemplateDoc, d as SuppressionScope, F as FlowStep, e as SegmentFilter, P as Predicate } from './null-
|
|
2
|
-
export { f as AuditLogDoc, B as BroadcastDoc, g as BroadcastStatus, h as CircuitBreakerThresholds, i as Collections, j as ContactTagDoc, E as EventDoc, k as FlowDoc, l as FlowGoal, m as FlowRunDoc, n as FlowRunStatus, o as FlowVersionDoc, H as HealthDoc, p as HealthStatus, L as LeadDoc, q as MailerConfig, r as NullProvider, O as OutboxDoc, s as RedisOptions, t as SegmentDefinition, u as SendDoc, v as SendStatus, w as SenderDomainConfig, x as SenderDomainRegistry, y as SenderDomainValidation, z as SubscriptionDoc, D as SubscriptionStatus, G as SuppressionDoc, I as SuppressionReason, J as TemplateKind, K as TemplateVersionDoc, W as WebhookEventDoc, Q as ensureIndexes, U as getCollections, V as validateSenderDomain } from './null-
|
|
1
|
+
import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, c as Mailer, T as TemplateDoc, d as SuppressionScope, F as FlowStep, e as SegmentFilter, P as Predicate } from './null-CGaNDDlQ.cjs';
|
|
2
|
+
export { f as AuditLogDoc, B as BroadcastDoc, g as BroadcastStatus, h as CircuitBreakerThresholds, i as Collections, j as ContactTagDoc, E as EventDoc, k as FlowDoc, l as FlowGoal, m as FlowRunDoc, n as FlowRunStatus, o as FlowVersionDoc, H as HealthDoc, p as HealthStatus, L as LeadDoc, q as MailerConfig, r as NullProvider, O as OutboxDoc, s as RedisOptions, t as SegmentDefinition, u as SendDoc, v as SendStatus, w as SenderDomainConfig, x as SenderDomainRegistry, y as SenderDomainValidation, z as SubscriptionDoc, D as SubscriptionStatus, G as SuppressionDoc, I as SuppressionReason, J as TemplateKind, K as TemplateVersionDoc, W as WebhookEventDoc, Q as ensureIndexes, U as getCollections, V as validateSenderDomain } from './null-CGaNDDlQ.cjs';
|
|
3
3
|
import { ObjectId, Db, Filter } from 'mongodb';
|
|
4
4
|
import { Request, Router } from 'express';
|
|
5
5
|
import Handlebars from 'handlebars';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, c as Mailer, T as TemplateDoc, d as SuppressionScope, F as FlowStep, e as SegmentFilter, P as Predicate } from './null-
|
|
2
|
-
export { f as AuditLogDoc, B as BroadcastDoc, g as BroadcastStatus, h as CircuitBreakerThresholds, i as Collections, j as ContactTagDoc, E as EventDoc, k as FlowDoc, l as FlowGoal, m as FlowRunDoc, n as FlowRunStatus, o as FlowVersionDoc, H as HealthDoc, p as HealthStatus, L as LeadDoc, q as MailerConfig, r as NullProvider, O as OutboxDoc, s as RedisOptions, t as SegmentDefinition, u as SendDoc, v as SendStatus, w as SenderDomainConfig, x as SenderDomainRegistry, y as SenderDomainValidation, z as SubscriptionDoc, D as SubscriptionStatus, G as SuppressionDoc, I as SuppressionReason, J as TemplateKind, K as TemplateVersionDoc, W as WebhookEventDoc, Q as ensureIndexes, U as getCollections, V as validateSenderDomain } from './null-
|
|
1
|
+
import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, c as Mailer, T as TemplateDoc, d as SuppressionScope, F as FlowStep, e as SegmentFilter, P as Predicate } from './null-CGaNDDlQ.js';
|
|
2
|
+
export { f as AuditLogDoc, B as BroadcastDoc, g as BroadcastStatus, h as CircuitBreakerThresholds, i as Collections, j as ContactTagDoc, E as EventDoc, k as FlowDoc, l as FlowGoal, m as FlowRunDoc, n as FlowRunStatus, o as FlowVersionDoc, H as HealthDoc, p as HealthStatus, L as LeadDoc, q as MailerConfig, r as NullProvider, O as OutboxDoc, s as RedisOptions, t as SegmentDefinition, u as SendDoc, v as SendStatus, w as SenderDomainConfig, x as SenderDomainRegistry, y as SenderDomainValidation, z as SubscriptionDoc, D as SubscriptionStatus, G as SuppressionDoc, I as SuppressionReason, J as TemplateKind, K as TemplateVersionDoc, W as WebhookEventDoc, Q as ensureIndexes, U as getCollections, V as validateSenderDomain } from './null-CGaNDDlQ.js';
|
|
3
3
|
import { ObjectId, Db, Filter } from 'mongodb';
|
|
4
4
|
import { Request, Router } from 'express';
|
|
5
5
|
import Handlebars from 'handlebars';
|
package/dist/index.js
CHANGED
|
@@ -817,6 +817,11 @@ function adaptBullQueue(q) {
|
|
|
817
817
|
return {
|
|
818
818
|
add: (name, data, opts) => q.add(name, data, opts),
|
|
819
819
|
getWaitingCount: () => q.getWaitingCount(),
|
|
820
|
+
getInFlightCount: async () => {
|
|
821
|
+
const [active, waiting] = await Promise.all([q.getActiveCount(), q.getWaitingCount()]);
|
|
822
|
+
return active + waiting;
|
|
823
|
+
},
|
|
824
|
+
getDelayedCount: () => q.getDelayedCount(),
|
|
820
825
|
close: () => q.close()
|
|
821
826
|
};
|
|
822
827
|
}
|
|
@@ -3247,43 +3252,121 @@ function createAdminRouter(mailer, opts = {}) {
|
|
|
3247
3252
|
function apiRouter(mailer) {
|
|
3248
3253
|
const r = Router();
|
|
3249
3254
|
const c = mailer.collections;
|
|
3250
|
-
r.get(
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3255
|
+
r.get(
|
|
3256
|
+
"/me",
|
|
3257
|
+
asyncHandler(async (req, res) => {
|
|
3258
|
+
const [flows, templates, broadcasts, contacts, suppressions, health] = await Promise.all([
|
|
3259
|
+
c.flows.estimatedDocumentCount(),
|
|
3260
|
+
c.templates.estimatedDocumentCount(),
|
|
3261
|
+
c.broadcasts.estimatedDocumentCount(),
|
|
3262
|
+
c.subscriptions.countDocuments({ status: "subscribed" }),
|
|
3263
|
+
c.suppressions.estimatedDocumentCount(),
|
|
3264
|
+
c.health.findOne({ _id: "singleton" })
|
|
3265
|
+
]);
|
|
3266
|
+
res.json({
|
|
3267
|
+
actor: req.actor,
|
|
3268
|
+
permissions: { canPublish: true, canSendBroadcasts: true, canManageSuppressions: true },
|
|
3269
|
+
counts: { flows, templates, broadcasts, contacts, suppressions },
|
|
3270
|
+
health: { status: health?.status ?? "healthy" },
|
|
3271
|
+
providers: {
|
|
3272
|
+
names: Object.keys(mailer.config.providers),
|
|
3273
|
+
default: mailer.config.defaultProvider
|
|
3274
|
+
}
|
|
3275
|
+
});
|
|
3276
|
+
})
|
|
3277
|
+
);
|
|
3256
3278
|
r.get(
|
|
3257
3279
|
"/dashboard",
|
|
3258
3280
|
asyncHandler(async (_req, res) => {
|
|
3259
|
-
const
|
|
3260
|
-
const
|
|
3281
|
+
const now = Date.now();
|
|
3282
|
+
const HOUR = 60 * 60 * 1e3;
|
|
3283
|
+
const since24h = new Date(now - 24 * HOUR);
|
|
3284
|
+
const since48h = new Date(now - 48 * HOUR);
|
|
3285
|
+
const [
|
|
3286
|
+
sentTotal,
|
|
3287
|
+
deliveredCount,
|
|
3288
|
+
bouncedCount,
|
|
3289
|
+
openedCount,
|
|
3290
|
+
clickedCount,
|
|
3291
|
+
sentPrev,
|
|
3292
|
+
deliveredPrev,
|
|
3293
|
+
openedPrev,
|
|
3294
|
+
clickedPrev,
|
|
3295
|
+
hourly
|
|
3296
|
+
] = await Promise.all([
|
|
3261
3297
|
c.sends.countDocuments({ queuedAt: { $gt: since24h } }),
|
|
3262
3298
|
c.sends.countDocuments({ queuedAt: { $gt: since24h }, status: "delivered" }),
|
|
3263
3299
|
c.sends.countDocuments({ queuedAt: { $gt: since24h }, status: "bounced" }),
|
|
3264
3300
|
c.sends.countDocuments({ queuedAt: { $gt: since24h }, openedAt: { $ne: null } }),
|
|
3265
|
-
c.sends.countDocuments({ queuedAt: { $gt: since24h }, firstClickAt: { $ne: null } })
|
|
3301
|
+
c.sends.countDocuments({ queuedAt: { $gt: since24h }, firstClickAt: { $ne: null } }),
|
|
3302
|
+
c.sends.countDocuments({ queuedAt: { $gt: since48h, $lte: since24h } }),
|
|
3303
|
+
c.sends.countDocuments({ queuedAt: { $gt: since48h, $lte: since24h }, status: "delivered" }),
|
|
3304
|
+
c.sends.countDocuments({ queuedAt: { $gt: since48h, $lte: since24h }, openedAt: { $ne: null } }),
|
|
3305
|
+
c.sends.countDocuments({ queuedAt: { $gt: since48h, $lte: since24h }, firstClickAt: { $ne: null } }),
|
|
3306
|
+
c.sends.aggregate([
|
|
3307
|
+
{ $match: { queuedAt: { $gt: since24h } } },
|
|
3308
|
+
{
|
|
3309
|
+
$project: {
|
|
3310
|
+
hour: {
|
|
3311
|
+
$toInt: {
|
|
3312
|
+
$divide: [{ $subtract: [now, { $toLong: "$queuedAt" }] }, HOUR]
|
|
3313
|
+
}
|
|
3314
|
+
},
|
|
3315
|
+
opened: { $cond: [{ $ifNull: ["$openedAt", false] }, 1, 0] }
|
|
3316
|
+
}
|
|
3317
|
+
},
|
|
3318
|
+
{ $group: { _id: "$hour", sends: { $sum: 1 }, opens: { $sum: "$opened" } } }
|
|
3319
|
+
]).toArray()
|
|
3266
3320
|
]);
|
|
3321
|
+
const sendSeries = new Array(24).fill(0);
|
|
3322
|
+
const openSeries = new Array(24).fill(0);
|
|
3323
|
+
for (const row of hourly) {
|
|
3324
|
+
const idx = 23 - Math.max(0, Math.min(23, row._id));
|
|
3325
|
+
sendSeries[idx] = row.sends;
|
|
3326
|
+
openSeries[idx] = row.opens;
|
|
3327
|
+
}
|
|
3328
|
+
const delta = (cur, prev) => {
|
|
3329
|
+
if (prev === 0) return null;
|
|
3330
|
+
return (cur - prev) / prev;
|
|
3331
|
+
};
|
|
3332
|
+
const rateDelta = (curN, curD, prevN, prevD) => {
|
|
3333
|
+
if (prevD === 0 || curD === 0) return null;
|
|
3334
|
+
return curN / curD - prevN / prevD;
|
|
3335
|
+
};
|
|
3267
3336
|
const health = await c.health.findOne({ _id: "singleton" });
|
|
3268
3337
|
const recentFlows = await c.flows.find({ enabled: true }).limit(5).toArray();
|
|
3269
3338
|
const recentSends = await c.sends.find().sort({ queuedAt: -1 }).limit(6).toArray();
|
|
3270
3339
|
const recentAudit = await c.auditLog.find().sort({ occurredAt: -1 }).limit(5).toArray();
|
|
3340
|
+
const queueCounts = await collectQueueCounts(mailer);
|
|
3341
|
+
const lastSendError = await c.sends.findOne({ status: { $in: ["bounced", "failed"] } }, { sort: { queuedAt: -1 } });
|
|
3342
|
+
const lastSendOk = await c.sends.findOne({ status: "delivered" }, { sort: { queuedAt: -1 } });
|
|
3343
|
+
const providerOk = lastSendOk && lastSendError ? new Date(lastSendOk.queuedAt).getTime() >= new Date(lastSendError.queuedAt).getTime() : lastSendOk ? true : lastSendError ? false : null;
|
|
3271
3344
|
res.json({
|
|
3272
3345
|
kpis: {
|
|
3273
|
-
sends: { value: sentTotal, delta:
|
|
3346
|
+
sends: { value: sentTotal, delta: delta(sentTotal, sentPrev) },
|
|
3274
3347
|
deliveredRate: {
|
|
3275
|
-
value: sentTotal === 0 ?
|
|
3276
|
-
delta:
|
|
3348
|
+
value: sentTotal === 0 ? null : deliveredCount / sentTotal,
|
|
3349
|
+
delta: rateDelta(deliveredCount, sentTotal, deliveredPrev, sentPrev),
|
|
3277
3350
|
bounced: bouncedCount
|
|
3278
3351
|
},
|
|
3279
|
-
openRate: {
|
|
3280
|
-
|
|
3352
|
+
openRate: {
|
|
3353
|
+
value: sentTotal === 0 ? null : openedCount / sentTotal,
|
|
3354
|
+
delta: rateDelta(openedCount, sentTotal, openedPrev, sentPrev),
|
|
3355
|
+
exclBots: false
|
|
3356
|
+
},
|
|
3357
|
+
clickRate: {
|
|
3358
|
+
value: sentTotal === 0 ? null : clickedCount / sentTotal,
|
|
3359
|
+
delta: rateDelta(clickedCount, sentTotal, clickedPrev, sentPrev)
|
|
3360
|
+
}
|
|
3281
3361
|
},
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3362
|
+
series: { hourly: { sends: sendSeries, opens: openSeries } },
|
|
3363
|
+
health: health ? { status: health.status, rates: health.rates } : { status: "healthy", rates: { hardBounceRate: 0, complaintRate: 0, combinedBounceRate: 0, failureRate: 0 } },
|
|
3364
|
+
queue: {
|
|
3365
|
+
inFlight: queueCounts?.inFlight ?? null,
|
|
3366
|
+
delayed: queueCounts?.delayed ?? null,
|
|
3367
|
+
providerOk,
|
|
3368
|
+
providerName: mailer.config.defaultProvider
|
|
3285
3369
|
},
|
|
3286
|
-
queue: { inFlight: 0, delayed: 0, providerOk: true, providerName: mailer.config.defaultProvider },
|
|
3287
3370
|
recentFlows,
|
|
3288
3371
|
recentSends,
|
|
3289
3372
|
recentAudit
|
|
@@ -3460,6 +3543,13 @@ function apiRouter(mailer) {
|
|
|
3460
3543
|
);
|
|
3461
3544
|
})
|
|
3462
3545
|
);
|
|
3546
|
+
r.get(
|
|
3547
|
+
"/health/trips",
|
|
3548
|
+
asyncHandler(async (_req, res) => {
|
|
3549
|
+
const rows = await c.auditLog.find({ action: { $in: ["health.trip", "health.resume"] } }).sort({ occurredAt: -1 }).limit(50).toArray();
|
|
3550
|
+
res.json(rows);
|
|
3551
|
+
})
|
|
3552
|
+
);
|
|
3463
3553
|
r.post(
|
|
3464
3554
|
"/health/resume",
|
|
3465
3555
|
asyncHandler(async (req, res) => {
|
|
@@ -3983,6 +4073,28 @@ function asyncHandler(fn) {
|
|
|
3983
4073
|
fn(req, res, next).catch(next);
|
|
3984
4074
|
};
|
|
3985
4075
|
}
|
|
4076
|
+
async function collectQueueCounts(mailer) {
|
|
4077
|
+
const qs = Object.values(mailer.queues);
|
|
4078
|
+
const sum = async (key) => {
|
|
4079
|
+
let total = 0;
|
|
4080
|
+
let supported = false;
|
|
4081
|
+
for (const q of qs) {
|
|
4082
|
+
const fn = q[key];
|
|
4083
|
+
if (!fn) continue;
|
|
4084
|
+
try {
|
|
4085
|
+
const v = await fn.call(q);
|
|
4086
|
+
if (v == null) continue;
|
|
4087
|
+
total += v;
|
|
4088
|
+
supported = true;
|
|
4089
|
+
} catch {
|
|
4090
|
+
return null;
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
return supported ? total : null;
|
|
4094
|
+
};
|
|
4095
|
+
const [inFlight, delayed] = await Promise.all([sum("getInFlightCount"), sum("getDelayedCount")]);
|
|
4096
|
+
return { inFlight, delayed };
|
|
4097
|
+
}
|
|
3986
4098
|
var PIXEL = Buffer.from(
|
|
3987
4099
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
|
|
3988
4100
|
"base64"
|