@mailhooks/sdk 1.0.4 → 1.1.3

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/README.md CHANGED
@@ -62,6 +62,7 @@ const emails = await mailhooks.emails.list({
62
62
  subject: 'Important',
63
63
  startDate: '2024-01-01',
64
64
  endDate: '2024-12-31',
65
+ read: false, // Filter by read status
65
66
  },
66
67
  sort: {
67
68
  field: 'createdAt',
@@ -73,7 +74,11 @@ const emails = await mailhooks.emails.list({
73
74
  #### Get Email
74
75
 
75
76
  ```typescript
77
+ // Get email without marking as read
76
78
  const email = await mailhooks.emails.getEmail('email-id');
79
+
80
+ // Get email and mark it as read in one call
81
+ const readEmail = await mailhooks.emails.getEmail('email-id', true);
77
82
  ```
78
83
 
79
84
  #### Get Email Content
@@ -99,14 +104,29 @@ const attachment = await mailhooks.emails.downloadAttachment('email-id', 'attach
99
104
  // attachment.contentType contains the MIME type
100
105
  ```
101
106
 
107
+ #### Mark Email as Read/Unread
108
+
109
+ ```typescript
110
+ // Mark email as read
111
+ const readEmail = await mailhooks.emails.markAsRead('email-id');
112
+ console.log(readEmail.read); // true
113
+
114
+ // Mark email as unread
115
+ const unreadEmail = await mailhooks.emails.markAsUnread('email-id');
116
+ console.log(unreadEmail.read); // false
117
+ ```
118
+
102
119
  #### Wait for Email
103
120
 
104
121
  Wait for an email that matches specific filters. Useful for testing and automation.
105
122
 
106
123
  ```typescript
107
- // Basic usage - wait for email from specific sender
124
+ // Basic usage - wait for unread email from specific sender
108
125
  const email = await mailhooks.emails.waitFor({
109
- filter: { from: 'noreply@example.com' },
126
+ filter: {
127
+ from: 'noreply@example.com',
128
+ read: false // Only wait for unread emails
129
+ },
110
130
  timeout: 30000, // 30 seconds
111
131
  pollInterval: 2000, // Check every 2 seconds
112
132
  });
@@ -117,6 +137,7 @@ const email = await mailhooks.emails.waitFor({
117
137
  from: 'noreply@example.com',
118
138
  to: 'test@yourdomain.com',
119
139
  subject: 'Order Confirmation',
140
+ read: false, // Only unread emails
120
141
  },
121
142
  lookbackWindow: 10000, // Only consider emails from last 10 seconds
122
143
  initialDelay: 5000, // Wait 5 seconds before first check
@@ -127,7 +148,7 @@ const email = await mailhooks.emails.waitFor({
127
148
  ```
128
149
 
129
150
  **Options:**
130
- - `filter`: Same filters as `list()` method (from, to, subject, startDate, endDate)
151
+ - `filter`: Same filters as `list()` method (from, to, subject, startDate, endDate, read)
131
152
  - `lookbackWindow`: How far back to look for emails on first check (default: 10000ms)
132
153
  - `initialDelay`: Delay before starting to poll (default: 0ms)
133
154
  - `timeout`: Maximum time to wait before throwing error (default: 30000ms)
@@ -150,6 +171,7 @@ interface Email {
150
171
  from: string;
151
172
  to: string[];
152
173
  subject: string;
174
+ read: boolean;
153
175
  createdAt: Date;
154
176
  attachments: Attachment[];
155
177
  }
@@ -189,6 +211,122 @@ try {
189
211
  - **404 Not Found**: Resource not found
190
212
  - **429 Too Many Requests**: Rate limit exceeded
191
213
 
214
+ ## Webhook Signature Verification
215
+
216
+ When you create a webhook in Mailhooks, you receive a secret that can be used to verify that incoming webhook requests are genuinely from Mailhooks. Each webhook request includes an `X-Webhook-Signature` header containing an HMAC-SHA256 signature of the request body.
217
+
218
+ ### Verifying Signatures
219
+
220
+ ```typescript
221
+ import { verifyWebhookSignature, parseWebhookPayload } from '@mailhooks/sdk';
222
+
223
+ // Express.js example - IMPORTANT: Use raw body parser for signature verification
224
+ app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
225
+ const signature = req.headers['x-webhook-signature'] as string;
226
+ const payload = req.body.toString();
227
+
228
+ // Verify the signature using your webhook secret
229
+ if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
230
+ console.error('Invalid webhook signature');
231
+ return res.status(401).send('Invalid signature');
232
+ }
233
+
234
+ // Parse the verified payload
235
+ const event = parseWebhookPayload(payload);
236
+
237
+ console.log(`Received email from ${event.from}: ${event.subject}`);
238
+
239
+ // Process the webhook...
240
+ res.status(200).send('OK');
241
+ });
242
+ ```
243
+
244
+ ### Webhook Payload
245
+
246
+ The webhook payload contains the following fields:
247
+
248
+ ```typescript
249
+ interface WebhookPayload {
250
+ id: string; // Unique email ID
251
+ from: string; // Sender email address
252
+ to: string[]; // Array of recipient addresses
253
+ subject: string; // Email subject
254
+ body: string; // Plain text body
255
+ html?: string; // HTML body (if available)
256
+ attachments: Array<{ // Attachment metadata
257
+ filename: string;
258
+ contentType: string;
259
+ size: number;
260
+ }>;
261
+ receivedAt: string; // ISO 8601 timestamp
262
+
263
+ // Email authentication results
264
+ spfResult?: 'pass' | 'fail' | 'softfail' | 'neutral' | 'none';
265
+ dkimResult?: 'pass' | 'fail' | 'none' | 'temperror' | 'permerror';
266
+ dmarcResult?: 'pass' | 'fail' | 'none' | 'temperror' | 'permerror';
267
+ authSummary?: 'pass' | 'fail' | 'partial';
268
+
269
+ // Spam detection
270
+ spamScore?: number; // 0.0 to 1.0
271
+ isSpam?: boolean;
272
+ }
273
+ ```
274
+
275
+ ### Security Best Practices
276
+
277
+ 1. **Always verify signatures** before processing webhook data
278
+ 2. **Use the raw request body** for signature verification (not parsed JSON)
279
+ 3. **Store secrets securely** in environment variables, not in code
280
+ 4. **Use HTTPS** for your webhook endpoint
281
+ 5. **Respond quickly** - return 200 OK before doing heavy processing
282
+
283
+ ### Framework Examples
284
+
285
+ #### Next.js API Route
286
+
287
+ ```typescript
288
+ import { verifyWebhookSignature, parseWebhookPayload } from '@mailhooks/sdk';
289
+ import { NextRequest, NextResponse } from 'next/server';
290
+
291
+ export async function POST(request: NextRequest) {
292
+ const payload = await request.text();
293
+ const signature = request.headers.get('x-webhook-signature') || '';
294
+
295
+ if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
296
+ return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
297
+ }
298
+
299
+ const event = parseWebhookPayload(payload);
300
+ // Process event...
301
+
302
+ return NextResponse.json({ received: true });
303
+ }
304
+ ```
305
+
306
+ #### Fastify
307
+
308
+ ```typescript
309
+ import { verifyWebhookSignature, parseWebhookPayload } from '@mailhooks/sdk';
310
+
311
+ fastify.post('/webhook', {
312
+ config: {
313
+ rawBody: true, // Enable raw body
314
+ },
315
+ }, async (request, reply) => {
316
+ const signature = request.headers['x-webhook-signature'] as string;
317
+ const payload = request.rawBody?.toString() || '';
318
+
319
+ if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
320
+ return reply.status(401).send({ error: 'Invalid signature' });
321
+ }
322
+
323
+ const event = parseWebhookPayload(payload);
324
+ // Process event...
325
+
326
+ return { received: true };
327
+ });
328
+ ```
329
+
192
330
  ## License
193
331
 
194
332
  MIT
package/dist/client.d.ts CHANGED
@@ -7,6 +7,7 @@ export declare class MailhooksClient {
7
7
  protected get<T>(path: string, params?: Record<string, any>): Promise<T>;
8
8
  protected post<T>(path: string, data?: any): Promise<T>;
9
9
  protected put<T>(path: string, data?: any): Promise<T>;
10
+ protected patch<T>(path: string, data?: any): Promise<T>;
10
11
  protected delete<T>(path: string): Promise<T>;
11
12
  protected downloadFile(path: string): Promise<ArrayBuffer>;
12
13
  protected getAxiosInstance(): AxiosInstance;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,aAAa,EAAiB,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAgB;gBAEhB,MAAM,EAAE,eAAe;IAkBnC,OAAO,CAAC,UAAU;cAkBF,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;cAK9D,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC;cAK7C,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC;cAK5C,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;cAKnC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAOhE,SAAS,CAAC,gBAAgB,IAAI,aAAa;CAG5C"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,aAAa,EAAiB,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAgB;gBAEhB,MAAM,EAAE,eAAe;IAkBnC,OAAO,CAAC,UAAU;cAkBF,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;cAK9D,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC;cAK7C,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC;cAK5C,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC;cAK9C,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;cAKnC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAOhE,SAAS,CAAC,gBAAgB,IAAI,aAAa;CAG5C"}
package/dist/client.js CHANGED
@@ -53,6 +53,10 @@ class MailhooksClient {
53
53
  const response = await this.http.put(path, data);
54
54
  return response.data;
55
55
  }
56
+ async patch(path, data) {
57
+ const response = await this.http.patch(path, data);
58
+ return response.data;
59
+ }
56
60
  async delete(path) {
57
61
  const response = await this.http.delete(path);
58
62
  return response.data;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { Mailhooks } from './mailhooks';
2
2
  export { EmailsResource } from './resources/emails';
3
3
  export * from './types';
4
+ export { verifyWebhookSignature, parseWebhookPayload, constructSignature, type WebhookPayload, } from './webhooks';
4
5
  import { Mailhooks } from './mailhooks';
5
6
  export default Mailhooks;
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,cAAc,SAAS,CAAC;AAGxB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,eAAe,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -14,12 +14,17 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.EmailsResource = exports.Mailhooks = void 0;
17
+ exports.constructSignature = exports.parseWebhookPayload = exports.verifyWebhookSignature = exports.EmailsResource = exports.Mailhooks = void 0;
18
18
  var mailhooks_1 = require("./mailhooks");
19
19
  Object.defineProperty(exports, "Mailhooks", { enumerable: true, get: function () { return mailhooks_1.Mailhooks; } });
20
20
  var emails_1 = require("./resources/emails");
21
21
  Object.defineProperty(exports, "EmailsResource", { enumerable: true, get: function () { return emails_1.EmailsResource; } });
22
22
  __exportStar(require("./types"), exports);
23
+ // Webhook verification utilities
24
+ var webhooks_1 = require("./webhooks");
25
+ Object.defineProperty(exports, "verifyWebhookSignature", { enumerable: true, get: function () { return webhooks_1.verifyWebhookSignature; } });
26
+ Object.defineProperty(exports, "parseWebhookPayload", { enumerable: true, get: function () { return webhooks_1.parseWebhookPayload; } });
27
+ Object.defineProperty(exports, "constructSignature", { enumerable: true, get: function () { return webhooks_1.constructSignature; } });
23
28
  // Default export for convenience
24
29
  const mailhooks_2 = require("./mailhooks");
25
30
  exports.default = mailhooks_2.Mailhooks;
@@ -7,8 +7,10 @@ export declare class EmailsResource extends MailhooksClient {
7
7
  list(params?: EmailListParams): Promise<EmailsResponse>;
8
8
  /**
9
9
  * Get a specific email by ID
10
+ * @param emailId - The ID of the email to retrieve
11
+ * @param markAsRead - Optional: Mark the email as read when fetching (default: false)
10
12
  */
11
- getEmail(emailId: string): Promise<Email>;
13
+ getEmail(emailId: string, markAsRead?: boolean): Promise<Email>;
12
14
  /**
13
15
  * Get the HTML and text content of an email
14
16
  */
@@ -21,6 +23,14 @@ export declare class EmailsResource extends MailhooksClient {
21
23
  * Download a specific attachment from an email
22
24
  */
23
25
  downloadAttachment(emailId: string, attachmentId: string): Promise<DownloadResponse>;
26
+ /**
27
+ * Mark an email as read
28
+ */
29
+ markAsRead(emailId: string): Promise<Email>;
30
+ /**
31
+ * Mark an email as unread
32
+ */
33
+ markAsUnread(emailId: string): Promise<Email>;
24
34
  /**
25
35
  * Wait for an email that matches the given filters
26
36
  *
@@ -1 +1 @@
1
- {"version":3,"file":"emails.d.ts","sourceRoot":"","sources":["../../src/resources/emails.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EACL,KAAK,EACL,YAAY,EACZ,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACf,MAAM,UAAU,CAAC;AAElB,qBAAa,cAAe,SAAQ,eAAe;IACjD;;OAEG;IACG,IAAI,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IAyB7D;;OAEG;IACG,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAI/C;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAIxD;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAS7D;;OAEG;IACG,kBAAkB,CACtB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,CAAC;IAqB5B;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,KAAK,CAAC;CA8F5D"}
1
+ {"version":3,"file":"emails.d.ts","sourceRoot":"","sources":["../../src/resources/emails.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EACL,KAAK,EACL,YAAY,EACZ,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACf,MAAM,UAAU,CAAC;AAElB,qBAAa,cAAe,SAAQ,eAAe;IACjD;;OAEG;IACG,IAAI,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IA0B7D;;;;OAIG;IACG,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,GAAE,OAAe,GAAG,OAAO,CAAC,KAAK,CAAC;IAK5E;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAIxD;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAS7D;;OAEG;IACG,kBAAkB,CACtB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,CAAC;IAqB5B;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAIjD;;OAEG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAInD;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,KAAK,CAAC;CA8F5D"}
@@ -24,6 +24,8 @@ class EmailsResource extends client_1.MailhooksClient {
24
24
  queryParams['filter.createdAfter'] = params.filter.startDate;
25
25
  if (params.filter.endDate)
26
26
  queryParams['filter.createdBefore'] = params.filter.endDate;
27
+ if (params.filter.read !== undefined)
28
+ queryParams['filter.read'] = String(params.filter.read);
27
29
  }
28
30
  // Handle sort params
29
31
  if (params?.sort) {
@@ -36,9 +38,12 @@ class EmailsResource extends client_1.MailhooksClient {
36
38
  }
37
39
  /**
38
40
  * Get a specific email by ID
41
+ * @param emailId - The ID of the email to retrieve
42
+ * @param markAsRead - Optional: Mark the email as read when fetching (default: false)
39
43
  */
40
- async getEmail(emailId) {
41
- return super.get(`/v1/emails/${emailId}`);
44
+ async getEmail(emailId, markAsRead = false) {
45
+ const params = markAsRead ? { markAsRead: 'true' } : undefined;
46
+ return super.get(`/v1/emails/${emailId}`, params);
42
47
  }
43
48
  /**
44
49
  * Get the HTML and text content of an email
@@ -75,6 +80,18 @@ class EmailsResource extends client_1.MailhooksClient {
75
80
  contentType: response.headers['content-type'],
76
81
  };
77
82
  }
83
+ /**
84
+ * Mark an email as read
85
+ */
86
+ async markAsRead(emailId) {
87
+ return super.patch(`/v1/emails/${emailId}/read`);
88
+ }
89
+ /**
90
+ * Mark an email as unread
91
+ */
92
+ async markAsUnread(emailId) {
93
+ return super.patch(`/v1/emails/${emailId}/unread`);
94
+ }
78
95
  /**
79
96
  * Wait for an email that matches the given filters
80
97
  *
package/dist/types.d.ts CHANGED
@@ -9,6 +9,7 @@ export interface Email {
9
9
  from: string;
10
10
  to: string[];
11
11
  subject: string;
12
+ read: boolean;
12
13
  createdAt: Date;
13
14
  attachments: Attachment[];
14
15
  }
@@ -33,6 +34,7 @@ export interface EmailFilter {
33
34
  subject?: string;
34
35
  startDate?: string;
35
36
  endDate?: string;
37
+ read?: boolean;
36
38
  }
37
39
  export interface EmailSort {
38
40
  field?: 'createdAt' | 'from' | 'subject';
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAe,SAAQ,kBAAkB;IACxD,IAAI,EAAE,KAAK,EAAE,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;IACzC,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAe,SAAQ,kBAAkB;IACxD,IAAI,EAAE,KAAK,EAAE,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;IACzC,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Webhook payload sent by Mailhooks when an email is received.
3
+ */
4
+ export interface WebhookPayload {
5
+ /** Unique email ID */
6
+ id: string;
7
+ /** Sender email address */
8
+ from: string;
9
+ /** Array of recipient email addresses */
10
+ to: string[];
11
+ /** Email subject line */
12
+ subject: string;
13
+ /** Plain text body of the email */
14
+ body: string;
15
+ /** HTML body of the email (if available) */
16
+ html?: string;
17
+ /** Array of attachment metadata */
18
+ attachments: Array<{
19
+ filename: string;
20
+ contentType: string;
21
+ size: number;
22
+ }>;
23
+ /** ISO 8601 timestamp when the email was received */
24
+ receivedAt: string;
25
+ /** SPF authentication result */
26
+ spfResult?: 'pass' | 'fail' | 'softfail' | 'neutral' | 'none';
27
+ /** DKIM authentication result */
28
+ dkimResult?: 'pass' | 'fail' | 'none' | 'temperror' | 'permerror';
29
+ /** DMARC authentication result */
30
+ dmarcResult?: 'pass' | 'fail' | 'none' | 'temperror' | 'permerror';
31
+ /** Overall authentication summary */
32
+ authSummary?: 'pass' | 'fail' | 'partial';
33
+ /** Email headers as key-value pairs */
34
+ headers?: Record<string, string>;
35
+ /** Authentication diagnostic details */
36
+ authDiagnostics?: {
37
+ spf: {
38
+ clientIp: string;
39
+ domain: string;
40
+ record?: string;
41
+ helo?: string;
42
+ } | null;
43
+ dkim: Array<{
44
+ domain: string;
45
+ selector?: string;
46
+ algorithm?: string;
47
+ aligned?: boolean;
48
+ result: string;
49
+ }>;
50
+ dmarc: {
51
+ domain: string;
52
+ policy: string;
53
+ record?: string;
54
+ alignment: {
55
+ spf: {
56
+ result: string | false;
57
+ strict: boolean;
58
+ };
59
+ dkim: {
60
+ result: string | false;
61
+ strict: boolean;
62
+ };
63
+ };
64
+ } | null;
65
+ };
66
+ }
67
+ /**
68
+ * Verifies a webhook signature using HMAC-SHA256.
69
+ *
70
+ * Each webhook request from Mailhooks includes an `X-Webhook-Signature` header
71
+ * containing a hex-encoded HMAC-SHA256 signature of the request body.
72
+ *
73
+ * @param payload - The raw request body as a string or Buffer
74
+ * @param signature - The signature from the `X-Webhook-Signature` header
75
+ * @param secret - Your webhook secret (starts with `whsec_`)
76
+ * @returns `true` if the signature is valid, `false` otherwise
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * import { verifyWebhookSignature } from '@mailhooks/sdk';
81
+ *
82
+ * // Express.js with raw body parser
83
+ * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
84
+ * const signature = req.headers['x-webhook-signature'] as string;
85
+ * const payload = req.body.toString();
86
+ *
87
+ * if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
88
+ * return res.status(401).send('Invalid signature');
89
+ * }
90
+ *
91
+ * const event = JSON.parse(payload);
92
+ * // Process the webhook...
93
+ * res.status(200).send('OK');
94
+ * });
95
+ * ```
96
+ */
97
+ export declare function verifyWebhookSignature(payload: string | Buffer, signature: string, secret: string): boolean;
98
+ /**
99
+ * Parses a webhook payload from a JSON string.
100
+ *
101
+ * @param body - The raw request body as a string
102
+ * @returns The parsed webhook payload
103
+ * @throws {SyntaxError} If the body is not valid JSON
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * import { parseWebhookPayload } from '@mailhooks/sdk';
108
+ *
109
+ * const payload = parseWebhookPayload(req.body.toString());
110
+ * console.log(`Received email from ${payload.from}: ${payload.subject}`);
111
+ * ```
112
+ */
113
+ export declare function parseWebhookPayload(body: string): WebhookPayload;
114
+ /**
115
+ * Constructs the expected signature for a webhook payload.
116
+ * Useful for debugging or manual verification.
117
+ *
118
+ * @param payload - The raw request body as a string or Buffer
119
+ * @param secret - Your webhook secret
120
+ * @returns The expected HMAC-SHA256 signature as a hex string
121
+ */
122
+ export declare function constructSignature(payload: string | Buffer, secret: string): string;
123
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,sBAAsB;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,WAAW,EAAE,KAAK,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;IAC9D,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,WAAW,CAAC;IAClE,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,WAAW,CAAC;IACnE,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC1C,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,wCAAwC;IACxC,eAAe,CAAC,EAAE;QAChB,GAAG,EAAE;YACH,QAAQ,EAAE,MAAM,CAAC;YACjB,MAAM,EAAE,MAAM,CAAC;YACf,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,IAAI,CAAC,EAAE,MAAM,CAAC;SACf,GAAG,IAAI,CAAC;QACT,IAAI,EAAE,KAAK,CAAC;YACV,MAAM,EAAE,MAAM,CAAC;YACf,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,OAAO,CAAC,EAAE,OAAO,CAAC;YAClB,MAAM,EAAE,MAAM,CAAC;SAChB,CAAC,CAAC;QACH,KAAK,EAAE;YACL,MAAM,EAAE,MAAM,CAAC;YACf,MAAM,EAAE,MAAM,CAAC;YACf,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,SAAS,EAAE;gBACT,GAAG,EAAE;oBAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;oBAAC,MAAM,EAAE,OAAO,CAAA;iBAAE,CAAC;gBACjD,IAAI,EAAE;oBAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;oBAAC,MAAM,EAAE,OAAO,CAAA;iBAAE,CAAC;aACnD,CAAC;SACH,GAAG,IAAI,CAAC;KACV,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAqBT;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAEhE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,MAAM,EAAE,MAAM,GACb,MAAM,CAER"}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyWebhookSignature = verifyWebhookSignature;
4
+ exports.parseWebhookPayload = parseWebhookPayload;
5
+ exports.constructSignature = constructSignature;
6
+ const crypto_1 = require("crypto");
7
+ /**
8
+ * Verifies a webhook signature using HMAC-SHA256.
9
+ *
10
+ * Each webhook request from Mailhooks includes an `X-Webhook-Signature` header
11
+ * containing a hex-encoded HMAC-SHA256 signature of the request body.
12
+ *
13
+ * @param payload - The raw request body as a string or Buffer
14
+ * @param signature - The signature from the `X-Webhook-Signature` header
15
+ * @param secret - Your webhook secret (starts with `whsec_`)
16
+ * @returns `true` if the signature is valid, `false` otherwise
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { verifyWebhookSignature } from '@mailhooks/sdk';
21
+ *
22
+ * // Express.js with raw body parser
23
+ * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
24
+ * const signature = req.headers['x-webhook-signature'] as string;
25
+ * const payload = req.body.toString();
26
+ *
27
+ * if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
28
+ * return res.status(401).send('Invalid signature');
29
+ * }
30
+ *
31
+ * const event = JSON.parse(payload);
32
+ * // Process the webhook...
33
+ * res.status(200).send('OK');
34
+ * });
35
+ * ```
36
+ */
37
+ function verifyWebhookSignature(payload, signature, secret) {
38
+ if (!payload || !signature || !secret) {
39
+ return false;
40
+ }
41
+ // Normalize and validate signature format (should be 64 hex chars for SHA256)
42
+ const normalizedSignature = signature.trim().toLowerCase();
43
+ if (!/^[a-f0-9]{64}$/.test(normalizedSignature)) {
44
+ return false;
45
+ }
46
+ const expectedSignature = (0, crypto_1.createHmac)('sha256', secret)
47
+ .update(payload)
48
+ .digest('hex');
49
+ // Use timing-safe comparison to prevent timing attacks
50
+ // Both buffers are guaranteed to be 32 bytes (64 hex chars)
51
+ return (0, crypto_1.timingSafeEqual)(Buffer.from(normalizedSignature, 'hex'), Buffer.from(expectedSignature, 'hex'));
52
+ }
53
+ /**
54
+ * Parses a webhook payload from a JSON string.
55
+ *
56
+ * @param body - The raw request body as a string
57
+ * @returns The parsed webhook payload
58
+ * @throws {SyntaxError} If the body is not valid JSON
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * import { parseWebhookPayload } from '@mailhooks/sdk';
63
+ *
64
+ * const payload = parseWebhookPayload(req.body.toString());
65
+ * console.log(`Received email from ${payload.from}: ${payload.subject}`);
66
+ * ```
67
+ */
68
+ function parseWebhookPayload(body) {
69
+ return JSON.parse(body);
70
+ }
71
+ /**
72
+ * Constructs the expected signature for a webhook payload.
73
+ * Useful for debugging or manual verification.
74
+ *
75
+ * @param payload - The raw request body as a string or Buffer
76
+ * @param secret - Your webhook secret
77
+ * @returns The expected HMAC-SHA256 signature as a hex string
78
+ */
79
+ function constructSignature(payload, secret) {
80
+ return (0, crypto_1.createHmac)('sha256', secret).update(payload).digest('hex');
81
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mailhooks/sdk",
3
- "version": "1.0.4",
3
+ "version": "1.1.3",
4
4
  "description": "TypeScript SDK for Mailhooks API",
5
5
  "publishConfig": {
6
6
  "access": "public"