@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 +273 -7
- package/dist/index.js +280 -8
- package/package.json +3 -2
- package/dist/client.d.ts +0 -15
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -67
- package/dist/index.d.ts.map +0 -1
- package/dist/mailhooks.d.ts +0 -11
- package/dist/mailhooks.d.ts.map +0 -1
- package/dist/mailhooks.js +0 -12
- package/dist/resources/emails.d.ts +0 -61
- package/dist/resources/emails.d.ts.map +0 -1
- package/dist/resources/emails.js +0 -196
- package/dist/types.d.ts +0 -66
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
- package/dist/webhooks.d.ts +0 -123
- package/dist/webhooks.d.ts.map +0 -1
- package/dist/webhooks.js +0 -76
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,273 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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.
|
|
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": "
|
|
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
|
package/dist/client.d.ts.map
DELETED
|
@@ -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
|
-
}
|
package/dist/index.d.ts.map
DELETED
|
@@ -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"}
|
package/dist/mailhooks.d.ts
DELETED
|
@@ -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
|
package/dist/mailhooks.d.ts.map
DELETED
|
@@ -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"}
|
package/dist/resources/emails.js
DELETED
|
@@ -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
|
package/dist/types.d.ts.map
DELETED
|
@@ -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 {};
|
package/dist/webhooks.d.ts
DELETED
|
@@ -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
|
package/dist/webhooks.d.ts.map
DELETED
|
@@ -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
|
-
}
|