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/index.cjs +61 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +61 -13
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +53 -9
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +53 -9
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -190,7 +190,15 @@ type WebhookToleranceOption = number | false;
|
|
|
190
190
|
|
|
191
191
|
interface SendGridProviderOptions {
|
|
192
192
|
apiKey: string;
|
|
193
|
-
/**
|
|
193
|
+
/**
|
|
194
|
+
* ECDSA public key from SendGrid → Settings → Mail Settings → Signed Event
|
|
195
|
+
* Webhook. Either form SendGrid hands out is accepted: the single-line
|
|
196
|
+
* base64 string its dashboard shows and its API returns as `public_key`
|
|
197
|
+
* (what `mailery setup-sendgrid` prints), or the same key as a PEM block.
|
|
198
|
+
* A PEM whose newlines were escaped to `\n` for an .env line is unescaped.
|
|
199
|
+
* Anything that does not parse as a public key throws at construction —
|
|
200
|
+
* a key that silently verifies nothing is the failure this replaces.
|
|
201
|
+
*/
|
|
194
202
|
webhookVerificationKey?: string;
|
|
195
203
|
/**
|
|
196
204
|
* Replay window for the signed webhook timestamp, in seconds.
|
|
@@ -213,6 +221,8 @@ declare class SendGridProvider implements MailProvider {
|
|
|
213
221
|
readonly sendRatePerSecond: number;
|
|
214
222
|
/** Resolved replay window in seconds; `0` means the check is disabled. */
|
|
215
223
|
readonly webhookToleranceSeconds: number;
|
|
224
|
+
/** The verification key as PEM, whatever shape it was configured in. */
|
|
225
|
+
private readonly verificationKeyPem;
|
|
216
226
|
constructor(opts: SendGridProviderOptions);
|
|
217
227
|
send(args: SendArgs): Promise<SendResult>;
|
|
218
228
|
verifyWebhook(rawBody: Buffer, headers: Record<string, string>): Promise<boolean>;
|
package/dist/index.d.ts
CHANGED
|
@@ -190,7 +190,15 @@ type WebhookToleranceOption = number | false;
|
|
|
190
190
|
|
|
191
191
|
interface SendGridProviderOptions {
|
|
192
192
|
apiKey: string;
|
|
193
|
-
/**
|
|
193
|
+
/**
|
|
194
|
+
* ECDSA public key from SendGrid → Settings → Mail Settings → Signed Event
|
|
195
|
+
* Webhook. Either form SendGrid hands out is accepted: the single-line
|
|
196
|
+
* base64 string its dashboard shows and its API returns as `public_key`
|
|
197
|
+
* (what `mailery setup-sendgrid` prints), or the same key as a PEM block.
|
|
198
|
+
* A PEM whose newlines were escaped to `\n` for an .env line is unescaped.
|
|
199
|
+
* Anything that does not parse as a public key throws at construction —
|
|
200
|
+
* a key that silently verifies nothing is the failure this replaces.
|
|
201
|
+
*/
|
|
194
202
|
webhookVerificationKey?: string;
|
|
195
203
|
/**
|
|
196
204
|
* Replay window for the signed webhook timestamp, in seconds.
|
|
@@ -213,6 +221,8 @@ declare class SendGridProvider implements MailProvider {
|
|
|
213
221
|
readonly sendRatePerSecond: number;
|
|
214
222
|
/** Resolved replay window in seconds; `0` means the check is disabled. */
|
|
215
223
|
readonly webhookToleranceSeconds: number;
|
|
224
|
+
/** The verification key as PEM, whatever shape it was configured in. */
|
|
225
|
+
private readonly verificationKeyPem;
|
|
216
226
|
constructor(opts: SendGridProviderOptions);
|
|
217
227
|
send(args: SendArgs): Promise<SendResult>;
|
|
218
228
|
verifyWebhook(rawBody: Buffer, headers: Record<string, string>): Promise<boolean>;
|
package/dist/index.js
CHANGED
|
@@ -268,15 +268,49 @@ var init_webhook_tolerance = __esm({
|
|
|
268
268
|
// src/server/providers/sendgrid.ts
|
|
269
269
|
var sendgrid_exports = {};
|
|
270
270
|
__export(sendgrid_exports, {
|
|
271
|
-
SendGridProvider: () => SendGridProvider
|
|
271
|
+
SendGridProvider: () => SendGridProvider,
|
|
272
|
+
normalizeSendGridMessageId: () => normalizeSendGridMessageId,
|
|
273
|
+
normalizeWebhookVerificationKey: () => normalizeWebhookVerificationKey
|
|
272
274
|
});
|
|
275
|
+
function normalizeWebhookVerificationKey(input) {
|
|
276
|
+
let key = String(input ?? "").trim().replace(/^["']|["']$/g, "");
|
|
277
|
+
if (key.includes("\\n")) key = key.replace(/\\n/g, "\n");
|
|
278
|
+
let pem;
|
|
279
|
+
if (key.includes(PEM_HEADER)) {
|
|
280
|
+
pem = key;
|
|
281
|
+
} else {
|
|
282
|
+
const b64 = key.replace(/\s+/g, "");
|
|
283
|
+
if (!b64 || !/^[A-Za-z0-9+/=]+$/.test(b64)) {
|
|
284
|
+
throw new Error(
|
|
285
|
+
"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."
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
pem = `${PEM_HEADER}
|
|
289
|
+
${b64.match(/.{1,64}/g).join("\n")}
|
|
290
|
+
${PEM_FOOTER}
|
|
291
|
+
`;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
crypto2.createPublicKey(pem);
|
|
295
|
+
} catch (err) {
|
|
296
|
+
throw new Error(
|
|
297
|
+
`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.`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
return pem;
|
|
301
|
+
}
|
|
302
|
+
function normalizeSendGridMessageId(raw) {
|
|
303
|
+
const id = String(raw ?? "");
|
|
304
|
+
const marker = /\.(?:filter|recvd)\S*$/i.exec(id);
|
|
305
|
+
return marker ? id.slice(0, marker.index) : id;
|
|
306
|
+
}
|
|
273
307
|
function normalizeSendGridEvent(e) {
|
|
274
308
|
const type = mapEventType(e.event);
|
|
275
309
|
if (!type) return null;
|
|
276
310
|
return {
|
|
277
311
|
type,
|
|
278
312
|
providerEventId: String(e.sg_event_id ?? e["smtp-id"] ?? `${e.event}-${e.timestamp}-${e.email}`),
|
|
279
|
-
providerMessageId:
|
|
313
|
+
providerMessageId: e.sg_message_id != null ? normalizeSendGridMessageId(e.sg_message_id) : String(e["smtp-id"] ?? ""),
|
|
280
314
|
email: String(e.email ?? "").toLowerCase(),
|
|
281
315
|
occurredAt: new Date(Number(e.timestamp) * 1e3),
|
|
282
316
|
details: {
|
|
@@ -308,24 +342,29 @@ function mapEventType(sgEvent) {
|
|
|
308
342
|
return null;
|
|
309
343
|
}
|
|
310
344
|
}
|
|
311
|
-
var SG_SIG_HEADER, SG_TS_HEADER, SendGridProvider;
|
|
345
|
+
var SG_SIG_HEADER, SG_TS_HEADER, PEM_HEADER, PEM_FOOTER, SendGridProvider;
|
|
312
346
|
var init_sendgrid = __esm({
|
|
313
347
|
"src/server/providers/sendgrid.ts"() {
|
|
314
348
|
init_webhook_tolerance();
|
|
315
349
|
SG_SIG_HEADER = "x-twilio-email-event-webhook-signature";
|
|
316
350
|
SG_TS_HEADER = "x-twilio-email-event-webhook-timestamp";
|
|
351
|
+
PEM_HEADER = "-----BEGIN PUBLIC KEY-----";
|
|
352
|
+
PEM_FOOTER = "-----END PUBLIC KEY-----";
|
|
317
353
|
SendGridProvider = class {
|
|
318
354
|
constructor(opts) {
|
|
319
355
|
this.opts = opts;
|
|
320
356
|
sgMail.setApiKey(opts.apiKey);
|
|
321
357
|
this.sendRatePerSecond = opts.sendRatePerSecond ?? 10;
|
|
322
358
|
this.webhookToleranceSeconds = resolveWebhookToleranceSeconds(opts.webhookToleranceSeconds);
|
|
359
|
+
this.verificationKeyPem = opts.webhookVerificationKey ? normalizeWebhookVerificationKey(opts.webhookVerificationKey) : null;
|
|
323
360
|
}
|
|
324
361
|
opts;
|
|
325
362
|
name = "sendgrid";
|
|
326
363
|
sendRatePerSecond;
|
|
327
364
|
/** Resolved replay window in seconds; `0` means the check is disabled. */
|
|
328
365
|
webhookToleranceSeconds;
|
|
366
|
+
/** The verification key as PEM, whatever shape it was configured in. */
|
|
367
|
+
verificationKeyPem;
|
|
329
368
|
async send(args) {
|
|
330
369
|
const msg = {
|
|
331
370
|
to: args.to,
|
|
@@ -357,7 +396,7 @@ var init_sendgrid = __esm({
|
|
|
357
396
|
};
|
|
358
397
|
}
|
|
359
398
|
async verifyWebhook(rawBody, headers) {
|
|
360
|
-
if (!this.
|
|
399
|
+
if (!this.verificationKeyPem) return false;
|
|
361
400
|
const sig = headers[SG_SIG_HEADER];
|
|
362
401
|
const ts = headers[SG_TS_HEADER];
|
|
363
402
|
if (!sig || !ts) return false;
|
|
@@ -365,7 +404,7 @@ var init_sendgrid = __esm({
|
|
|
365
404
|
try {
|
|
366
405
|
const verifier = crypto2.createVerify("sha256");
|
|
367
406
|
verifier.update(payload);
|
|
368
|
-
if (!verifier.verify(this.
|
|
407
|
+
if (!verifier.verify(this.verificationKeyPem, sig, "base64")) return false;
|
|
369
408
|
} catch {
|
|
370
409
|
return false;
|
|
371
410
|
}
|
|
@@ -1708,11 +1747,20 @@ async function processWebhookBacklog(ctx, opts = {}) {
|
|
|
1708
1747
|
}
|
|
1709
1748
|
}
|
|
1710
1749
|
}
|
|
1750
|
+
async function findSendForEvent(event, ctx) {
|
|
1751
|
+
if (event.providerMessageId) {
|
|
1752
|
+
const byId = await ctx.collections.sends.findOne({ providerMessageId: event.providerMessageId });
|
|
1753
|
+
if (byId) return byId;
|
|
1754
|
+
}
|
|
1755
|
+
if (!event.email) return null;
|
|
1756
|
+
return ctx.collections.sends.findOne({ emailAtSend: event.email, sentAt: { $ne: null } }, { sort: { sentAt: -1 } });
|
|
1757
|
+
}
|
|
1758
|
+
function webhookEventsForMessageId(providerMessageId) {
|
|
1759
|
+
const escaped = providerMessageId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1760
|
+
return { $or: [{ providerMessageId }, { providerMessageId: { $regex: `^${escaped}\\.` } }] };
|
|
1761
|
+
}
|
|
1711
1762
|
async function applyWebhookEvent(event, ctx) {
|
|
1712
|
-
const send = await ctx
|
|
1713
|
-
event.providerMessageId ? { $or: [{ providerMessageId: event.providerMessageId }, { emailAtSend: event.email }] } : { emailAtSend: event.email },
|
|
1714
|
-
{ sort: { queuedAt: -1 } }
|
|
1715
|
-
);
|
|
1763
|
+
const send = await findSendForEvent(event, ctx);
|
|
1716
1764
|
switch (event.type) {
|
|
1717
1765
|
case "delivered":
|
|
1718
1766
|
if (send) {
|
|
@@ -6466,7 +6514,7 @@ function createAdminApiRouter(mailer, opts = {}) {
|
|
|
6466
6514
|
if (!ObjectId.isValid(id)) return res.status(400).json({ error: "bad_id" });
|
|
6467
6515
|
const send = await c.sends.findOne({ _id: new ObjectId(id) });
|
|
6468
6516
|
if (!send) return res.status(404).json({ error: "not_found" });
|
|
6469
|
-
const events = send.providerMessageId ? await c.webhookEvents.find(
|
|
6517
|
+
const events = send.providerMessageId ? await c.webhookEvents.find(webhookEventsForMessageId(send.providerMessageId)).sort({ receivedAt: -1 }).limit(100).toArray() : [];
|
|
6470
6518
|
return res.json({ send, webhookEvents: events });
|
|
6471
6519
|
})
|
|
6472
6520
|
);
|
|
@@ -8205,7 +8253,7 @@ function unitToMs2(value, unit) {
|
|
|
8205
8253
|
}
|
|
8206
8254
|
|
|
8207
8255
|
// src/server/api/agent.ts
|
|
8208
|
-
var VERSION = "0.16.
|
|
8256
|
+
var VERSION = "0.16.2" ;
|
|
8209
8257
|
var MIN_AGENT_TOKEN_LENGTH = 24;
|
|
8210
8258
|
function createAgentRouter(mailer, opts) {
|
|
8211
8259
|
if (!opts || !Array.isArray(opts.tokens) || opts.tokens.length === 0) {
|
|
@@ -8408,7 +8456,7 @@ function createAgentRouter(mailer, opts) {
|
|
|
8408
8456
|
if (reached || Date.now() - started >= timeoutMs) break;
|
|
8409
8457
|
await sleep2(1e3);
|
|
8410
8458
|
}
|
|
8411
|
-
const webhookEvents = send.providerMessageId ? await c.webhookEvents.find(
|
|
8459
|
+
const webhookEvents = send.providerMessageId ? await c.webhookEvents.find(webhookEventsForMessageId(send.providerMessageId)).sort({ receivedAt: 1 }).limit(100).toArray() : [];
|
|
8412
8460
|
res.json({ reached, target, waitedMs: Date.now() - started, send: sendSummary(send), webhookEvents });
|
|
8413
8461
|
})
|
|
8414
8462
|
);
|
|
@@ -9905,7 +9953,7 @@ var DEDUPE_POLICIES = [
|
|
|
9905
9953
|
];
|
|
9906
9954
|
|
|
9907
9955
|
// src/server/index.ts
|
|
9908
|
-
var VERSION2 = "0.16.
|
|
9956
|
+
var VERSION2 = "0.16.2" ;
|
|
9909
9957
|
|
|
9910
9958
|
export { DEDUPE_POLICIES, DEFAULT_BOT_UA_RE, FLOW_STEP_KINDS, FlowOperationError, MIN_AGENT_TOKEN_LENGTH, Mailer, MongoContactAdapter, NullProvider, PREDICATE_KINDS, RESERVED_VAR_KEYS, SEGMENT_FILTER_KINDS, SendGridProvider, TRACKING_SIG_LENGTH, VERSION2 as VERSION, applyTracking, applyWebhookEvent, armFlow, compileMailyTemplate, compileTemplate, computeDeliveryTime, createAdminApiRouter, createAdminRouter, createAgentRouter, createPublicRouter, defaultFlowStep, defaultPredicate, defaultSegmentFilter, defineVars, derivePlaintext, disarmFlow, dispatchSend, drainPendingUnsubscribes, ensureIndexes, gateFlow, getCollections, isCanaryGate, predicateKind, processNewlyFiredEventTriggers, processOneRunStep, referencedPaths, renderForContact, renderTemplate, runTick, sendgridInboundParser, sha256Hex, signTrackingToken, signUnsubscribeToken, simulateFlow, stampWatermarkIfNull, sweepStrandedFlowRuns, ungateFlow, validateSenderDomain, varsJsonSchema, verifyTemplate, verifyTrackingToken, verifyUnsubscribeToken };
|
|
9911
9959
|
//# sourceMappingURL=index.js.map
|