@mailhooks/sdk 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -3
- package/dist/index.d.ts +81 -1
- package/dist/index.js +36 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -257,6 +257,7 @@ interface WebhookPayload {
|
|
|
257
257
|
filename: string;
|
|
258
258
|
contentType: string;
|
|
259
259
|
size: number;
|
|
260
|
+
storagePath?: string; // Storage path (only for custom storage)
|
|
260
261
|
}>;
|
|
261
262
|
receivedAt: string; // ISO 8601 timestamp
|
|
262
263
|
|
|
@@ -266,9 +267,9 @@ interface WebhookPayload {
|
|
|
266
267
|
dmarcResult?: 'pass' | 'fail' | 'none' | 'temperror' | 'permerror';
|
|
267
268
|
authSummary?: 'pass' | 'fail' | 'partial';
|
|
268
269
|
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
// Custom storage (BYOB)
|
|
271
|
+
usesCustomStorage: boolean; // true if using your own S3 bucket
|
|
272
|
+
storagePath?: string; // EML file path (only for custom storage)
|
|
272
273
|
}
|
|
273
274
|
```
|
|
274
275
|
|
|
@@ -327,6 +328,98 @@ fastify.post('/webhook', {
|
|
|
327
328
|
});
|
|
328
329
|
```
|
|
329
330
|
|
|
331
|
+
## Parsing EML Files (BYOB)
|
|
332
|
+
|
|
333
|
+
If you use custom storage (Bring Your Own Bucket), you can fetch and parse EML files directly using the `parseEml` function. This is useful when you want to process emails from your own S3-compatible storage.
|
|
334
|
+
|
|
335
|
+
### Basic Usage
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { parseEml } from '@mailhooks/sdk';
|
|
339
|
+
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
340
|
+
|
|
341
|
+
// Fetch EML from your S3 bucket
|
|
342
|
+
const s3 = new S3Client({ region: 'us-east-1' });
|
|
343
|
+
const response = await s3.send(new GetObjectCommand({
|
|
344
|
+
Bucket: 'my-email-bucket',
|
|
345
|
+
Key: storagePath, // from webhook payload
|
|
346
|
+
}));
|
|
347
|
+
|
|
348
|
+
const emlBuffer = Buffer.from(await response.Body!.transformToByteArray());
|
|
349
|
+
|
|
350
|
+
// Parse the EML
|
|
351
|
+
const email = await parseEml(emlBuffer);
|
|
352
|
+
|
|
353
|
+
console.log(email.from); // "sender@example.com"
|
|
354
|
+
console.log(email.to); // ["recipient@yourdomain.com"]
|
|
355
|
+
console.log(email.subject); // "Hello World"
|
|
356
|
+
console.log(email.body); // Plain text content
|
|
357
|
+
console.log(email.html); // HTML content (if available)
|
|
358
|
+
console.log(email.headers); // All headers as key-value pairs
|
|
359
|
+
console.log(email.attachments); // [{ filename, contentType, size }]
|
|
360
|
+
console.log(email.date); // Date object from Date header
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Webhook Integration
|
|
364
|
+
|
|
365
|
+
When using custom storage, your webhook payload includes `storagePath` for both the email and attachments:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import { verifyWebhookSignature, parseWebhookPayload, parseEml } from '@mailhooks/sdk';
|
|
369
|
+
|
|
370
|
+
app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
|
|
371
|
+
const signature = req.headers['x-webhook-signature'] as string;
|
|
372
|
+
const payload = req.body.toString();
|
|
373
|
+
|
|
374
|
+
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
|
|
375
|
+
return res.status(401).send('Invalid signature');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const event = parseWebhookPayload(payload);
|
|
379
|
+
|
|
380
|
+
// For custom storage users
|
|
381
|
+
if (event.usesCustomStorage && event.storagePath) {
|
|
382
|
+
// Fetch and parse the full email from your storage
|
|
383
|
+
const emlBuffer = await fetchFromS3(event.storagePath);
|
|
384
|
+
const fullEmail = await parseEml(emlBuffer);
|
|
385
|
+
|
|
386
|
+
// Access full email content
|
|
387
|
+
console.log(fullEmail.html);
|
|
388
|
+
console.log(fullEmail.headers);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Attachment paths are also available for custom storage
|
|
392
|
+
for (const attachment of event.attachments) {
|
|
393
|
+
if (attachment.storagePath) {
|
|
394
|
+
// Fetch attachment from your storage
|
|
395
|
+
const attachmentBuffer = await fetchFromS3(attachment.storagePath);
|
|
396
|
+
// Process attachment...
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
res.status(200).send('OK');
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### ParsedEmail Type
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
interface ParsedEmail {
|
|
408
|
+
from: string; // Sender email address
|
|
409
|
+
to: string[]; // Recipient addresses
|
|
410
|
+
subject: string; // Email subject
|
|
411
|
+
body: string; // Plain text body
|
|
412
|
+
html?: string; // HTML body (if available)
|
|
413
|
+
attachments: Array<{ // Attachment metadata
|
|
414
|
+
filename: string;
|
|
415
|
+
contentType: string;
|
|
416
|
+
size: number;
|
|
417
|
+
}>;
|
|
418
|
+
headers: Record<string, string>; // All email headers
|
|
419
|
+
date?: Date; // Date from headers
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
330
423
|
## License
|
|
331
424
|
|
|
332
425
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,14 @@ interface Attachment {
|
|
|
5
5
|
filename: string;
|
|
6
6
|
contentType: string;
|
|
7
7
|
size: number;
|
|
8
|
+
/** Storage path for the attachment (only present for custom storage) */
|
|
9
|
+
storagePath?: string;
|
|
10
|
+
}
|
|
11
|
+
interface StorageConfigSummary {
|
|
12
|
+
/** Storage provider type */
|
|
13
|
+
provider: 'S3' | 'AZURE_BLOB' | 'GCS';
|
|
14
|
+
/** Storage bucket or container name */
|
|
15
|
+
bucket: string;
|
|
8
16
|
}
|
|
9
17
|
interface Email {
|
|
10
18
|
id: string;
|
|
@@ -14,6 +22,12 @@ interface Email {
|
|
|
14
22
|
read: boolean;
|
|
15
23
|
createdAt: Date;
|
|
16
24
|
attachments: Attachment[];
|
|
25
|
+
/** Whether this email is stored in custom (BYOB) storage */
|
|
26
|
+
usesCustomStorage?: boolean;
|
|
27
|
+
/** Storage configuration details (only present for custom storage) */
|
|
28
|
+
storageConfig?: StorageConfigSummary;
|
|
29
|
+
/** Storage path for the email EML file (only present for custom storage) */
|
|
30
|
+
storagePath?: string;
|
|
17
31
|
}
|
|
18
32
|
interface EmailContent {
|
|
19
33
|
html?: string;
|
|
@@ -168,6 +182,8 @@ interface WebhookPayload {
|
|
|
168
182
|
filename: string;
|
|
169
183
|
contentType: string;
|
|
170
184
|
size: number;
|
|
185
|
+
/** Storage path for the attachment (only present when usesCustomStorage is true) */
|
|
186
|
+
storagePath?: string;
|
|
171
187
|
}>;
|
|
172
188
|
/** ISO 8601 timestamp when the email was received */
|
|
173
189
|
receivedAt: string;
|
|
@@ -216,6 +232,13 @@ interface WebhookPayload {
|
|
|
216
232
|
usesCustomStorage: boolean;
|
|
217
233
|
/** Storage path for the email (only present when usesCustomStorage is true) */
|
|
218
234
|
storagePath?: string;
|
|
235
|
+
/** Storage configuration details (only present when usesCustomStorage is true) */
|
|
236
|
+
storageConfig?: {
|
|
237
|
+
/** Storage provider type (S3, AZURE_BLOB, GCS) */
|
|
238
|
+
provider: string;
|
|
239
|
+
/** Storage bucket or container name */
|
|
240
|
+
bucket: string;
|
|
241
|
+
};
|
|
219
242
|
}
|
|
220
243
|
/**
|
|
221
244
|
* Verifies a webhook signature using HMAC-SHA256.
|
|
@@ -274,4 +297,61 @@ declare function parseWebhookPayload(body: string): WebhookPayload;
|
|
|
274
297
|
*/
|
|
275
298
|
declare function constructSignature(payload: string | Buffer, secret: string): string;
|
|
276
299
|
|
|
277
|
-
|
|
300
|
+
/**
|
|
301
|
+
* Parsed email structure returned by parseEml.
|
|
302
|
+
* Matches the webhook payload structure for consistency.
|
|
303
|
+
*/
|
|
304
|
+
interface ParsedEmail {
|
|
305
|
+
/** Sender email address */
|
|
306
|
+
from: string;
|
|
307
|
+
/** Array of recipient email addresses */
|
|
308
|
+
to: string[];
|
|
309
|
+
/** Email subject line */
|
|
310
|
+
subject: string;
|
|
311
|
+
/** Plain text body of the email */
|
|
312
|
+
body: string;
|
|
313
|
+
/** HTML body of the email (if available) */
|
|
314
|
+
html?: string;
|
|
315
|
+
/** Array of attachment metadata */
|
|
316
|
+
attachments: Array<{
|
|
317
|
+
filename: string;
|
|
318
|
+
contentType: string;
|
|
319
|
+
size: number;
|
|
320
|
+
}>;
|
|
321
|
+
/** Email headers as key-value pairs */
|
|
322
|
+
headers: Record<string, string>;
|
|
323
|
+
/** Date the email was sent (from Date header) */
|
|
324
|
+
date?: Date;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Parses a raw EML file and returns a structured email object.
|
|
328
|
+
*
|
|
329
|
+
* This is useful for BYOB (Bring Your Own Bucket) users who store emails
|
|
330
|
+
* in their own S3-compatible storage and need to parse them.
|
|
331
|
+
*
|
|
332
|
+
* @param eml - The raw EML content as a Buffer or string
|
|
333
|
+
* @returns A Promise that resolves to the parsed email
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```typescript
|
|
337
|
+
* import { parseEml } from '@mailhooks/sdk';
|
|
338
|
+
* import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
339
|
+
*
|
|
340
|
+
* // Fetch EML from your S3 bucket
|
|
341
|
+
* const s3 = new S3Client({ region: 'us-east-1' });
|
|
342
|
+
* const response = await s3.send(new GetObjectCommand({
|
|
343
|
+
* Bucket: 'my-email-bucket',
|
|
344
|
+
* Key: storagePath, // from webhook payload
|
|
345
|
+
* }));
|
|
346
|
+
* const emlBuffer = Buffer.from(await response.Body.transformToByteArray());
|
|
347
|
+
*
|
|
348
|
+
* // Parse the EML
|
|
349
|
+
* const email = await parseEml(emlBuffer);
|
|
350
|
+
* console.log(email.from); // "sender@example.com"
|
|
351
|
+
* console.log(email.subject); // "Hello World"
|
|
352
|
+
* console.log(email.attachments); // [{ filename: "doc.pdf", ... }]
|
|
353
|
+
* ```
|
|
354
|
+
*/
|
|
355
|
+
declare function parseEml(eml: Buffer | string): Promise<ParsedEmail>;
|
|
356
|
+
|
|
357
|
+
export { type Attachment, type DownloadResponse, type Email, type EmailContent, type EmailFilter, type EmailListParams, type EmailSort, EmailsResource, type EmailsResponse, Mailhooks, type MailhooksConfig, type PaginationResponse, type ParsedEmail, type StorageConfigSummary, type WaitForOptions, type WebhookPayload, constructSignature, Mailhooks as default, parseEml, parseWebhookPayload, verifyWebhookSignature };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { createHmac, timingSafeEqual } from 'crypto';
|
|
3
|
+
import { simpleParser } from 'mailparser';
|
|
3
4
|
|
|
4
5
|
// src/client.ts
|
|
5
6
|
var MailhooksClient = class {
|
|
@@ -273,8 +274,42 @@ function parseWebhookPayload(body) {
|
|
|
273
274
|
function constructSignature(payload, secret) {
|
|
274
275
|
return createHmac("sha256", secret).update(payload).digest("hex");
|
|
275
276
|
}
|
|
277
|
+
async function parseEml(eml) {
|
|
278
|
+
const parsed = await simpleParser(eml);
|
|
279
|
+
const from = parsed.from?.value?.[0]?.address || "";
|
|
280
|
+
const toAddresses = parsed.to;
|
|
281
|
+
let to = [];
|
|
282
|
+
if (toAddresses) {
|
|
283
|
+
if (Array.isArray(toAddresses)) {
|
|
284
|
+
to = toAddresses.flatMap(
|
|
285
|
+
(addr) => addr.value?.map((v) => v.address || "") || []
|
|
286
|
+
);
|
|
287
|
+
} else {
|
|
288
|
+
to = toAddresses.value?.map((v) => v.address || "") || [];
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const headers = {};
|
|
292
|
+
for (const [key, value] of parsed.headers) {
|
|
293
|
+
headers[key] = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
294
|
+
}
|
|
295
|
+
const attachments = (parsed.attachments || []).map((att) => ({
|
|
296
|
+
filename: att.filename || "unknown",
|
|
297
|
+
contentType: att.contentType || "application/octet-stream",
|
|
298
|
+
size: att.size || 0
|
|
299
|
+
}));
|
|
300
|
+
return {
|
|
301
|
+
from,
|
|
302
|
+
to,
|
|
303
|
+
subject: parsed.subject || "",
|
|
304
|
+
body: parsed.text || "",
|
|
305
|
+
html: parsed.html || void 0,
|
|
306
|
+
attachments,
|
|
307
|
+
headers,
|
|
308
|
+
date: parsed.date
|
|
309
|
+
};
|
|
310
|
+
}
|
|
276
311
|
|
|
277
312
|
// src/index.ts
|
|
278
313
|
var index_default = Mailhooks;
|
|
279
314
|
|
|
280
|
-
export { EmailsResource, Mailhooks, constructSignature, index_default as default, parseWebhookPayload, verifyWebhookSignature };
|
|
315
|
+
export { EmailsResource, Mailhooks, constructSignature, index_default as default, parseEml, parseWebhookPayload, verifyWebhookSignature };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mailhooks/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "TypeScript SDK for Mailhooks API",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -22,9 +22,11 @@
|
|
|
22
22
|
"api"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"axios": "^1.6.0"
|
|
25
|
+
"axios": "^1.6.0",
|
|
26
|
+
"mailparser": "^3.7.2"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
29
|
+
"@types/mailparser": "^3.4.6",
|
|
28
30
|
"@types/node": "^20.19.0",
|
|
29
31
|
"dotenv": "^17.2.1",
|
|
30
32
|
"tsup": "^8.5.1",
|