@hogsend/engine 0.23.0 → 0.23.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hogsend/engine",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
"svix": "^1.95.1",
|
|
41
41
|
"winston": "^3.19.0",
|
|
42
42
|
"zod": "^4.4.3",
|
|
43
|
-
"@hogsend/core": "^0.23.
|
|
44
|
-
"@hogsend/db": "^0.23.
|
|
45
|
-
"@hogsend/email": "^0.23.
|
|
46
|
-
"@hogsend/plugin-posthog": "^0.23.
|
|
47
|
-
"@hogsend/plugin-resend": "^0.23.
|
|
43
|
+
"@hogsend/core": "^0.23.1",
|
|
44
|
+
"@hogsend/db": "^0.23.1",
|
|
45
|
+
"@hogsend/email": "^0.23.1",
|
|
46
|
+
"@hogsend/plugin-posthog": "^0.23.1",
|
|
47
|
+
"@hogsend/plugin-resend": "^0.23.1"
|
|
48
48
|
},
|
|
49
49
|
"optionalDependencies": {
|
|
50
|
-
"@hogsend/plugin-postmark": "^0.23.
|
|
50
|
+
"@hogsend/plugin-postmark": "^0.23.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/node": "^22.15.3",
|
package/src/routes/admin/bulk.ts
CHANGED
|
@@ -144,6 +144,12 @@ const replayRoute = createRoute({
|
|
|
144
144
|
},
|
|
145
145
|
description: "Replay results",
|
|
146
146
|
},
|
|
147
|
+
400: {
|
|
148
|
+
content: {
|
|
149
|
+
"application/json": { schema: errorSchema },
|
|
150
|
+
},
|
|
151
|
+
description: "No replay selection (eventIds or filter) provided",
|
|
152
|
+
},
|
|
147
153
|
},
|
|
148
154
|
});
|
|
149
155
|
|
|
@@ -362,14 +368,26 @@ export const bulkRouter = new OpenAPIHono<AppEnv>()
|
|
|
362
368
|
conditions.push(lte(userEvents.occurredAt, new Date(body.filter.to)));
|
|
363
369
|
}
|
|
364
370
|
|
|
365
|
-
|
|
371
|
+
// Refuse an unscoped replay. With no `eventIds` and no filter the WHERE
|
|
372
|
+
// would collapse to `undefined`, silently re-pushing the most-recent
|
|
373
|
+
// `limit` events back through the full ingestion pipeline (re-triggering
|
|
374
|
+
// journeys, re-evaluating exits). Require an explicit selection.
|
|
375
|
+
if (conditions.length === 0) {
|
|
376
|
+
return c.json(
|
|
377
|
+
{
|
|
378
|
+
error:
|
|
379
|
+
"Replay requires `eventIds` or at least one `filter` field (event, userId, from, to).",
|
|
380
|
+
},
|
|
381
|
+
400,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
366
384
|
|
|
367
385
|
events = await db
|
|
368
386
|
.select()
|
|
369
387
|
.from(userEvents)
|
|
370
|
-
.where(
|
|
388
|
+
.where(and(...conditions))
|
|
371
389
|
.orderBy(desc(userEvents.occurredAt))
|
|
372
|
-
.limit(body.limit
|
|
390
|
+
.limit(body.limit);
|
|
373
391
|
}
|
|
374
392
|
|
|
375
393
|
let replayed = 0;
|
|
@@ -147,6 +147,14 @@ export const preferencesRouter = new OpenAPIHono<AppEnv>()
|
|
|
147
147
|
? {
|
|
148
148
|
suppressed: body.suppressed,
|
|
149
149
|
suppressedAt: body.suppressed ? new Date() : null,
|
|
150
|
+
// Un-suppressing clears the bounce slate. `bounceCount` only
|
|
151
|
+
// drives the auto-suppress threshold (the send-gate keys off
|
|
152
|
+
// `suppressed`/`unsubscribedAll`), so a leftover count would
|
|
153
|
+
// otherwise keep a bounced recipient pinned to the suppression
|
|
154
|
+
// list with no way to remove them.
|
|
155
|
+
...(body.suppressed
|
|
156
|
+
? {}
|
|
157
|
+
: { bounceCount: 0, lastBounceAt: null }),
|
|
150
158
|
}
|
|
151
159
|
: {}),
|
|
152
160
|
...(body.categories !== undefined
|
|
@@ -362,8 +362,19 @@ reportingRouter.get("/sends/export", async (c) => {
|
|
|
362
362
|
);
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
// The export intentionally returns all matching sends, but is hard-capped at
|
|
366
|
+
// MAX_EXPORT_ROWS. Signal when the result was truncated so a caller never
|
|
367
|
+
// mistakes a partial CSV for the complete history.
|
|
368
|
+
const truncated = rows.length >= MAX_EXPORT_ROWS;
|
|
369
|
+
|
|
365
370
|
return c.body(lines.join("\n"), 200, {
|
|
366
371
|
"Content-Type": "text/csv; charset=utf-8",
|
|
367
372
|
"Content-Disposition": 'attachment; filename="email-sends.csv"',
|
|
373
|
+
...(truncated
|
|
374
|
+
? {
|
|
375
|
+
"X-Hogsend-Export-Truncated": "true",
|
|
376
|
+
"X-Hogsend-Export-Limit": String(MAX_EXPORT_ROWS),
|
|
377
|
+
}
|
|
378
|
+
: {}),
|
|
368
379
|
});
|
|
369
380
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { emailPreferences } from "@hogsend/db";
|
|
2
2
|
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
|
3
|
-
import { and, count, desc, eq, gt, type SQL } from "drizzle-orm";
|
|
3
|
+
import { and, count, desc, eq, gt, or, type SQL } from "drizzle-orm";
|
|
4
4
|
import type { AppEnv } from "../../app.js";
|
|
5
5
|
import { serializePrefs } from "../../lib/contacts.js";
|
|
6
6
|
|
|
@@ -8,6 +8,11 @@ import { serializePrefs } from "../../lib/contacts.js";
|
|
|
8
8
|
// `complained` has no dedicated column — a complaint sets `suppressed` without
|
|
9
9
|
// incrementing `bounceCount` (see mailer `handleComplaint`), so we identify it
|
|
10
10
|
// as suppressed-but-not-bounced.
|
|
11
|
+
//
|
|
12
|
+
// IMPORTANT: the `email_preferences` table holds a row for (nearly) every
|
|
13
|
+
// contact, most of whom are NOT suppressed. The "All" view must therefore
|
|
14
|
+
// restrict to recipients suppressed in *some* way — returning `undefined`
|
|
15
|
+
// here would drop the WHERE clause entirely and list every contact.
|
|
11
16
|
function typeFilter(
|
|
12
17
|
type: "bounced" | "unsubscribed" | "complained" | undefined,
|
|
13
18
|
): SQL | undefined {
|
|
@@ -22,7 +27,12 @@ function typeFilter(
|
|
|
22
27
|
eq(emailPreferences.bounceCount, 0),
|
|
23
28
|
);
|
|
24
29
|
default:
|
|
25
|
-
|
|
30
|
+
// "All" = the union of every suppression reason.
|
|
31
|
+
return or(
|
|
32
|
+
eq(emailPreferences.suppressed, true),
|
|
33
|
+
eq(emailPreferences.unsubscribedAll, true),
|
|
34
|
+
gt(emailPreferences.bounceCount, 0),
|
|
35
|
+
);
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
|