mailery 0.16.0 → 0.16.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/testing.cjs CHANGED
@@ -314,15 +314,49 @@ var init_webhook_tolerance = __esm({
314
314
  // src/server/providers/sendgrid.ts
315
315
  var sendgrid_exports = {};
316
316
  __export(sendgrid_exports, {
317
- SendGridProvider: () => SendGridProvider
317
+ SendGridProvider: () => SendGridProvider,
318
+ normalizeSendGridMessageId: () => normalizeSendGridMessageId,
319
+ normalizeWebhookVerificationKey: () => normalizeWebhookVerificationKey
318
320
  });
321
+ function normalizeWebhookVerificationKey(input) {
322
+ let key = String(input ?? "").trim().replace(/^["']|["']$/g, "");
323
+ if (key.includes("\\n")) key = key.replace(/\\n/g, "\n");
324
+ let pem;
325
+ if (key.includes(PEM_HEADER)) {
326
+ pem = key;
327
+ } else {
328
+ const b64 = key.replace(/\s+/g, "");
329
+ if (!b64 || !/^[A-Za-z0-9+/=]+$/.test(b64)) {
330
+ throw new Error(
331
+ "SendGridProvider: webhookVerificationKey is neither a PEM public key nor a base64 SPKI key. Copy the Verification Key from SendGrid \u2192 Settings \u2192 Mail Settings \u2192 Signed Event Webhook."
332
+ );
333
+ }
334
+ pem = `${PEM_HEADER}
335
+ ${b64.match(/.{1,64}/g).join("\n")}
336
+ ${PEM_FOOTER}
337
+ `;
338
+ }
339
+ try {
340
+ crypto2__default.default.createPublicKey(pem);
341
+ } catch (err) {
342
+ throw new Error(
343
+ `SendGridProvider: webhookVerificationKey did not parse as a public key (${String(err?.message ?? err)}). Copy the Verification Key from SendGrid \u2192 Settings \u2192 Mail Settings \u2192 Signed Event Webhook.`
344
+ );
345
+ }
346
+ return pem;
347
+ }
348
+ function normalizeSendGridMessageId(raw) {
349
+ const id = String(raw ?? "");
350
+ const marker = /\.(?:filter|recvd)\S*$/i.exec(id);
351
+ return marker ? id.slice(0, marker.index) : id;
352
+ }
319
353
  function normalizeSendGridEvent(e) {
320
354
  const type = mapEventType(e.event);
321
355
  if (!type) return null;
322
356
  return {
323
357
  type,
324
358
  providerEventId: String(e.sg_event_id ?? e["smtp-id"] ?? `${e.event}-${e.timestamp}-${e.email}`),
325
- providerMessageId: String(e.sg_message_id ?? e["smtp-id"] ?? ""),
359
+ providerMessageId: e.sg_message_id != null ? normalizeSendGridMessageId(e.sg_message_id) : String(e["smtp-id"] ?? ""),
326
360
  email: String(e.email ?? "").toLowerCase(),
327
361
  occurredAt: new Date(Number(e.timestamp) * 1e3),
328
362
  details: {
@@ -354,24 +388,29 @@ function mapEventType(sgEvent) {
354
388
  return null;
355
389
  }
356
390
  }
357
- var SG_SIG_HEADER, SG_TS_HEADER, SendGridProvider;
391
+ var SG_SIG_HEADER, SG_TS_HEADER, PEM_HEADER, PEM_FOOTER, SendGridProvider;
358
392
  var init_sendgrid = __esm({
359
393
  "src/server/providers/sendgrid.ts"() {
360
394
  init_webhook_tolerance();
361
395
  SG_SIG_HEADER = "x-twilio-email-event-webhook-signature";
362
396
  SG_TS_HEADER = "x-twilio-email-event-webhook-timestamp";
397
+ PEM_HEADER = "-----BEGIN PUBLIC KEY-----";
398
+ PEM_FOOTER = "-----END PUBLIC KEY-----";
363
399
  SendGridProvider = class {
364
400
  constructor(opts) {
365
401
  this.opts = opts;
366
402
  sgMail__default.default.setApiKey(opts.apiKey);
367
403
  this.sendRatePerSecond = opts.sendRatePerSecond ?? 10;
368
404
  this.webhookToleranceSeconds = resolveWebhookToleranceSeconds(opts.webhookToleranceSeconds);
405
+ this.verificationKeyPem = opts.webhookVerificationKey ? normalizeWebhookVerificationKey(opts.webhookVerificationKey) : null;
369
406
  }
370
407
  opts;
371
408
  name = "sendgrid";
372
409
  sendRatePerSecond;
373
410
  /** Resolved replay window in seconds; `0` means the check is disabled. */
374
411
  webhookToleranceSeconds;
412
+ /** The verification key as PEM, whatever shape it was configured in. */
413
+ verificationKeyPem;
375
414
  async send(args) {
376
415
  const msg = {
377
416
  to: args.to,
@@ -403,7 +442,7 @@ var init_sendgrid = __esm({
403
442
  };
404
443
  }
405
444
  async verifyWebhook(rawBody, headers) {
406
- if (!this.opts.webhookVerificationKey) return false;
445
+ if (!this.verificationKeyPem) return false;
407
446
  const sig = headers[SG_SIG_HEADER];
408
447
  const ts = headers[SG_TS_HEADER];
409
448
  if (!sig || !ts) return false;
@@ -411,7 +450,7 @@ var init_sendgrid = __esm({
411
450
  try {
412
451
  const verifier = crypto2__default.default.createVerify("sha256");
413
452
  verifier.update(payload);
414
- if (!verifier.verify(this.opts.webhookVerificationKey, sig, "base64")) return false;
453
+ if (!verifier.verify(this.verificationKeyPem, sig, "base64")) return false;
415
454
  } catch {
416
455
  return false;
417
456
  }
@@ -15444,11 +15483,16 @@ async function processWebhookBacklog(ctx, opts = {}) {
15444
15483
  }
15445
15484
  }
15446
15485
  }
15486
+ async function findSendForEvent(event, ctx) {
15487
+ if (event.providerMessageId) {
15488
+ const byId = await ctx.collections.sends.findOne({ providerMessageId: event.providerMessageId });
15489
+ if (byId) return byId;
15490
+ }
15491
+ if (!event.email) return null;
15492
+ return ctx.collections.sends.findOne({ emailAtSend: event.email, sentAt: { $ne: null } }, { sort: { sentAt: -1 } });
15493
+ }
15447
15494
  async function applyWebhookEvent(event, ctx) {
15448
- const send = await ctx.collections.sends.findOne(
15449
- event.providerMessageId ? { $or: [{ providerMessageId: event.providerMessageId }, { emailAtSend: event.email }] } : { emailAtSend: event.email },
15450
- { sort: { queuedAt: -1 } }
15451
- );
15495
+ const send = await findSendForEvent(event, ctx);
15452
15496
  switch (event.type) {
15453
15497
  case "delivered":
15454
15498
  if (send) {