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.
@@ -1,6 +1,6 @@
1
1
  import { Db } from 'mongodb';
2
- import { C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, N as NormalizedEvent, F as FlowStep, a3 as FlowTrigger, n as FlowGoal, V as TemplateKind, T as TemplateDoc, f as FlowDoc, D as DeliveryWindow, R as RunnerContext, d as Mailer, a4 as QueueDriverConfig } from './null-CDlseQxO.cjs';
3
- export { t as NullProvider } from './null-CDlseQxO.cjs';
2
+ import { C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, N as NormalizedEvent, F as FlowStep, a3 as FlowTrigger, n as FlowGoal, V as TemplateKind, T as TemplateDoc, f as FlowDoc, D as DeliveryWindow, R as RunnerContext, d as Mailer, a4 as QueueDriverConfig } from './null-oKPS74Gr.cjs';
3
+ export { t as NullProvider } from './null-oKPS74Gr.cjs';
4
4
  import 'zod';
5
5
  import 'handlebars';
6
6
  import 'ioredis';
package/dist/testing.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Db } from 'mongodb';
2
- import { C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, N as NormalizedEvent, F as FlowStep, a3 as FlowTrigger, n as FlowGoal, V as TemplateKind, T as TemplateDoc, f as FlowDoc, D as DeliveryWindow, R as RunnerContext, d as Mailer, a4 as QueueDriverConfig } from './null-CDlseQxO.js';
3
- export { t as NullProvider } from './null-CDlseQxO.js';
2
+ import { C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, N as NormalizedEvent, F as FlowStep, a3 as FlowTrigger, n as FlowGoal, V as TemplateKind, T as TemplateDoc, f as FlowDoc, D as DeliveryWindow, R as RunnerContext, d as Mailer, a4 as QueueDriverConfig } from './null-oKPS74Gr.js';
3
+ export { t as NullProvider } from './null-oKPS74Gr.js';
4
4
  import 'zod';
5
5
  import 'handlebars';
6
6
  import 'ioredis';
package/dist/testing.js CHANGED
@@ -14276,6 +14276,15 @@ var unsubscribeInputSchema = z.object({
14276
14276
  source: z.string().max(256).default("manual"),
14277
14277
  notes: z.string().max(1024).optional()
14278
14278
  });
14279
+ var resubscribeInputSchema = z.object({
14280
+ externalId: externalIdSchema,
14281
+ /** `marketing` clears marketing + all-scope opt-outs; `all` clears every scope. */
14282
+ scope: z.enum(["marketing", "all"]).default("marketing"),
14283
+ source: z.string().min(1).max(256),
14284
+ consentTimestamp: z.date().optional(),
14285
+ consentIp: z.string().optional(),
14286
+ consentUserAgent: z.string().optional()
14287
+ });
14279
14288
  var suppressInputSchema = z.object({
14280
14289
  email: emailSchema,
14281
14290
  scope: unsubscribeScopeSchema,
@@ -14773,6 +14782,15 @@ function signDoiToken(payload, secret) {
14773
14782
  }
14774
14783
 
14775
14784
  // src/server/unsubscribe.ts
14785
+ async function clearUnsubscribeSuppressions(collections, email, scope) {
14786
+ const scopes = scope === "all" ? ["all", "marketing", "transactional"] : ["marketing", "all"];
14787
+ const result = await collections.suppressions.deleteMany({
14788
+ email,
14789
+ reason: "unsubscribed",
14790
+ scope: { $in: scopes }
14791
+ });
14792
+ return result.deletedCount ?? 0;
14793
+ }
14776
14794
  async function applyUnsubscribe(collections, input, now = /* @__PURE__ */ new Date()) {
14777
14795
  const normalized = input.email;
14778
14796
  await collections.suppressions.updateOne(
@@ -18146,6 +18164,38 @@ var Mailer = class _Mailer {
18146
18164
  }
18147
18165
  }
18148
18166
  }
18167
+ /**
18168
+ * An explicit opt-in from a contact who unsubscribed before.
18169
+ *
18170
+ * `upsertSubscription` alone is not enough: an unsubscribe also writes a
18171
+ * `mailer_suppressions` row, and the suppression check runs at enqueue
18172
+ * time regardless of subscription status — so a contact re-subscribed
18173
+ * through `upsertSubscription` reads as subscribed while every send comes
18174
+ * back `suppressed`. This clears the opt-out rows (only those: a bounce or
18175
+ * complaint is not the contact's to reverse), then upserts the subscription
18176
+ * through the normal path, double opt-in included.
18177
+ *
18178
+ * Deliberately a separate method rather than a side effect of
18179
+ * `upsertSubscription`, so a backfill or a model hook that re-upserts every
18180
+ * account cannot silently resurrect addresses that opted out.
18181
+ */
18182
+ async resubscribe(input) {
18183
+ const parsed = resubscribeInputSchema.parse(input);
18184
+ const contact = await this.adapter.getById(parsed.externalId);
18185
+ if (!contact) throw new Error(`adapter has no contact for externalId ${parsed.externalId}`);
18186
+ const removedSuppressions = await clearUnsubscribeSuppressions(this.collections, contact.email, parsed.scope);
18187
+ const { scope: _scope, ...subscription } = parsed;
18188
+ await this.upsertSubscription(subscription);
18189
+ if (removedSuppressions > 0) {
18190
+ await this.audit({
18191
+ actor: `host:${parsed.source}`,
18192
+ action: "contact.resubscribe",
18193
+ resource: { collection: "mailer_suppressions", id: parsed.externalId },
18194
+ diffSummary: `${contact.email}: removed ${removedSuppressions} unsubscribed suppression${removedSuppressions === 1 ? "" : "s"} (${parsed.scope})`
18195
+ });
18196
+ }
18197
+ return { removedSuppressions };
18198
+ }
18149
18199
  /**
18150
18200
  * The writes live in `server/unsubscribe.ts` so the pending-unsubscribe
18151
18201
  * drain (INVARIANT 8) replays a journaled opt-out through exactly this path