mailtruss 0.1.1 → 0.2.0

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.d.ts CHANGED
@@ -82,6 +82,30 @@ export type ClientOptions = {
82
82
  maxRetries?: number;
83
83
  fetch?: typeof fetch;
84
84
  };
85
+ export type EmailEvents = {
86
+ object: "email_events";
87
+ email_id: string;
88
+ status: string;
89
+ diagnosis: {
90
+ title: string;
91
+ explanation: string;
92
+ retry_safe: boolean;
93
+ next_step: string;
94
+ } | null;
95
+ recipients: {
96
+ address: string;
97
+ kind: "to" | "cc" | "bcc";
98
+ status: string;
99
+ }[];
100
+ data: {
101
+ type: string;
102
+ occurred_at: string;
103
+ source: string;
104
+ recipient: string | null;
105
+ meaning: string | null;
106
+ detail: Record<string, unknown>;
107
+ }[];
108
+ };
85
109
  export type RequestOptions = {
86
110
  idempotencyKey?: string;
87
111
  signal?: AbortSignal;
@@ -122,6 +146,8 @@ export declare class MailClient {
122
146
  cancel: (id: string, o?: RequestOptions) => Promise<{
123
147
  id: string;
124
148
  }>;
149
+ /** The delivery timeline with what each event proves, and a plain-language diagnosis when something went wrong. */
150
+ events: (id: string, o?: RequestOptions) => Promise<EmailEvents>;
125
151
  };
126
152
  readonly domains: {
127
153
  create: (p: {
@@ -432,3 +458,11 @@ export declare class MailClient {
432
458
  }>>;
433
459
  };
434
460
  }
461
+ /**
462
+ * Verifies a Mailtruss webhook (Standard Webhooks format). Pass the raw request body exactly as received, the request headers,
463
+ * and the endpoint's signing secret. Returns the parsed event, or throws if the signature or timestamp is wrong.
464
+ * Works in Node 18+, edge runtimes and Workers (Web Crypto). Standard libraries (`standardwebhooks`, `svix`) also work.
465
+ *
466
+ * const event = await verifyWebhook(await req.text(), req.headers, process.env.MAILTRUSS_WEBHOOK_SECRET!);
467
+ */
468
+ export declare function verifyWebhook<T = unknown>(body: string, headers: Headers | Record<string, string | string[] | undefined>, secret: string, toleranceSeconds?: number): Promise<T>;
package/dist/index.js CHANGED
@@ -74,6 +74,8 @@ export class MailClient {
74
74
  list: (q = {}, o) => this.request("GET", `/api/v1/emails${this.q(q)}`, undefined, o),
75
75
  reschedule: (id, scheduled_at, o) => this.request("PATCH", `/api/v1/emails/${encodeURIComponent(id)}`, { scheduled_at }, o),
76
76
  cancel: (id, o) => this.request("POST", `/api/v1/emails/${encodeURIComponent(id)}/cancel`, undefined, o),
77
+ /** The delivery timeline with what each event proves, and a plain-language diagnosis when something went wrong. */
78
+ events: (id, o) => this.request("GET", `/api/v1/emails/${encodeURIComponent(id)}/events`, undefined, o),
77
79
  };
78
80
  domains = {
79
81
  create: (p, o) => this.request("POST", "/api/v1/domains", p, o),
@@ -149,3 +151,40 @@ export class MailClient {
149
151
  list: (q = {}, o) => this.request("GET", `/api/v1/logs${this.q(q)}`, undefined, o),
150
152
  };
151
153
  }
154
+ /**
155
+ * Verifies a Mailtruss webhook (Standard Webhooks format). Pass the raw request body exactly as received, the request headers,
156
+ * and the endpoint's signing secret. Returns the parsed event, or throws if the signature or timestamp is wrong.
157
+ * Works in Node 18+, edge runtimes and Workers (Web Crypto). Standard libraries (`standardwebhooks`, `svix`) also work.
158
+ *
159
+ * const event = await verifyWebhook(await req.text(), req.headers, process.env.MAILTRUSS_WEBHOOK_SECRET!);
160
+ */
161
+ export async function verifyWebhook(body, headers, secret, toleranceSeconds = 300) {
162
+ const h = (k) => {
163
+ const v = headers instanceof Headers ? headers.get(k) : headers[k] ?? headers[k.toLowerCase()];
164
+ return Array.isArray(v) ? v[0] : v ?? undefined;
165
+ };
166
+ const id = h("webhook-id") ?? h("svix-id"), ts = h("webhook-timestamp") ?? h("svix-timestamp"), sigs = h("webhook-signature") ?? h("svix-signature");
167
+ if (!id || !ts || !sigs)
168
+ throw new Error("Missing webhook-id, webhook-timestamp or webhook-signature header");
169
+ if (Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSeconds)
170
+ throw new Error("Webhook timestamp is outside the allowed window");
171
+ // Secrets issued before 2026-11-14 (whsec_ + 32 url-safe characters) were used as text; newer ones are base64 key bytes.
172
+ const raw = secret.replace(/^whsec_/, "");
173
+ const legacy = /^whsec_[A-Za-z0-9_-]{32}$/.test(secret);
174
+ const keyBytes = legacy ? new TextEncoder().encode(secret) : Uint8Array.from(atob(raw), (c) => c.charCodeAt(0));
175
+ const key = await crypto.subtle.importKey("raw", keyBytes, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
176
+ const mac = new Uint8Array(await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${id}.${ts}.${body}`)));
177
+ const expected = btoa(String.fromCharCode(...mac));
178
+ const ok = sigs.split(" ").some((part) => {
179
+ const [v, sig] = part.split(",");
180
+ if (v !== "v1" || !sig || sig.length !== expected.length)
181
+ return false;
182
+ let diff = 0;
183
+ for (let i = 0; i < sig.length; i++)
184
+ diff |= sig.charCodeAt(i) ^ expected.charCodeAt(i);
185
+ return diff === 0;
186
+ });
187
+ if (!ok)
188
+ throw new Error("Webhook signature does not match");
189
+ return JSON.parse(body);
190
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mailtruss",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Official TypeScript and JavaScript client for the Mailtruss email API",
5
5
  "license": "MIT",
6
6
  "author": "Mailtruss <hello@mailtruss.com>",