@mailhooks/sdk 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
- // Spam detection
270
- spamScore?: number; // 0.0 to 1.0
271
- isSpam?: boolean;
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
@@ -168,6 +168,8 @@ interface WebhookPayload {
168
168
  filename: string;
169
169
  contentType: string;
170
170
  size: number;
171
+ /** Storage path for the attachment (only present when usesCustomStorage is true) */
172
+ storagePath?: string;
171
173
  }>;
172
174
  /** ISO 8601 timestamp when the email was received */
173
175
  receivedAt: string;
@@ -274,4 +276,61 @@ declare function parseWebhookPayload(body: string): WebhookPayload;
274
276
  */
275
277
  declare function constructSignature(payload: string | Buffer, secret: string): string;
276
278
 
277
- 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 };
279
+ /**
280
+ * Parsed email structure returned by parseEml.
281
+ * Matches the webhook payload structure for consistency.
282
+ */
283
+ interface ParsedEmail {
284
+ /** Sender email address */
285
+ from: string;
286
+ /** Array of recipient email addresses */
287
+ to: string[];
288
+ /** Email subject line */
289
+ subject: string;
290
+ /** Plain text body of the email */
291
+ body: string;
292
+ /** HTML body of the email (if available) */
293
+ html?: string;
294
+ /** Array of attachment metadata */
295
+ attachments: Array<{
296
+ filename: string;
297
+ contentType: string;
298
+ size: number;
299
+ }>;
300
+ /** Email headers as key-value pairs */
301
+ headers: Record<string, string>;
302
+ /** Date the email was sent (from Date header) */
303
+ date?: Date;
304
+ }
305
+ /**
306
+ * Parses a raw EML file and returns a structured email object.
307
+ *
308
+ * This is useful for BYOB (Bring Your Own Bucket) users who store emails
309
+ * in their own S3-compatible storage and need to parse them.
310
+ *
311
+ * @param eml - The raw EML content as a Buffer or string
312
+ * @returns A Promise that resolves to the parsed email
313
+ *
314
+ * @example
315
+ * ```typescript
316
+ * import { parseEml } from '@mailhooks/sdk';
317
+ * import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
318
+ *
319
+ * // Fetch EML from your S3 bucket
320
+ * const s3 = new S3Client({ region: 'us-east-1' });
321
+ * const response = await s3.send(new GetObjectCommand({
322
+ * Bucket: 'my-email-bucket',
323
+ * Key: storagePath, // from webhook payload
324
+ * }));
325
+ * const emlBuffer = Buffer.from(await response.Body.transformToByteArray());
326
+ *
327
+ * // Parse the EML
328
+ * const email = await parseEml(emlBuffer);
329
+ * console.log(email.from); // "sender@example.com"
330
+ * console.log(email.subject); // "Hello World"
331
+ * console.log(email.attachments); // [{ filename: "doc.pdf", ... }]
332
+ * ```
333
+ */
334
+ declare function parseEml(eml: Buffer | string): Promise<ParsedEmail>;
335
+
336
+ 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 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.1.0",
3
+ "version": "2.2.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",