@mailhooks/sdk 2.0.0 → 2.0.1

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
@@ -1,7 +1,273 @@
1
- export { Mailhooks } from './mailhooks';
2
- export { EmailsResource } from './resources/emails';
3
- export * from './types';
4
- export { verifyWebhookSignature, parseWebhookPayload, constructSignature, type WebhookPayload, } from './webhooks';
5
- import { Mailhooks } from './mailhooks';
6
- export default Mailhooks;
7
- //# sourceMappingURL=index.d.ts.map
1
+ import { AxiosInstance } from 'axios';
2
+
3
+ interface Attachment {
4
+ id: string;
5
+ filename: string;
6
+ contentType: string;
7
+ size: number;
8
+ }
9
+ interface Email {
10
+ id: string;
11
+ from: string;
12
+ to: string[];
13
+ subject: string;
14
+ read: boolean;
15
+ createdAt: Date;
16
+ attachments: Attachment[];
17
+ }
18
+ interface EmailContent {
19
+ html?: string;
20
+ text?: string;
21
+ }
22
+ interface PaginationResponse {
23
+ currentPage: number;
24
+ perPage: number;
25
+ totalItems: number;
26
+ totalPages: number;
27
+ hasNextPage: boolean;
28
+ nextCursor?: string;
29
+ }
30
+ interface EmailsResponse extends PaginationResponse {
31
+ data: Email[];
32
+ }
33
+ interface EmailFilter {
34
+ from?: string;
35
+ to?: string;
36
+ subject?: string;
37
+ startDate?: string;
38
+ endDate?: string;
39
+ read?: boolean;
40
+ }
41
+ interface EmailSort {
42
+ field?: 'createdAt' | 'from' | 'subject';
43
+ order?: 'asc' | 'desc';
44
+ }
45
+ interface EmailListParams {
46
+ page?: number;
47
+ perPage?: number;
48
+ filter?: EmailFilter;
49
+ sort?: EmailSort;
50
+ }
51
+ interface MailhooksConfig {
52
+ apiKey: string;
53
+ baseUrl?: string;
54
+ }
55
+ interface DownloadResponse {
56
+ data: ArrayBuffer;
57
+ filename?: string;
58
+ contentType?: string;
59
+ }
60
+ interface WaitForOptions {
61
+ filter?: EmailFilter;
62
+ timeout?: number;
63
+ pollInterval?: number;
64
+ maxRetries?: number;
65
+ initialDelay?: number;
66
+ lookbackWindow?: number;
67
+ }
68
+
69
+ declare class MailhooksClient {
70
+ private http;
71
+ constructor(config: MailhooksConfig);
72
+ private parseDates;
73
+ protected get<T>(path: string, params?: Record<string, any>): Promise<T>;
74
+ protected post<T>(path: string, data?: any): Promise<T>;
75
+ protected put<T>(path: string, data?: any): Promise<T>;
76
+ protected patch<T>(path: string, data?: any): Promise<T>;
77
+ protected delete<T>(path: string): Promise<T>;
78
+ protected downloadFile(path: string): Promise<ArrayBuffer>;
79
+ protected getAxiosInstance(): AxiosInstance;
80
+ }
81
+
82
+ declare class EmailsResource extends MailhooksClient {
83
+ /**
84
+ * Get a paginated list of emails
85
+ */
86
+ list(params?: EmailListParams): Promise<EmailsResponse>;
87
+ /**
88
+ * Get a specific email by ID
89
+ * @param emailId - The ID of the email to retrieve
90
+ * @param markAsRead - Optional: Mark the email as read when fetching (default: false)
91
+ */
92
+ getEmail(emailId: string, markAsRead?: boolean): Promise<Email>;
93
+ /**
94
+ * Get the HTML and text content of an email
95
+ */
96
+ getContent(emailId: string): Promise<EmailContent>;
97
+ /**
98
+ * Download email in EML format
99
+ */
100
+ downloadEml(emailId: string): Promise<DownloadResponse>;
101
+ /**
102
+ * Download a specific attachment from an email
103
+ */
104
+ downloadAttachment(emailId: string, attachmentId: string): Promise<DownloadResponse>;
105
+ /**
106
+ * Mark an email as read
107
+ */
108
+ markAsRead(emailId: string): Promise<Email>;
109
+ /**
110
+ * Mark an email as unread
111
+ */
112
+ markAsUnread(emailId: string): Promise<Email>;
113
+ /**
114
+ * Wait for an email that matches the given filters
115
+ *
116
+ * @param options - Options for waiting including filters, timeouts, and delays
117
+ * @returns The first email that matches the filters
118
+ * @throws Error if timeout is reached or max retries exceeded
119
+ *
120
+ * @example
121
+ * // Wait for an email from a specific sender (only considers emails from last 10 seconds)
122
+ * const email = await mailhooks.emails.waitFor({
123
+ * filter: { from: 'test@example.com' },
124
+ * timeout: 30000, // 30 seconds
125
+ * pollInterval: 2000, // Check every 2 seconds
126
+ * lookbackWindow: 10000, // Only consider emails from last 10 seconds
127
+ * });
128
+ *
129
+ * @example
130
+ * // Wait with initial delay (useful when you know email will take time)
131
+ * const email = await mailhooks.emails.waitFor({
132
+ * filter: { subject: 'Order Confirmation' },
133
+ * initialDelay: 5000, // Wait 5 seconds before first check
134
+ * timeout: 60000,
135
+ * lookbackWindow: 5000, // Only consider very recent emails
136
+ * });
137
+ */
138
+ waitFor(options?: WaitForOptions): Promise<Email>;
139
+ }
140
+
141
+ declare class Mailhooks {
142
+ emails: EmailsResource;
143
+ constructor(config: MailhooksConfig);
144
+ /**
145
+ * Create a new Mailhooks SDK instance
146
+ */
147
+ static create(config: MailhooksConfig): Mailhooks;
148
+ }
149
+
150
+ /**
151
+ * Webhook payload sent by Mailhooks when an email is received.
152
+ */
153
+ interface WebhookPayload {
154
+ /** Unique email ID */
155
+ id: string;
156
+ /** Sender email address */
157
+ from: string;
158
+ /** Array of recipient email addresses */
159
+ to: string[];
160
+ /** Email subject line */
161
+ subject: string;
162
+ /** Plain text body of the email */
163
+ body: string;
164
+ /** HTML body of the email (if available) */
165
+ html?: string;
166
+ /** Array of attachment metadata */
167
+ attachments: Array<{
168
+ filename: string;
169
+ contentType: string;
170
+ size: number;
171
+ }>;
172
+ /** ISO 8601 timestamp when the email was received */
173
+ receivedAt: string;
174
+ /** SPF authentication result */
175
+ spfResult?: 'pass' | 'fail' | 'softfail' | 'neutral' | 'none';
176
+ /** DKIM authentication result */
177
+ dkimResult?: 'pass' | 'fail' | 'none' | 'temperror' | 'permerror';
178
+ /** DMARC authentication result */
179
+ dmarcResult?: 'pass' | 'fail' | 'none' | 'temperror' | 'permerror';
180
+ /** Overall authentication summary */
181
+ authSummary?: 'pass' | 'fail' | 'partial';
182
+ /** Email headers as key-value pairs */
183
+ headers?: Record<string, string>;
184
+ /** Authentication diagnostic details */
185
+ authDiagnostics?: {
186
+ spf: {
187
+ clientIp: string;
188
+ domain: string;
189
+ record?: string;
190
+ helo?: string;
191
+ } | null;
192
+ dkim: Array<{
193
+ domain: string;
194
+ selector?: string;
195
+ algorithm?: string;
196
+ aligned?: boolean;
197
+ result: string;
198
+ }>;
199
+ dmarc: {
200
+ domain: string;
201
+ policy: string;
202
+ record?: string;
203
+ alignment: {
204
+ spf: {
205
+ result: string | false;
206
+ strict: boolean;
207
+ };
208
+ dkim: {
209
+ result: string | false;
210
+ strict: boolean;
211
+ };
212
+ };
213
+ } | null;
214
+ };
215
+ }
216
+ /**
217
+ * Verifies a webhook signature using HMAC-SHA256.
218
+ *
219
+ * Each webhook request from Mailhooks includes an `X-Webhook-Signature` header
220
+ * containing a hex-encoded HMAC-SHA256 signature of the request body.
221
+ *
222
+ * @param payload - The raw request body as a string or Buffer
223
+ * @param signature - The signature from the `X-Webhook-Signature` header
224
+ * @param secret - Your webhook secret (starts with `whsec_`)
225
+ * @returns `true` if the signature is valid, `false` otherwise
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * import { verifyWebhookSignature } from '@mailhooks/sdk';
230
+ *
231
+ * // Express.js with raw body parser
232
+ * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
233
+ * const signature = req.headers['x-webhook-signature'] as string;
234
+ * const payload = req.body.toString();
235
+ *
236
+ * if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
237
+ * return res.status(401).send('Invalid signature');
238
+ * }
239
+ *
240
+ * const event = JSON.parse(payload);
241
+ * // Process the webhook...
242
+ * res.status(200).send('OK');
243
+ * });
244
+ * ```
245
+ */
246
+ declare function verifyWebhookSignature(payload: string | Buffer, signature: string, secret: string): boolean;
247
+ /**
248
+ * Parses a webhook payload from a JSON string.
249
+ *
250
+ * @param body - The raw request body as a string
251
+ * @returns The parsed webhook payload
252
+ * @throws {SyntaxError} If the body is not valid JSON
253
+ *
254
+ * @example
255
+ * ```typescript
256
+ * import { parseWebhookPayload } from '@mailhooks/sdk';
257
+ *
258
+ * const payload = parseWebhookPayload(req.body.toString());
259
+ * console.log(`Received email from ${payload.from}: ${payload.subject}`);
260
+ * ```
261
+ */
262
+ declare function parseWebhookPayload(body: string): WebhookPayload;
263
+ /**
264
+ * Constructs the expected signature for a webhook payload.
265
+ * Useful for debugging or manual verification.
266
+ *
267
+ * @param payload - The raw request body as a string or Buffer
268
+ * @param secret - Your webhook secret
269
+ * @returns The expected HMAC-SHA256 signature as a hex string
270
+ */
271
+ declare function constructSignature(payload: string | Buffer, secret: string): string;
272
+
273
+ export { type Attachment, type DownloadResponse, type Email, type EmailContent, type EmailFilter, type EmailListParams, type EmailSort, EmailsResource, type EmailsResponse, Mailhooks, type MailhooksConfig, type PaginationResponse, type WaitForOptions, type WebhookPayload, constructSignature, Mailhooks as default, parseWebhookPayload, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -1,8 +1,280 @@
1
- export { Mailhooks } from './mailhooks';
2
- export { EmailsResource } from './resources/emails';
3
- export * from './types';
4
- // Webhook verification utilities
5
- export { verifyWebhookSignature, parseWebhookPayload, constructSignature, } from './webhooks';
6
- // Default export for convenience
7
- import { Mailhooks } from './mailhooks';
8
- export default Mailhooks;
1
+ import axios from 'axios';
2
+ import { createHmac, timingSafeEqual } from 'crypto';
3
+
4
+ // src/client.ts
5
+ var MailhooksClient = class {
6
+ constructor(config) {
7
+ this.http = axios.create({
8
+ baseURL: config.baseUrl ?? "https://mailhooks.dev/api",
9
+ headers: {
10
+ "x-api-key": config.apiKey,
11
+ "Content-Type": "application/json"
12
+ }
13
+ });
14
+ this.http.interceptors.response.use((response) => {
15
+ if (response.data) {
16
+ this.parseDates(response.data);
17
+ }
18
+ return response;
19
+ });
20
+ }
21
+ parseDates(obj) {
22
+ if (obj && typeof obj === "object") {
23
+ if (Array.isArray(obj)) {
24
+ obj.forEach((item) => this.parseDates(item));
25
+ } else {
26
+ Object.keys(obj).forEach((key) => {
27
+ if (key === "createdAt" || key === "updatedAt") {
28
+ if (typeof obj[key] === "string") {
29
+ obj[key] = new Date(obj[key]);
30
+ }
31
+ } else if (typeof obj[key] === "object") {
32
+ this.parseDates(obj[key]);
33
+ }
34
+ });
35
+ }
36
+ }
37
+ }
38
+ async get(path, params) {
39
+ const response = await this.http.get(path, { params });
40
+ return response.data;
41
+ }
42
+ async post(path, data) {
43
+ const response = await this.http.post(path, data);
44
+ return response.data;
45
+ }
46
+ async put(path, data) {
47
+ const response = await this.http.put(path, data);
48
+ return response.data;
49
+ }
50
+ async patch(path, data) {
51
+ const response = await this.http.patch(path, data);
52
+ return response.data;
53
+ }
54
+ async delete(path) {
55
+ const response = await this.http.delete(path);
56
+ return response.data;
57
+ }
58
+ async downloadFile(path) {
59
+ const response = await this.http.get(path, {
60
+ responseType: "arraybuffer"
61
+ });
62
+ return response.data;
63
+ }
64
+ getAxiosInstance() {
65
+ return this.http;
66
+ }
67
+ };
68
+
69
+ // src/resources/emails.ts
70
+ var EmailsResource = class extends MailhooksClient {
71
+ /**
72
+ * Get a paginated list of emails
73
+ */
74
+ async list(params) {
75
+ const queryParams = {
76
+ page: params?.page || 1,
77
+ perPage: params?.perPage || 20
78
+ };
79
+ if (params?.filter) {
80
+ if (params.filter.from) queryParams["filter.from"] = params.filter.from;
81
+ if (params.filter.to) queryParams["filter.to"] = params.filter.to;
82
+ if (params.filter.subject) queryParams["filter.subject"] = params.filter.subject;
83
+ if (params.filter.startDate) queryParams["filter.createdAfter"] = params.filter.startDate;
84
+ if (params.filter.endDate) queryParams["filter.createdBefore"] = params.filter.endDate;
85
+ if (params.filter.read !== void 0) queryParams["filter.read"] = String(params.filter.read);
86
+ }
87
+ if (params?.sort) {
88
+ if (params.sort.field) queryParams["sort[field]"] = params.sort.field;
89
+ if (params.sort.order) queryParams["sort[order]"] = params.sort.order;
90
+ }
91
+ return super.get("/v1/emails", queryParams);
92
+ }
93
+ /**
94
+ * Get a specific email by ID
95
+ * @param emailId - The ID of the email to retrieve
96
+ * @param markAsRead - Optional: Mark the email as read when fetching (default: false)
97
+ */
98
+ async getEmail(emailId, markAsRead = false) {
99
+ const params = markAsRead ? { markAsRead: "true" } : void 0;
100
+ return super.get(`/v1/emails/${emailId}`, params);
101
+ }
102
+ /**
103
+ * Get the HTML and text content of an email
104
+ */
105
+ async getContent(emailId) {
106
+ return super.get(`/v1/emails/${emailId}/content`);
107
+ }
108
+ /**
109
+ * Download email in EML format
110
+ */
111
+ async downloadEml(emailId) {
112
+ const data = await this.downloadFile(`/v1/emails/${emailId}/eml`);
113
+ return {
114
+ data,
115
+ filename: `email-${emailId}.eml`,
116
+ contentType: "message/rfc822"
117
+ };
118
+ }
119
+ /**
120
+ * Download a specific attachment from an email
121
+ */
122
+ async downloadAttachment(emailId, attachmentId) {
123
+ const axios2 = this.getAxiosInstance();
124
+ const response = await axios2.get(
125
+ `/v1/emails/${emailId}/attachments/${attachmentId}`,
126
+ {
127
+ responseType: "arraybuffer"
128
+ }
129
+ );
130
+ const contentDisposition = response.headers["content-disposition"];
131
+ const filename = contentDisposition ? contentDisposition.split("filename=")[1]?.replace(/['"]/g, "") : `attachment-${attachmentId}`;
132
+ return {
133
+ data: response.data,
134
+ filename,
135
+ contentType: response.headers["content-type"]
136
+ };
137
+ }
138
+ /**
139
+ * Mark an email as read
140
+ */
141
+ async markAsRead(emailId) {
142
+ return super.patch(`/v1/emails/${emailId}/read`);
143
+ }
144
+ /**
145
+ * Mark an email as unread
146
+ */
147
+ async markAsUnread(emailId) {
148
+ return super.patch(`/v1/emails/${emailId}/unread`);
149
+ }
150
+ /**
151
+ * Wait for an email that matches the given filters
152
+ *
153
+ * @param options - Options for waiting including filters, timeouts, and delays
154
+ * @returns The first email that matches the filters
155
+ * @throws Error if timeout is reached or max retries exceeded
156
+ *
157
+ * @example
158
+ * // Wait for an email from a specific sender (only considers emails from last 10 seconds)
159
+ * const email = await mailhooks.emails.waitFor({
160
+ * filter: { from: 'test@example.com' },
161
+ * timeout: 30000, // 30 seconds
162
+ * pollInterval: 2000, // Check every 2 seconds
163
+ * lookbackWindow: 10000, // Only consider emails from last 10 seconds
164
+ * });
165
+ *
166
+ * @example
167
+ * // Wait with initial delay (useful when you know email will take time)
168
+ * const email = await mailhooks.emails.waitFor({
169
+ * filter: { subject: 'Order Confirmation' },
170
+ * initialDelay: 5000, // Wait 5 seconds before first check
171
+ * timeout: 60000,
172
+ * lookbackWindow: 5000, // Only consider very recent emails
173
+ * });
174
+ */
175
+ async waitFor(options = {}) {
176
+ const {
177
+ filter = {},
178
+ timeout = 3e4,
179
+ // Default 30 seconds
180
+ pollInterval = 1e3,
181
+ // Default poll every 1 second
182
+ maxRetries = null,
183
+ initialDelay = 0,
184
+ lookbackWindow = 1e4
185
+ // Default 10 seconds lookback
186
+ } = options;
187
+ const startTime = Date.now();
188
+ let retries = 0;
189
+ let lastCheckedTime = null;
190
+ const checkForEmail = async (isFirstCheck = false) => {
191
+ try {
192
+ const now = /* @__PURE__ */ new Date();
193
+ let startDateFilter;
194
+ if (isFirstCheck && lookbackWindow) {
195
+ startDateFilter = new Date(now.getTime() - lookbackWindow).toISOString();
196
+ } else if (lastCheckedTime) {
197
+ startDateFilter = lastCheckedTime.toISOString();
198
+ } else {
199
+ startDateFilter = new Date(now.getTime() - lookbackWindow).toISOString();
200
+ }
201
+ const searchFilter = {
202
+ ...filter,
203
+ startDate: startDateFilter
204
+ };
205
+ const response = await this.list({
206
+ filter: searchFilter,
207
+ perPage: 10,
208
+ sort: { field: "createdAt", order: "desc" }
209
+ });
210
+ lastCheckedTime = now;
211
+ if (response.data.length > 0) {
212
+ return response.data[0];
213
+ }
214
+ return null;
215
+ } catch (error) {
216
+ console.warn("Error checking for email:", error);
217
+ return null;
218
+ }
219
+ };
220
+ const existingEmail = await checkForEmail(true);
221
+ if (existingEmail) {
222
+ return existingEmail;
223
+ }
224
+ if (initialDelay > 0) {
225
+ await new Promise((resolve) => setTimeout(resolve, initialDelay));
226
+ }
227
+ while (true) {
228
+ if (timeout && Date.now() - startTime > timeout) {
229
+ throw new Error(`Timeout waiting for email after ${timeout}ms`);
230
+ }
231
+ if (maxRetries !== null && retries >= maxRetries) {
232
+ throw new Error(`Max retries (${maxRetries}) exceeded waiting for email`);
233
+ }
234
+ const email = await checkForEmail();
235
+ if (email) {
236
+ return email;
237
+ }
238
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
239
+ retries++;
240
+ }
241
+ }
242
+ };
243
+
244
+ // src/mailhooks.ts
245
+ var Mailhooks = class _Mailhooks {
246
+ constructor(config) {
247
+ this.emails = new EmailsResource(config);
248
+ }
249
+ /**
250
+ * Create a new Mailhooks SDK instance
251
+ */
252
+ static create(config) {
253
+ return new _Mailhooks(config);
254
+ }
255
+ };
256
+ function verifyWebhookSignature(payload, signature, secret) {
257
+ if (!payload || !signature || !secret) {
258
+ return false;
259
+ }
260
+ const normalizedSignature = signature.trim().toLowerCase();
261
+ if (!/^[a-f0-9]{64}$/.test(normalizedSignature)) {
262
+ return false;
263
+ }
264
+ const expectedSignature = createHmac("sha256", secret).update(payload).digest("hex");
265
+ return timingSafeEqual(
266
+ Buffer.from(normalizedSignature, "hex"),
267
+ Buffer.from(expectedSignature, "hex")
268
+ );
269
+ }
270
+ function parseWebhookPayload(body) {
271
+ return JSON.parse(body);
272
+ }
273
+ function constructSignature(payload, secret) {
274
+ return createHmac("sha256", secret).update(payload).digest("hex");
275
+ }
276
+
277
+ // src/index.ts
278
+ var index_default = Mailhooks;
279
+
280
+ export { EmailsResource, Mailhooks, constructSignature, index_default as default, parseWebhookPayload, verifyWebhookSignature };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mailhooks/sdk",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "TypeScript SDK for Mailhooks API",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -9,7 +9,7 @@
9
9
  "main": "dist/index.js",
10
10
  "types": "dist/index.d.ts",
11
11
  "scripts": {
12
- "build": "tsc -p .",
12
+ "build": "tsup",
13
13
  "dev": "tsc -p . --watch",
14
14
  "test": "echo \"No tests yet\"",
15
15
  "prepublishOnly": "pnpm build"
@@ -27,6 +27,7 @@
27
27
  "devDependencies": {
28
28
  "@types/node": "^20.19.0",
29
29
  "dotenv": "^17.2.1",
30
+ "tsup": "^8.5.1",
30
31
  "typescript": "^5.4.5"
31
32
  },
32
33
  "files": [
package/dist/client.d.ts DELETED
@@ -1,15 +0,0 @@
1
- import { AxiosInstance } from 'axios';
2
- import { MailhooksConfig } from './types';
3
- export declare class MailhooksClient {
4
- private http;
5
- constructor(config: MailhooksConfig);
6
- private parseDates;
7
- protected get<T>(path: string, params?: Record<string, any>): Promise<T>;
8
- protected post<T>(path: string, data?: any): Promise<T>;
9
- protected put<T>(path: string, data?: any): Promise<T>;
10
- protected patch<T>(path: string, data?: any): Promise<T>;
11
- protected delete<T>(path: string): Promise<T>;
12
- protected downloadFile(path: string): Promise<ArrayBuffer>;
13
- protected getAxiosInstance(): AxiosInstance;
14
- }
15
- //# sourceMappingURL=client.d.ts.map
@@ -1 +0,0 @@
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 DELETED
@@ -1,67 +0,0 @@
1
- import axios from 'axios';
2
- export class MailhooksClient {
3
- constructor(config) {
4
- this.http = axios.create({
5
- baseURL: config.baseUrl ?? 'https://mailhooks.dev/api',
6
- headers: {
7
- 'x-api-key': config.apiKey,
8
- 'Content-Type': 'application/json',
9
- },
10
- });
11
- // Response interceptor to handle date parsing
12
- this.http.interceptors.response.use((response) => {
13
- if (response.data) {
14
- this.parseDates(response.data);
15
- }
16
- return response;
17
- });
18
- }
19
- parseDates(obj) {
20
- if (obj && typeof obj === 'object') {
21
- if (Array.isArray(obj)) {
22
- obj.forEach(item => this.parseDates(item));
23
- }
24
- else {
25
- Object.keys(obj).forEach(key => {
26
- if (key === 'createdAt' || key === 'updatedAt') {
27
- if (typeof obj[key] === 'string') {
28
- obj[key] = new Date(obj[key]);
29
- }
30
- }
31
- else if (typeof obj[key] === 'object') {
32
- this.parseDates(obj[key]);
33
- }
34
- });
35
- }
36
- }
37
- }
38
- async get(path, params) {
39
- const response = await this.http.get(path, { params });
40
- return response.data;
41
- }
42
- async post(path, data) {
43
- const response = await this.http.post(path, data);
44
- return response.data;
45
- }
46
- async put(path, data) {
47
- const response = await this.http.put(path, data);
48
- return response.data;
49
- }
50
- async patch(path, data) {
51
- const response = await this.http.patch(path, data);
52
- return response.data;
53
- }
54
- async delete(path) {
55
- const response = await this.http.delete(path);
56
- return response.data;
57
- }
58
- async downloadFile(path) {
59
- const response = await this.http.get(path, {
60
- responseType: 'arraybuffer',
61
- });
62
- return response.data;
63
- }
64
- getAxiosInstance() {
65
- return this.http;
66
- }
67
- }
@@ -1 +0,0 @@
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"}
@@ -1,11 +0,0 @@
1
- import { MailhooksConfig } from './types';
2
- import { EmailsResource } from './resources/emails';
3
- export declare class Mailhooks {
4
- emails: EmailsResource;
5
- constructor(config: MailhooksConfig);
6
- /**
7
- * Create a new Mailhooks SDK instance
8
- */
9
- static create(config: MailhooksConfig): Mailhooks;
10
- }
11
- //# sourceMappingURL=mailhooks.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mailhooks.d.ts","sourceRoot":"","sources":["../src/mailhooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,qBAAa,SAAS;IACb,MAAM,EAAE,cAAc,CAAC;gBAElB,MAAM,EAAE,eAAe;IAInC;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS;CAGlD"}
package/dist/mailhooks.js DELETED
@@ -1,12 +0,0 @@
1
- import { EmailsResource } from './resources/emails';
2
- export class Mailhooks {
3
- constructor(config) {
4
- this.emails = new EmailsResource(config);
5
- }
6
- /**
7
- * Create a new Mailhooks SDK instance
8
- */
9
- static create(config) {
10
- return new Mailhooks(config);
11
- }
12
- }
@@ -1,61 +0,0 @@
1
- import { MailhooksClient } from '../client';
2
- import { Email, EmailContent, EmailsResponse, EmailListParams, DownloadResponse, WaitForOptions } from '../types';
3
- export declare class EmailsResource extends MailhooksClient {
4
- /**
5
- * Get a paginated list of emails
6
- */
7
- list(params?: EmailListParams): Promise<EmailsResponse>;
8
- /**
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)
12
- */
13
- getEmail(emailId: string, markAsRead?: boolean): Promise<Email>;
14
- /**
15
- * Get the HTML and text content of an email
16
- */
17
- getContent(emailId: string): Promise<EmailContent>;
18
- /**
19
- * Download email in EML format
20
- */
21
- downloadEml(emailId: string): Promise<DownloadResponse>;
22
- /**
23
- * Download a specific attachment from an email
24
- */
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>;
34
- /**
35
- * Wait for an email that matches the given filters
36
- *
37
- * @param options - Options for waiting including filters, timeouts, and delays
38
- * @returns The first email that matches the filters
39
- * @throws Error if timeout is reached or max retries exceeded
40
- *
41
- * @example
42
- * // Wait for an email from a specific sender (only considers emails from last 10 seconds)
43
- * const email = await mailhooks.emails.waitFor({
44
- * filter: { from: 'test@example.com' },
45
- * timeout: 30000, // 30 seconds
46
- * pollInterval: 2000, // Check every 2 seconds
47
- * lookbackWindow: 10000, // Only consider emails from last 10 seconds
48
- * });
49
- *
50
- * @example
51
- * // Wait with initial delay (useful when you know email will take time)
52
- * const email = await mailhooks.emails.waitFor({
53
- * filter: { subject: 'Order Confirmation' },
54
- * initialDelay: 5000, // Wait 5 seconds before first check
55
- * timeout: 60000,
56
- * lookbackWindow: 5000, // Only consider very recent emails
57
- * });
58
- */
59
- waitFor(options?: WaitForOptions): Promise<Email>;
60
- }
61
- //# sourceMappingURL=emails.d.ts.map
@@ -1 +0,0 @@
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"}
@@ -1,196 +0,0 @@
1
- import { MailhooksClient } from '../client';
2
- export class EmailsResource extends MailhooksClient {
3
- /**
4
- * Get a paginated list of emails
5
- */
6
- async list(params) {
7
- // Set defaults
8
- const queryParams = {
9
- page: params?.page || 1,
10
- perPage: params?.perPage || 20,
11
- };
12
- // Handle filter params
13
- if (params?.filter) {
14
- if (params.filter.from)
15
- queryParams['filter.from'] = params.filter.from;
16
- if (params.filter.to)
17
- queryParams['filter.to'] = params.filter.to;
18
- if (params.filter.subject)
19
- queryParams['filter.subject'] = params.filter.subject;
20
- if (params.filter.startDate)
21
- queryParams['filter.createdAfter'] = params.filter.startDate;
22
- if (params.filter.endDate)
23
- queryParams['filter.createdBefore'] = params.filter.endDate;
24
- if (params.filter.read !== undefined)
25
- queryParams['filter.read'] = String(params.filter.read);
26
- }
27
- // Handle sort params
28
- if (params?.sort) {
29
- if (params.sort.field)
30
- queryParams['sort[field]'] = params.sort.field;
31
- if (params.sort.order)
32
- queryParams['sort[order]'] = params.sort.order;
33
- }
34
- return super.get('/v1/emails', queryParams);
35
- }
36
- /**
37
- * Get a specific email by ID
38
- * @param emailId - The ID of the email to retrieve
39
- * @param markAsRead - Optional: Mark the email as read when fetching (default: false)
40
- */
41
- async getEmail(emailId, markAsRead = false) {
42
- const params = markAsRead ? { markAsRead: 'true' } : undefined;
43
- return super.get(`/v1/emails/${emailId}`, params);
44
- }
45
- /**
46
- * Get the HTML and text content of an email
47
- */
48
- async getContent(emailId) {
49
- return super.get(`/v1/emails/${emailId}/content`);
50
- }
51
- /**
52
- * Download email in EML format
53
- */
54
- async downloadEml(emailId) {
55
- const data = await this.downloadFile(`/v1/emails/${emailId}/eml`);
56
- return {
57
- data,
58
- filename: `email-${emailId}.eml`,
59
- contentType: 'message/rfc822',
60
- };
61
- }
62
- /**
63
- * Download a specific attachment from an email
64
- */
65
- async downloadAttachment(emailId, attachmentId) {
66
- const axios = this.getAxiosInstance();
67
- const response = await axios.get(`/v1/emails/${emailId}/attachments/${attachmentId}`, {
68
- responseType: 'arraybuffer',
69
- });
70
- const contentDisposition = response.headers['content-disposition'];
71
- const filename = contentDisposition
72
- ? contentDisposition.split('filename=')[1]?.replace(/['"]/g, '')
73
- : `attachment-${attachmentId}`;
74
- return {
75
- data: response.data,
76
- filename,
77
- contentType: response.headers['content-type'],
78
- };
79
- }
80
- /**
81
- * Mark an email as read
82
- */
83
- async markAsRead(emailId) {
84
- return super.patch(`/v1/emails/${emailId}/read`);
85
- }
86
- /**
87
- * Mark an email as unread
88
- */
89
- async markAsUnread(emailId) {
90
- return super.patch(`/v1/emails/${emailId}/unread`);
91
- }
92
- /**
93
- * Wait for an email that matches the given filters
94
- *
95
- * @param options - Options for waiting including filters, timeouts, and delays
96
- * @returns The first email that matches the filters
97
- * @throws Error if timeout is reached or max retries exceeded
98
- *
99
- * @example
100
- * // Wait for an email from a specific sender (only considers emails from last 10 seconds)
101
- * const email = await mailhooks.emails.waitFor({
102
- * filter: { from: 'test@example.com' },
103
- * timeout: 30000, // 30 seconds
104
- * pollInterval: 2000, // Check every 2 seconds
105
- * lookbackWindow: 10000, // Only consider emails from last 10 seconds
106
- * });
107
- *
108
- * @example
109
- * // Wait with initial delay (useful when you know email will take time)
110
- * const email = await mailhooks.emails.waitFor({
111
- * filter: { subject: 'Order Confirmation' },
112
- * initialDelay: 5000, // Wait 5 seconds before first check
113
- * timeout: 60000,
114
- * lookbackWindow: 5000, // Only consider very recent emails
115
- * });
116
- */
117
- async waitFor(options = {}) {
118
- const { filter = {}, timeout = 30000, // Default 30 seconds
119
- pollInterval = 1000, // Default poll every 1 second
120
- maxRetries = null, initialDelay = 0, lookbackWindow = 10000, // Default 10 seconds lookback
121
- } = options;
122
- const startTime = Date.now();
123
- let retries = 0;
124
- let lastCheckedTime = null;
125
- // Helper function to check for matching emails
126
- const checkForEmail = async (isFirstCheck = false) => {
127
- try {
128
- // Calculate the time window for filtering
129
- const now = new Date();
130
- let startDateFilter;
131
- if (isFirstCheck && lookbackWindow) {
132
- // On first check, only look back the specified window
133
- startDateFilter = new Date(now.getTime() - lookbackWindow).toISOString();
134
- }
135
- else if (lastCheckedTime) {
136
- // On subsequent checks, look for emails since last check
137
- startDateFilter = lastCheckedTime.toISOString();
138
- }
139
- else {
140
- // Fallback to lookback window
141
- startDateFilter = new Date(now.getTime() - lookbackWindow).toISOString();
142
- }
143
- // Merge the time filter with user-provided filters
144
- const searchFilter = {
145
- ...filter,
146
- startDate: startDateFilter,
147
- };
148
- const response = await this.list({
149
- filter: searchFilter,
150
- perPage: 10,
151
- sort: { field: 'createdAt', order: 'desc' },
152
- });
153
- // Update last checked time for next iteration
154
- lastCheckedTime = now;
155
- if (response.data.length > 0) {
156
- // Return the most recent matching email
157
- return response.data[0];
158
- }
159
- return null;
160
- }
161
- catch (error) {
162
- // Log error but continue polling
163
- console.warn('Error checking for email:', error);
164
- return null;
165
- }
166
- };
167
- // Check immediately for existing emails (before any delay)
168
- const existingEmail = await checkForEmail(true);
169
- if (existingEmail) {
170
- return existingEmail;
171
- }
172
- // Apply initial delay if specified
173
- if (initialDelay > 0) {
174
- await new Promise(resolve => setTimeout(resolve, initialDelay));
175
- }
176
- // Start polling
177
- while (true) {
178
- // Check timeout
179
- if (timeout && Date.now() - startTime > timeout) {
180
- throw new Error(`Timeout waiting for email after ${timeout}ms`);
181
- }
182
- // Check max retries
183
- if (maxRetries !== null && retries >= maxRetries) {
184
- throw new Error(`Max retries (${maxRetries}) exceeded waiting for email`);
185
- }
186
- // Check for email
187
- const email = await checkForEmail();
188
- if (email) {
189
- return email;
190
- }
191
- // Wait before next poll
192
- await new Promise(resolve => setTimeout(resolve, pollInterval));
193
- retries++;
194
- }
195
- }
196
- }
package/dist/types.d.ts DELETED
@@ -1,66 +0,0 @@
1
- export interface Attachment {
2
- id: string;
3
- filename: string;
4
- contentType: string;
5
- size: number;
6
- }
7
- export interface Email {
8
- id: string;
9
- from: string;
10
- to: string[];
11
- subject: string;
12
- read: boolean;
13
- createdAt: Date;
14
- attachments: Attachment[];
15
- }
16
- export interface EmailContent {
17
- html?: string;
18
- text?: string;
19
- }
20
- export interface PaginationResponse {
21
- currentPage: number;
22
- perPage: number;
23
- totalItems: number;
24
- totalPages: number;
25
- hasNextPage: boolean;
26
- nextCursor?: string;
27
- }
28
- export interface EmailsResponse extends PaginationResponse {
29
- data: Email[];
30
- }
31
- export interface EmailFilter {
32
- from?: string;
33
- to?: string;
34
- subject?: string;
35
- startDate?: string;
36
- endDate?: string;
37
- read?: boolean;
38
- }
39
- export interface EmailSort {
40
- field?: 'createdAt' | 'from' | 'subject';
41
- order?: 'asc' | 'desc';
42
- }
43
- export interface EmailListParams {
44
- page?: number;
45
- perPage?: number;
46
- filter?: EmailFilter;
47
- sort?: EmailSort;
48
- }
49
- export interface MailhooksConfig {
50
- apiKey: string;
51
- baseUrl?: string;
52
- }
53
- export interface DownloadResponse {
54
- data: ArrayBuffer;
55
- filename?: string;
56
- contentType?: string;
57
- }
58
- export interface WaitForOptions {
59
- filter?: EmailFilter;
60
- timeout?: number;
61
- pollInterval?: number;
62
- maxRetries?: number;
63
- initialDelay?: number;
64
- lookbackWindow?: number;
65
- }
66
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
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"}
package/dist/types.js DELETED
@@ -1 +0,0 @@
1
- export {};
@@ -1,123 +0,0 @@
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
@@ -1 +0,0 @@
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"}
package/dist/webhooks.js DELETED
@@ -1,76 +0,0 @@
1
- import { createHmac, timingSafeEqual } from 'crypto';
2
- /**
3
- * Verifies a webhook signature using HMAC-SHA256.
4
- *
5
- * Each webhook request from Mailhooks includes an `X-Webhook-Signature` header
6
- * containing a hex-encoded HMAC-SHA256 signature of the request body.
7
- *
8
- * @param payload - The raw request body as a string or Buffer
9
- * @param signature - The signature from the `X-Webhook-Signature` header
10
- * @param secret - Your webhook secret (starts with `whsec_`)
11
- * @returns `true` if the signature is valid, `false` otherwise
12
- *
13
- * @example
14
- * ```typescript
15
- * import { verifyWebhookSignature } from '@mailhooks/sdk';
16
- *
17
- * // Express.js with raw body parser
18
- * app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
19
- * const signature = req.headers['x-webhook-signature'] as string;
20
- * const payload = req.body.toString();
21
- *
22
- * if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
23
- * return res.status(401).send('Invalid signature');
24
- * }
25
- *
26
- * const event = JSON.parse(payload);
27
- * // Process the webhook...
28
- * res.status(200).send('OK');
29
- * });
30
- * ```
31
- */
32
- export function verifyWebhookSignature(payload, signature, secret) {
33
- if (!payload || !signature || !secret) {
34
- return false;
35
- }
36
- // Normalize and validate signature format (should be 64 hex chars for SHA256)
37
- const normalizedSignature = signature.trim().toLowerCase();
38
- if (!/^[a-f0-9]{64}$/.test(normalizedSignature)) {
39
- return false;
40
- }
41
- const expectedSignature = createHmac('sha256', secret)
42
- .update(payload)
43
- .digest('hex');
44
- // Use timing-safe comparison to prevent timing attacks
45
- // Both buffers are guaranteed to be 32 bytes (64 hex chars)
46
- return timingSafeEqual(Buffer.from(normalizedSignature, 'hex'), Buffer.from(expectedSignature, 'hex'));
47
- }
48
- /**
49
- * Parses a webhook payload from a JSON string.
50
- *
51
- * @param body - The raw request body as a string
52
- * @returns The parsed webhook payload
53
- * @throws {SyntaxError} If the body is not valid JSON
54
- *
55
- * @example
56
- * ```typescript
57
- * import { parseWebhookPayload } from '@mailhooks/sdk';
58
- *
59
- * const payload = parseWebhookPayload(req.body.toString());
60
- * console.log(`Received email from ${payload.from}: ${payload.subject}`);
61
- * ```
62
- */
63
- export function parseWebhookPayload(body) {
64
- return JSON.parse(body);
65
- }
66
- /**
67
- * Constructs the expected signature for a webhook payload.
68
- * Useful for debugging or manual verification.
69
- *
70
- * @param payload - The raw request body as a string or Buffer
71
- * @param secret - Your webhook secret
72
- * @returns The expected HMAC-SHA256 signature as a hex string
73
- */
74
- export function constructSignature(payload, secret) {
75
- return createHmac('sha256', secret).update(payload).digest('hex');
76
- }