mailery 0.16.6 → 0.16.7
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/index.cjs +95 -14
- 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 +95 -14
- package/dist/index.js.map +1 -1
- package/dist/{null-CDlseQxO.d.cts → null-oKPS74Gr.d.cts} +43 -0
- package/dist/{null-CDlseQxO.d.ts → null-oKPS74Gr.d.ts} +43 -0
- package/dist/testing.cjs +50 -0
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +50 -0
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
|
@@ -316,6 +316,23 @@ declare const unsubscribeInputSchema: z.ZodObject<{
|
|
|
316
316
|
notes: z.ZodOptional<z.ZodString>;
|
|
317
317
|
}, z.core.$strip>;
|
|
318
318
|
type UnsubscribeInput = z.infer<typeof unsubscribeInputSchema>;
|
|
319
|
+
/**
|
|
320
|
+
* An explicit opt-in from someone who previously unsubscribed. Clears only the
|
|
321
|
+
* suppression an unsubscribe wrote (`reason: 'unsubscribed'`) — a bounce or
|
|
322
|
+
* complaint row is deliverability, not preference, and survives.
|
|
323
|
+
*/
|
|
324
|
+
declare const resubscribeInputSchema: z.ZodObject<{
|
|
325
|
+
externalId: z.ZodString;
|
|
326
|
+
scope: z.ZodDefault<z.ZodEnum<{
|
|
327
|
+
all: "all";
|
|
328
|
+
marketing: "marketing";
|
|
329
|
+
}>>;
|
|
330
|
+
source: z.ZodString;
|
|
331
|
+
consentTimestamp: z.ZodOptional<z.ZodDate>;
|
|
332
|
+
consentIp: z.ZodOptional<z.ZodString>;
|
|
333
|
+
consentUserAgent: z.ZodOptional<z.ZodString>;
|
|
334
|
+
}, z.core.$strip>;
|
|
335
|
+
type ResubscribeInput = z.input<typeof resubscribeInputSchema>;
|
|
319
336
|
declare const suppressInputSchema: z.ZodObject<{
|
|
320
337
|
email: z.ZodString;
|
|
321
338
|
scope: z.ZodEnum<{
|
|
@@ -727,6 +744,14 @@ interface MailerConfig {
|
|
|
727
744
|
* Domains are case-insensitive. If unset, no enforcement happens.
|
|
728
745
|
*/
|
|
729
746
|
senderDomains?: SenderDomainRegistry;
|
|
747
|
+
/**
|
|
748
|
+
* Hosts the linter treats as your own for the `offdomain_links` warning,
|
|
749
|
+
* on top of the From domain and `publicUrl`'s host. Set it when the product
|
|
750
|
+
* lives on a different domain than the sending identity (`app.example.com`
|
|
751
|
+
* mailed from `hello@example-mail.com`), so a body that links to the
|
|
752
|
+
* product is not reported as pointing away from the sender.
|
|
753
|
+
*/
|
|
754
|
+
linkDomains?: string[];
|
|
730
755
|
requireDoubleOptIn?: boolean;
|
|
731
756
|
unsubscribeTokenLifetimeDays?: number;
|
|
732
757
|
transactionalRespectUnsubscribe?: boolean;
|
|
@@ -1570,6 +1595,24 @@ declare class Mailer {
|
|
|
1570
1595
|
fire(eventName: string, externalId: string, properties?: Record<string, unknown>, dedupeKey?: string): Promise<void>;
|
|
1571
1596
|
fireFromSession(session: ClientSession, eventName: string, externalId: string, properties?: Record<string, unknown>, dedupeKey?: string): Promise<void>;
|
|
1572
1597
|
upsertSubscription(input: UpsertSubscriptionInput): Promise<void>;
|
|
1598
|
+
/**
|
|
1599
|
+
* An explicit opt-in from a contact who unsubscribed before.
|
|
1600
|
+
*
|
|
1601
|
+
* `upsertSubscription` alone is not enough: an unsubscribe also writes a
|
|
1602
|
+
* `mailer_suppressions` row, and the suppression check runs at enqueue
|
|
1603
|
+
* time regardless of subscription status — so a contact re-subscribed
|
|
1604
|
+
* through `upsertSubscription` reads as subscribed while every send comes
|
|
1605
|
+
* back `suppressed`. This clears the opt-out rows (only those: a bounce or
|
|
1606
|
+
* complaint is not the contact's to reverse), then upserts the subscription
|
|
1607
|
+
* through the normal path, double opt-in included.
|
|
1608
|
+
*
|
|
1609
|
+
* Deliberately a separate method rather than a side effect of
|
|
1610
|
+
* `upsertSubscription`, so a backfill or a model hook that re-upserts every
|
|
1611
|
+
* account cannot silently resurrect addresses that opted out.
|
|
1612
|
+
*/
|
|
1613
|
+
resubscribe(input: ResubscribeInput): Promise<{
|
|
1614
|
+
removedSuppressions: number;
|
|
1615
|
+
}>;
|
|
1573
1616
|
/**
|
|
1574
1617
|
* The writes live in `server/unsubscribe.ts` so the pending-unsubscribe
|
|
1575
1618
|
* drain (INVARIANT 8) replays a journaled opt-out through exactly this path
|
|
@@ -316,6 +316,23 @@ declare const unsubscribeInputSchema: z.ZodObject<{
|
|
|
316
316
|
notes: z.ZodOptional<z.ZodString>;
|
|
317
317
|
}, z.core.$strip>;
|
|
318
318
|
type UnsubscribeInput = z.infer<typeof unsubscribeInputSchema>;
|
|
319
|
+
/**
|
|
320
|
+
* An explicit opt-in from someone who previously unsubscribed. Clears only the
|
|
321
|
+
* suppression an unsubscribe wrote (`reason: 'unsubscribed'`) — a bounce or
|
|
322
|
+
* complaint row is deliverability, not preference, and survives.
|
|
323
|
+
*/
|
|
324
|
+
declare const resubscribeInputSchema: z.ZodObject<{
|
|
325
|
+
externalId: z.ZodString;
|
|
326
|
+
scope: z.ZodDefault<z.ZodEnum<{
|
|
327
|
+
all: "all";
|
|
328
|
+
marketing: "marketing";
|
|
329
|
+
}>>;
|
|
330
|
+
source: z.ZodString;
|
|
331
|
+
consentTimestamp: z.ZodOptional<z.ZodDate>;
|
|
332
|
+
consentIp: z.ZodOptional<z.ZodString>;
|
|
333
|
+
consentUserAgent: z.ZodOptional<z.ZodString>;
|
|
334
|
+
}, z.core.$strip>;
|
|
335
|
+
type ResubscribeInput = z.input<typeof resubscribeInputSchema>;
|
|
319
336
|
declare const suppressInputSchema: z.ZodObject<{
|
|
320
337
|
email: z.ZodString;
|
|
321
338
|
scope: z.ZodEnum<{
|
|
@@ -727,6 +744,14 @@ interface MailerConfig {
|
|
|
727
744
|
* Domains are case-insensitive. If unset, no enforcement happens.
|
|
728
745
|
*/
|
|
729
746
|
senderDomains?: SenderDomainRegistry;
|
|
747
|
+
/**
|
|
748
|
+
* Hosts the linter treats as your own for the `offdomain_links` warning,
|
|
749
|
+
* on top of the From domain and `publicUrl`'s host. Set it when the product
|
|
750
|
+
* lives on a different domain than the sending identity (`app.example.com`
|
|
751
|
+
* mailed from `hello@example-mail.com`), so a body that links to the
|
|
752
|
+
* product is not reported as pointing away from the sender.
|
|
753
|
+
*/
|
|
754
|
+
linkDomains?: string[];
|
|
730
755
|
requireDoubleOptIn?: boolean;
|
|
731
756
|
unsubscribeTokenLifetimeDays?: number;
|
|
732
757
|
transactionalRespectUnsubscribe?: boolean;
|
|
@@ -1570,6 +1595,24 @@ declare class Mailer {
|
|
|
1570
1595
|
fire(eventName: string, externalId: string, properties?: Record<string, unknown>, dedupeKey?: string): Promise<void>;
|
|
1571
1596
|
fireFromSession(session: ClientSession, eventName: string, externalId: string, properties?: Record<string, unknown>, dedupeKey?: string): Promise<void>;
|
|
1572
1597
|
upsertSubscription(input: UpsertSubscriptionInput): Promise<void>;
|
|
1598
|
+
/**
|
|
1599
|
+
* An explicit opt-in from a contact who unsubscribed before.
|
|
1600
|
+
*
|
|
1601
|
+
* `upsertSubscription` alone is not enough: an unsubscribe also writes a
|
|
1602
|
+
* `mailer_suppressions` row, and the suppression check runs at enqueue
|
|
1603
|
+
* time regardless of subscription status — so a contact re-subscribed
|
|
1604
|
+
* through `upsertSubscription` reads as subscribed while every send comes
|
|
1605
|
+
* back `suppressed`. This clears the opt-out rows (only those: a bounce or
|
|
1606
|
+
* complaint is not the contact's to reverse), then upserts the subscription
|
|
1607
|
+
* through the normal path, double opt-in included.
|
|
1608
|
+
*
|
|
1609
|
+
* Deliberately a separate method rather than a side effect of
|
|
1610
|
+
* `upsertSubscription`, so a backfill or a model hook that re-upserts every
|
|
1611
|
+
* account cannot silently resurrect addresses that opted out.
|
|
1612
|
+
*/
|
|
1613
|
+
resubscribe(input: ResubscribeInput): Promise<{
|
|
1614
|
+
removedSuppressions: number;
|
|
1615
|
+
}>;
|
|
1573
1616
|
/**
|
|
1574
1617
|
* The writes live in `server/unsubscribe.ts` so the pending-unsubscribe
|
|
1575
1618
|
* drain (INVARIANT 8) replays a journaled opt-out through exactly this path
|
package/dist/testing.cjs
CHANGED
|
@@ -14291,6 +14291,15 @@ var unsubscribeInputSchema = zod.z.object({
|
|
|
14291
14291
|
source: zod.z.string().max(256).default("manual"),
|
|
14292
14292
|
notes: zod.z.string().max(1024).optional()
|
|
14293
14293
|
});
|
|
14294
|
+
var resubscribeInputSchema = zod.z.object({
|
|
14295
|
+
externalId: externalIdSchema,
|
|
14296
|
+
/** `marketing` clears marketing + all-scope opt-outs; `all` clears every scope. */
|
|
14297
|
+
scope: zod.z.enum(["marketing", "all"]).default("marketing"),
|
|
14298
|
+
source: zod.z.string().min(1).max(256),
|
|
14299
|
+
consentTimestamp: zod.z.date().optional(),
|
|
14300
|
+
consentIp: zod.z.string().optional(),
|
|
14301
|
+
consentUserAgent: zod.z.string().optional()
|
|
14302
|
+
});
|
|
14294
14303
|
var suppressInputSchema = zod.z.object({
|
|
14295
14304
|
email: emailSchema,
|
|
14296
14305
|
scope: unsubscribeScopeSchema,
|
|
@@ -14788,6 +14797,15 @@ function signDoiToken(payload, secret) {
|
|
|
14788
14797
|
}
|
|
14789
14798
|
|
|
14790
14799
|
// src/server/unsubscribe.ts
|
|
14800
|
+
async function clearUnsubscribeSuppressions(collections, email, scope) {
|
|
14801
|
+
const scopes = scope === "all" ? ["all", "marketing", "transactional"] : ["marketing", "all"];
|
|
14802
|
+
const result = await collections.suppressions.deleteMany({
|
|
14803
|
+
email,
|
|
14804
|
+
reason: "unsubscribed",
|
|
14805
|
+
scope: { $in: scopes }
|
|
14806
|
+
});
|
|
14807
|
+
return result.deletedCount ?? 0;
|
|
14808
|
+
}
|
|
14791
14809
|
async function applyUnsubscribe(collections, input, now = /* @__PURE__ */ new Date()) {
|
|
14792
14810
|
const normalized = input.email;
|
|
14793
14811
|
await collections.suppressions.updateOne(
|
|
@@ -18161,6 +18179,38 @@ var Mailer = class _Mailer {
|
|
|
18161
18179
|
}
|
|
18162
18180
|
}
|
|
18163
18181
|
}
|
|
18182
|
+
/**
|
|
18183
|
+
* An explicit opt-in from a contact who unsubscribed before.
|
|
18184
|
+
*
|
|
18185
|
+
* `upsertSubscription` alone is not enough: an unsubscribe also writes a
|
|
18186
|
+
* `mailer_suppressions` row, and the suppression check runs at enqueue
|
|
18187
|
+
* time regardless of subscription status — so a contact re-subscribed
|
|
18188
|
+
* through `upsertSubscription` reads as subscribed while every send comes
|
|
18189
|
+
* back `suppressed`. This clears the opt-out rows (only those: a bounce or
|
|
18190
|
+
* complaint is not the contact's to reverse), then upserts the subscription
|
|
18191
|
+
* through the normal path, double opt-in included.
|
|
18192
|
+
*
|
|
18193
|
+
* Deliberately a separate method rather than a side effect of
|
|
18194
|
+
* `upsertSubscription`, so a backfill or a model hook that re-upserts every
|
|
18195
|
+
* account cannot silently resurrect addresses that opted out.
|
|
18196
|
+
*/
|
|
18197
|
+
async resubscribe(input) {
|
|
18198
|
+
const parsed = resubscribeInputSchema.parse(input);
|
|
18199
|
+
const contact = await this.adapter.getById(parsed.externalId);
|
|
18200
|
+
if (!contact) throw new Error(`adapter has no contact for externalId ${parsed.externalId}`);
|
|
18201
|
+
const removedSuppressions = await clearUnsubscribeSuppressions(this.collections, contact.email, parsed.scope);
|
|
18202
|
+
const { scope: _scope, ...subscription } = parsed;
|
|
18203
|
+
await this.upsertSubscription(subscription);
|
|
18204
|
+
if (removedSuppressions > 0) {
|
|
18205
|
+
await this.audit({
|
|
18206
|
+
actor: `host:${parsed.source}`,
|
|
18207
|
+
action: "contact.resubscribe",
|
|
18208
|
+
resource: { collection: "mailer_suppressions", id: parsed.externalId },
|
|
18209
|
+
diffSummary: `${contact.email}: removed ${removedSuppressions} unsubscribed suppression${removedSuppressions === 1 ? "" : "s"} (${parsed.scope})`
|
|
18210
|
+
});
|
|
18211
|
+
}
|
|
18212
|
+
return { removedSuppressions };
|
|
18213
|
+
}
|
|
18164
18214
|
/**
|
|
18165
18215
|
* The writes live in `server/unsubscribe.ts` so the pending-unsubscribe
|
|
18166
18216
|
* drain (INVARIANT 8) replays a journaled opt-out through exactly this path
|