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.js CHANGED
@@ -299,15 +299,49 @@ var init_webhook_tolerance = __esm({
299
299
  // src/server/providers/sendgrid.ts
300
300
  var sendgrid_exports = {};
301
301
  __export(sendgrid_exports, {
302
- SendGridProvider: () => SendGridProvider
302
+ SendGridProvider: () => SendGridProvider,
303
+ normalizeSendGridMessageId: () => normalizeSendGridMessageId,
304
+ normalizeWebhookVerificationKey: () => normalizeWebhookVerificationKey
303
305
  });
306
+ function normalizeWebhookVerificationKey(input) {
307
+ let key = String(input ?? "").trim().replace(/^["']|["']$/g, "");
308
+ if (key.includes("\\n")) key = key.replace(/\\n/g, "\n");
309
+ let pem;
310
+ if (key.includes(PEM_HEADER)) {
311
+ pem = key;
312
+ } else {
313
+ const b64 = key.replace(/\s+/g, "");
314
+ if (!b64 || !/^[A-Za-z0-9+/=]+$/.test(b64)) {
315
+ throw new Error(
316
+ "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."
317
+ );
318
+ }
319
+ pem = `${PEM_HEADER}
320
+ ${b64.match(/.{1,64}/g).join("\n")}
321
+ ${PEM_FOOTER}
322
+ `;
323
+ }
324
+ try {
325
+ crypto2.createPublicKey(pem);
326
+ } catch (err) {
327
+ throw new Error(
328
+ `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.`
329
+ );
330
+ }
331
+ return pem;
332
+ }
333
+ function normalizeSendGridMessageId(raw) {
334
+ const id = String(raw ?? "");
335
+ const marker = /\.(?:filter|recvd)\S*$/i.exec(id);
336
+ return marker ? id.slice(0, marker.index) : id;
337
+ }
304
338
  function normalizeSendGridEvent(e) {
305
339
  const type = mapEventType(e.event);
306
340
  if (!type) return null;
307
341
  return {
308
342
  type,
309
343
  providerEventId: String(e.sg_event_id ?? e["smtp-id"] ?? `${e.event}-${e.timestamp}-${e.email}`),
310
- providerMessageId: String(e.sg_message_id ?? e["smtp-id"] ?? ""),
344
+ providerMessageId: e.sg_message_id != null ? normalizeSendGridMessageId(e.sg_message_id) : String(e["smtp-id"] ?? ""),
311
345
  email: String(e.email ?? "").toLowerCase(),
312
346
  occurredAt: new Date(Number(e.timestamp) * 1e3),
313
347
  details: {
@@ -339,24 +373,29 @@ function mapEventType(sgEvent) {
339
373
  return null;
340
374
  }
341
375
  }
342
- var SG_SIG_HEADER, SG_TS_HEADER, SendGridProvider;
376
+ var SG_SIG_HEADER, SG_TS_HEADER, PEM_HEADER, PEM_FOOTER, SendGridProvider;
343
377
  var init_sendgrid = __esm({
344
378
  "src/server/providers/sendgrid.ts"() {
345
379
  init_webhook_tolerance();
346
380
  SG_SIG_HEADER = "x-twilio-email-event-webhook-signature";
347
381
  SG_TS_HEADER = "x-twilio-email-event-webhook-timestamp";
382
+ PEM_HEADER = "-----BEGIN PUBLIC KEY-----";
383
+ PEM_FOOTER = "-----END PUBLIC KEY-----";
348
384
  SendGridProvider = class {
349
385
  constructor(opts) {
350
386
  this.opts = opts;
351
387
  sgMail.setApiKey(opts.apiKey);
352
388
  this.sendRatePerSecond = opts.sendRatePerSecond ?? 10;
353
389
  this.webhookToleranceSeconds = resolveWebhookToleranceSeconds(opts.webhookToleranceSeconds);
390
+ this.verificationKeyPem = opts.webhookVerificationKey ? normalizeWebhookVerificationKey(opts.webhookVerificationKey) : null;
354
391
  }
355
392
  opts;
356
393
  name = "sendgrid";
357
394
  sendRatePerSecond;
358
395
  /** Resolved replay window in seconds; `0` means the check is disabled. */
359
396
  webhookToleranceSeconds;
397
+ /** The verification key as PEM, whatever shape it was configured in. */
398
+ verificationKeyPem;
360
399
  async send(args) {
361
400
  const msg = {
362
401
  to: args.to,
@@ -388,7 +427,7 @@ var init_sendgrid = __esm({
388
427
  };
389
428
  }
390
429
  async verifyWebhook(rawBody, headers) {
391
- if (!this.opts.webhookVerificationKey) return false;
430
+ if (!this.verificationKeyPem) return false;
392
431
  const sig = headers[SG_SIG_HEADER];
393
432
  const ts = headers[SG_TS_HEADER];
394
433
  if (!sig || !ts) return false;
@@ -396,7 +435,7 @@ var init_sendgrid = __esm({
396
435
  try {
397
436
  const verifier = crypto2.createVerify("sha256");
398
437
  verifier.update(payload);
399
- if (!verifier.verify(this.opts.webhookVerificationKey, sig, "base64")) return false;
438
+ if (!verifier.verify(this.verificationKeyPem, sig, "base64")) return false;
400
439
  } catch {
401
440
  return false;
402
441
  }
@@ -15429,11 +15468,16 @@ async function processWebhookBacklog(ctx, opts = {}) {
15429
15468
  }
15430
15469
  }
15431
15470
  }
15471
+ async function findSendForEvent(event, ctx) {
15472
+ if (event.providerMessageId) {
15473
+ const byId = await ctx.collections.sends.findOne({ providerMessageId: event.providerMessageId });
15474
+ if (byId) return byId;
15475
+ }
15476
+ if (!event.email) return null;
15477
+ return ctx.collections.sends.findOne({ emailAtSend: event.email, sentAt: { $ne: null } }, { sort: { sentAt: -1 } });
15478
+ }
15432
15479
  async function applyWebhookEvent(event, ctx) {
15433
- const send = await ctx.collections.sends.findOne(
15434
- event.providerMessageId ? { $or: [{ providerMessageId: event.providerMessageId }, { emailAtSend: event.email }] } : { emailAtSend: event.email },
15435
- { sort: { queuedAt: -1 } }
15436
- );
15480
+ const send = await findSendForEvent(event, ctx);
15437
15481
  switch (event.type) {
15438
15482
  case "delivered":
15439
15483
  if (send) {