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.cjs
CHANGED
|
@@ -831,6 +831,11 @@ function adaptBullQueue(q) {
|
|
|
831
831
|
return {
|
|
832
832
|
add: (name, data, opts) => q.add(name, data, opts),
|
|
833
833
|
getWaitingCount: () => q.getWaitingCount(),
|
|
834
|
+
getInFlightCount: async () => {
|
|
835
|
+
const [active, waiting] = await Promise.all([q.getActiveCount(), q.getWaitingCount()]);
|
|
836
|
+
return active + waiting;
|
|
837
|
+
},
|
|
838
|
+
getDelayedCount: () => q.getDelayedCount(),
|
|
834
839
|
close: () => q.close()
|
|
835
840
|
};
|
|
836
841
|
}
|
|
@@ -3261,43 +3266,121 @@ function createAdminRouter(mailer, opts = {}) {
|
|
|
3261
3266
|
function apiRouter(mailer) {
|
|
3262
3267
|
const r = express.Router();
|
|
3263
3268
|
const c = mailer.collections;
|
|
3264
|
-
r.get(
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3269
|
+
r.get(
|
|
3270
|
+
"/me",
|
|
3271
|
+
asyncHandler(async (req, res) => {
|
|
3272
|
+
const [flows, templates, broadcasts, contacts, suppressions, health] = await Promise.all([
|
|
3273
|
+
c.flows.estimatedDocumentCount(),
|
|
3274
|
+
c.templates.estimatedDocumentCount(),
|
|
3275
|
+
c.broadcasts.estimatedDocumentCount(),
|
|
3276
|
+
c.subscriptions.countDocuments({ status: "subscribed" }),
|
|
3277
|
+
c.suppressions.estimatedDocumentCount(),
|
|
3278
|
+
c.health.findOne({ _id: "singleton" })
|
|
3279
|
+
]);
|
|
3280
|
+
res.json({
|
|
3281
|
+
actor: req.actor,
|
|
3282
|
+
permissions: { canPublish: true, canSendBroadcasts: true, canManageSuppressions: true },
|
|
3283
|
+
counts: { flows, templates, broadcasts, contacts, suppressions },
|
|
3284
|
+
health: { status: health?.status ?? "healthy" },
|
|
3285
|
+
providers: {
|
|
3286
|
+
names: Object.keys(mailer.config.providers),
|
|
3287
|
+
default: mailer.config.defaultProvider
|
|
3288
|
+
}
|
|
3289
|
+
});
|
|
3290
|
+
})
|
|
3291
|
+
);
|
|
3270
3292
|
r.get(
|
|
3271
3293
|
"/dashboard",
|
|
3272
3294
|
asyncHandler(async (_req, res) => {
|
|
3273
|
-
const
|
|
3274
|
-
const
|
|
3295
|
+
const now = Date.now();
|
|
3296
|
+
const HOUR = 60 * 60 * 1e3;
|
|
3297
|
+
const since24h = new Date(now - 24 * HOUR);
|
|
3298
|
+
const since48h = new Date(now - 48 * HOUR);
|
|
3299
|
+
const [
|
|
3300
|
+
sentTotal,
|
|
3301
|
+
deliveredCount,
|
|
3302
|
+
bouncedCount,
|
|
3303
|
+
openedCount,
|
|
3304
|
+
clickedCount,
|
|
3305
|
+
sentPrev,
|
|
3306
|
+
deliveredPrev,
|
|
3307
|
+
openedPrev,
|
|
3308
|
+
clickedPrev,
|
|
3309
|
+
hourly
|
|
3310
|
+
] = await Promise.all([
|
|
3275
3311
|
c.sends.countDocuments({ queuedAt: { $gt: since24h } }),
|
|
3276
3312
|
c.sends.countDocuments({ queuedAt: { $gt: since24h }, status: "delivered" }),
|
|
3277
3313
|
c.sends.countDocuments({ queuedAt: { $gt: since24h }, status: "bounced" }),
|
|
3278
3314
|
c.sends.countDocuments({ queuedAt: { $gt: since24h }, openedAt: { $ne: null } }),
|
|
3279
|
-
c.sends.countDocuments({ queuedAt: { $gt: since24h }, firstClickAt: { $ne: null } })
|
|
3315
|
+
c.sends.countDocuments({ queuedAt: { $gt: since24h }, firstClickAt: { $ne: null } }),
|
|
3316
|
+
c.sends.countDocuments({ queuedAt: { $gt: since48h, $lte: since24h } }),
|
|
3317
|
+
c.sends.countDocuments({ queuedAt: { $gt: since48h, $lte: since24h }, status: "delivered" }),
|
|
3318
|
+
c.sends.countDocuments({ queuedAt: { $gt: since48h, $lte: since24h }, openedAt: { $ne: null } }),
|
|
3319
|
+
c.sends.countDocuments({ queuedAt: { $gt: since48h, $lte: since24h }, firstClickAt: { $ne: null } }),
|
|
3320
|
+
c.sends.aggregate([
|
|
3321
|
+
{ $match: { queuedAt: { $gt: since24h } } },
|
|
3322
|
+
{
|
|
3323
|
+
$project: {
|
|
3324
|
+
hour: {
|
|
3325
|
+
$toInt: {
|
|
3326
|
+
$divide: [{ $subtract: [now, { $toLong: "$queuedAt" }] }, HOUR]
|
|
3327
|
+
}
|
|
3328
|
+
},
|
|
3329
|
+
opened: { $cond: [{ $ifNull: ["$openedAt", false] }, 1, 0] }
|
|
3330
|
+
}
|
|
3331
|
+
},
|
|
3332
|
+
{ $group: { _id: "$hour", sends: { $sum: 1 }, opens: { $sum: "$opened" } } }
|
|
3333
|
+
]).toArray()
|
|
3280
3334
|
]);
|
|
3335
|
+
const sendSeries = new Array(24).fill(0);
|
|
3336
|
+
const openSeries = new Array(24).fill(0);
|
|
3337
|
+
for (const row of hourly) {
|
|
3338
|
+
const idx = 23 - Math.max(0, Math.min(23, row._id));
|
|
3339
|
+
sendSeries[idx] = row.sends;
|
|
3340
|
+
openSeries[idx] = row.opens;
|
|
3341
|
+
}
|
|
3342
|
+
const delta = (cur, prev) => {
|
|
3343
|
+
if (prev === 0) return null;
|
|
3344
|
+
return (cur - prev) / prev;
|
|
3345
|
+
};
|
|
3346
|
+
const rateDelta = (curN, curD, prevN, prevD) => {
|
|
3347
|
+
if (prevD === 0 || curD === 0) return null;
|
|
3348
|
+
return curN / curD - prevN / prevD;
|
|
3349
|
+
};
|
|
3281
3350
|
const health = await c.health.findOne({ _id: "singleton" });
|
|
3282
3351
|
const recentFlows = await c.flows.find({ enabled: true }).limit(5).toArray();
|
|
3283
3352
|
const recentSends = await c.sends.find().sort({ queuedAt: -1 }).limit(6).toArray();
|
|
3284
3353
|
const recentAudit = await c.auditLog.find().sort({ occurredAt: -1 }).limit(5).toArray();
|
|
3354
|
+
const queueCounts = await collectQueueCounts(mailer);
|
|
3355
|
+
const lastSendError = await c.sends.findOne({ status: { $in: ["bounced", "failed"] } }, { sort: { queuedAt: -1 } });
|
|
3356
|
+
const lastSendOk = await c.sends.findOne({ status: "delivered" }, { sort: { queuedAt: -1 } });
|
|
3357
|
+
const providerOk = lastSendOk && lastSendError ? new Date(lastSendOk.queuedAt).getTime() >= new Date(lastSendError.queuedAt).getTime() : lastSendOk ? true : lastSendError ? false : null;
|
|
3285
3358
|
res.json({
|
|
3286
3359
|
kpis: {
|
|
3287
|
-
sends: { value: sentTotal, delta:
|
|
3360
|
+
sends: { value: sentTotal, delta: delta(sentTotal, sentPrev) },
|
|
3288
3361
|
deliveredRate: {
|
|
3289
|
-
value: sentTotal === 0 ?
|
|
3290
|
-
delta:
|
|
3362
|
+
value: sentTotal === 0 ? null : deliveredCount / sentTotal,
|
|
3363
|
+
delta: rateDelta(deliveredCount, sentTotal, deliveredPrev, sentPrev),
|
|
3291
3364
|
bounced: bouncedCount
|
|
3292
3365
|
},
|
|
3293
|
-
openRate: {
|
|
3294
|
-
|
|
3366
|
+
openRate: {
|
|
3367
|
+
value: sentTotal === 0 ? null : openedCount / sentTotal,
|
|
3368
|
+
delta: rateDelta(openedCount, sentTotal, openedPrev, sentPrev),
|
|
3369
|
+
exclBots: false
|
|
3370
|
+
},
|
|
3371
|
+
clickRate: {
|
|
3372
|
+
value: sentTotal === 0 ? null : clickedCount / sentTotal,
|
|
3373
|
+
delta: rateDelta(clickedCount, sentTotal, clickedPrev, sentPrev)
|
|
3374
|
+
}
|
|
3295
3375
|
},
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3376
|
+
series: { hourly: { sends: sendSeries, opens: openSeries } },
|
|
3377
|
+
health: health ? { status: health.status, rates: health.rates } : { status: "healthy", rates: { hardBounceRate: 0, complaintRate: 0, combinedBounceRate: 0, failureRate: 0 } },
|
|
3378
|
+
queue: {
|
|
3379
|
+
inFlight: queueCounts?.inFlight ?? null,
|
|
3380
|
+
delayed: queueCounts?.delayed ?? null,
|
|
3381
|
+
providerOk,
|
|
3382
|
+
providerName: mailer.config.defaultProvider
|
|
3299
3383
|
},
|
|
3300
|
-
queue: { inFlight: 0, delayed: 0, providerOk: true, providerName: mailer.config.defaultProvider },
|
|
3301
3384
|
recentFlows,
|
|
3302
3385
|
recentSends,
|
|
3303
3386
|
recentAudit
|
|
@@ -3474,6 +3557,13 @@ function apiRouter(mailer) {
|
|
|
3474
3557
|
);
|
|
3475
3558
|
})
|
|
3476
3559
|
);
|
|
3560
|
+
r.get(
|
|
3561
|
+
"/health/trips",
|
|
3562
|
+
asyncHandler(async (_req, res) => {
|
|
3563
|
+
const rows = await c.auditLog.find({ action: { $in: ["health.trip", "health.resume"] } }).sort({ occurredAt: -1 }).limit(50).toArray();
|
|
3564
|
+
res.json(rows);
|
|
3565
|
+
})
|
|
3566
|
+
);
|
|
3477
3567
|
r.post(
|
|
3478
3568
|
"/health/resume",
|
|
3479
3569
|
asyncHandler(async (req, res) => {
|
|
@@ -3997,6 +4087,28 @@ function asyncHandler(fn) {
|
|
|
3997
4087
|
fn(req, res, next).catch(next);
|
|
3998
4088
|
};
|
|
3999
4089
|
}
|
|
4090
|
+
async function collectQueueCounts(mailer) {
|
|
4091
|
+
const qs = Object.values(mailer.queues);
|
|
4092
|
+
const sum = async (key) => {
|
|
4093
|
+
let total = 0;
|
|
4094
|
+
let supported = false;
|
|
4095
|
+
for (const q of qs) {
|
|
4096
|
+
const fn = q[key];
|
|
4097
|
+
if (!fn) continue;
|
|
4098
|
+
try {
|
|
4099
|
+
const v = await fn.call(q);
|
|
4100
|
+
if (v == null) continue;
|
|
4101
|
+
total += v;
|
|
4102
|
+
supported = true;
|
|
4103
|
+
} catch {
|
|
4104
|
+
return null;
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
return supported ? total : null;
|
|
4108
|
+
};
|
|
4109
|
+
const [inFlight, delayed] = await Promise.all([sum("getInFlightCount"), sum("getDelayedCount")]);
|
|
4110
|
+
return { inFlight, delayed };
|
|
4111
|
+
}
|
|
4000
4112
|
var PIXEL = Buffer.from(
|
|
4001
4113
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
|
|
4002
4114
|
"base64"
|