arn-rawmime 0.1.8 → 0.1.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arn-rawmime",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "A lightweight, dependency-free raw MIME email builder with DKIM support.",
5
5
  "author": "ARNDESK",
6
6
  "type": "module",
@@ -38,7 +38,12 @@ export class MimeMessage {
38
38
  /**
39
39
  * Sets the style of the multipart boundary separator.
40
40
  */
41
- setBoundaryStyle(style: "google" | "outlook" | "apple-mail"): void;
41
+ setBoundaryStyle(style: "google_hex" | "outlook_exchange" | "outlook_nextpart" | "apple_mail_equal" | "apple_mail_dash" | "apple_webmail" | "yahoo_part" | "samsung_android" | "edomail" | "hex_segments" | "custom_dashes"): void;
42
+
43
+ /**
44
+ * Sets the style of the Message-ID generation.
45
+ */
46
+ setMessageIdStyle(style: "apple_uuid" | "yahoo_mobile" | "yahoo_web" | "gmail_ca" | "outlook_exchange" | "outlook_web" | "samsung_android" | "generic_uuid" | "timestamp_dot" | "generic" | "none"): void;
42
47
 
43
48
  /**
44
49
  * Sets whether to preserve the exact insertion order of MIME parts.
@@ -5,6 +5,8 @@ import { randomBytes } from "crypto";
5
5
  import { convert } from "html-to-text";
6
6
  import { DKIMSign } from "./dkim-signer.js";
7
7
  import { processMarkDown } from "./processMarkDown.js";
8
+ import { generateBoundary, ALLOWED_BOUNDARY_STYLES } from "./utility/boundaryGenerator.js";
9
+ import { generateMessageId, ALLOWED_MESSAGE_ID_STYLES } from "./utility/messageIdGenerator.js";
8
10
 
9
11
  const desiredOrder = [
10
12
  "List-Unsubscribe-Post",
@@ -29,7 +31,8 @@ class MimeMessage {
29
31
  this.attachmentPromises = [];
30
32
  this.processingPromises = [];
31
33
  this.removedHeaders = new Set();
32
- this.boundaryStyle = "google";
34
+ this.boundaryStyle = "google_hex";
35
+ this.messageIdStyle = "gmail_ca";
33
36
  this.preserveOrder = false;
34
37
  }
35
38
 
@@ -38,13 +41,19 @@ class MimeMessage {
38
41
  }
39
42
 
40
43
  setBoundaryStyle(style) {
41
- const allowed = ["google", "outlook", "apple-mail"];
42
- if (!allowed.includes(style)) {
43
- throw new Error(`Invalid boundary style: ${style}. Allowed: ${allowed.join(", ")}`);
44
+ if (!ALLOWED_BOUNDARY_STYLES.includes(style)) {
45
+ throw new Error(`Invalid boundary style: ${style}. Allowed: ${ALLOWED_BOUNDARY_STYLES.join(", ")}`);
44
46
  }
45
47
  this.boundaryStyle = style;
46
48
  }
47
49
 
50
+ setMessageIdStyle(style) {
51
+ if (!ALLOWED_MESSAGE_ID_STYLES.includes(style)) {
52
+ throw new Error(`Invalid Message-ID style: ${style}. Allowed: ${ALLOWED_MESSAGE_ID_STYLES.join(", ")}`);
53
+ }
54
+ this.messageIdStyle = style;
55
+ }
56
+
48
57
  // ─── HELPER: HTML to Text Converter ─────────────────────────────
49
58
  _htmlToText(html) {
50
59
  if (!html) return "";
@@ -366,7 +375,6 @@ class MimeMessage {
366
375
 
367
376
  messageId(options = {}) {
368
377
  const { id, domain } = options;
369
- let localPart = id ? id : "CA" + this._generateRandomString(49);
370
378
  let msgDomain = domain;
371
379
  if (!msgDomain) {
372
380
  const fromHeader = this.headers.find((h) => h.name.toLowerCase() === "from");
@@ -379,8 +387,17 @@ class MimeMessage {
379
387
  msgDomain = "mail.gmail.com";
380
388
  }
381
389
  }
382
- const fullMessageId = `<${localPart.trim()}@${msgDomain.trim()}>`;
383
- this.updateHeader("Message-ID", fullMessageId);
390
+ if (id) {
391
+ const fullMessageId = `<${id.trim()}@${msgDomain.trim()}>`;
392
+ this.updateHeader("Message-ID", fullMessageId);
393
+ } else {
394
+ const generated = generateMessageId(this.messageIdStyle, msgDomain);
395
+ if (generated) {
396
+ this.updateHeader("Message-ID", generated);
397
+ } else {
398
+ this.removeHeaders(["Message-ID"]);
399
+ }
400
+ }
384
401
  }
385
402
 
386
403
  _generateRandomString(length) {
@@ -597,34 +614,7 @@ class MimeMessage {
597
614
  }
598
615
 
599
616
  generateBoundary(oldBoundary) {
600
- if (oldBoundary) {
601
- const prefix = oldBoundary.slice(0, 17);
602
- let newPart;
603
- do {
604
- newPart = randomBytes(6).toString("hex").slice(0, 11);
605
- } while (prefix + newPart === oldBoundary);
606
- return prefix + newPart;
607
- }
608
-
609
- if (this.boundaryStyle === "outlook") {
610
- // Office 365/Outlook format: _000_HashedIDapcp_
611
- const middle = randomBytes(24).toString("hex").toUpperCase();
612
- return `_000_${middle}apcp_`;
613
- } else if (this.boundaryStyle === "apple-mail") {
614
- // Apple Mail style: Apple-Mail=_UUID
615
- const uuid = randomBytes(16).toString("hex").toUpperCase();
616
- const formattedUuid = [
617
- uuid.slice(0, 8),
618
- uuid.slice(8, 12),
619
- uuid.slice(12, 16),
620
- uuid.slice(16, 20),
621
- uuid.slice(20),
622
- ].join("-");
623
- return `Apple-Mail=_${formattedUuid}`;
624
- } else {
625
- // Default Google style
626
- return "000000000000" + randomBytes(8).toString("hex");
627
- }
617
+ return generateBoundary(this.boundaryStyle, oldBoundary);
628
618
  }
629
619
 
630
620
  // ─── BUILDING THE RAW EMAIL ───────────────────────────────────────
@@ -0,0 +1,113 @@
1
+ import { randomBytes } from "crypto";
2
+
3
+ export const ALLOWED_BOUNDARY_STYLES = [
4
+ "google_hex",
5
+ "outlook_exchange",
6
+ "outlook_nextpart",
7
+ "apple_mail_equal",
8
+ "apple_mail_dash",
9
+ "apple_webmail",
10
+ "yahoo_part",
11
+ "samsung_android",
12
+ "edomail",
13
+ "hex_segments",
14
+ "custom_dashes"
15
+ ];
16
+
17
+ export function generateBoundary(style, oldBoundary) {
18
+ if (oldBoundary) {
19
+ const prefix = oldBoundary.slice(0, 17);
20
+ let newPart;
21
+ do {
22
+ newPart = randomBytes(6).toString("hex").slice(0, 11);
23
+ } while (prefix + newPart === oldBoundary);
24
+ return prefix + newPart;
25
+ }
26
+
27
+ switch (style) {
28
+ case "google_hex":
29
+ return "000000000000" + randomBytes(8).toString("hex");
30
+
31
+ case "outlook_exchange": {
32
+ const middle = randomBytes(24).toString("hex").toUpperCase();
33
+ return `_000_${middle}apcp_`;
34
+ }
35
+
36
+ case "outlook_nextpart": {
37
+ const part1 = Math.floor(Math.random() * 9000000) + 1000000;
38
+ const part2 = Math.floor(Math.random() * 9000000000) + 1000000000;
39
+ const timestamp = Date.now();
40
+ return `----=_NextPart_${part1}_${part2}.${timestamp}`;
41
+ }
42
+
43
+ case "apple_mail_equal": {
44
+ const uuid = randomBytes(16).toString("hex").toUpperCase();
45
+ const formattedUuid = [
46
+ uuid.slice(0, 8),
47
+ uuid.slice(8, 12),
48
+ uuid.slice(12, 16),
49
+ uuid.slice(16, 20),
50
+ uuid.slice(20)
51
+ ].join("-");
52
+ return `Apple-Mail=_${formattedUuid}`;
53
+ }
54
+
55
+ case "apple_mail_dash": {
56
+ const uuid = randomBytes(16).toString("hex").toUpperCase();
57
+ const formattedUuid = [
58
+ uuid.slice(0, 8),
59
+ uuid.slice(8, 12),
60
+ uuid.slice(12, 16),
61
+ uuid.slice(16, 20),
62
+ uuid.slice(20)
63
+ ].join("-");
64
+ return `Apple-Mail-_${formattedUuid}`;
65
+ }
66
+
67
+ case "apple_webmail": {
68
+ const uuid = randomBytes(16).toString("hex").toUpperCase();
69
+ const formattedUuid = [
70
+ uuid.slice(0, 8),
71
+ uuid.slice(8, 12),
72
+ uuid.slice(12, 16),
73
+ uuid.slice(16, 20),
74
+ uuid.slice(20)
75
+ ].join("-");
76
+ return `Apple-Webmail-_${formattedUuid}`;
77
+ }
78
+
79
+ case "yahoo_part": {
80
+ const part1 = Math.floor(Math.random() * 9000000) + 1000000;
81
+ const part2 = Math.floor(Math.random() * 9000000000) + 1000000000;
82
+ const timestamp = Date.now();
83
+ return `----=_Part_${part1}_${part2}.${timestamp}`;
84
+ }
85
+
86
+ case "samsung_android": {
87
+ const timestamp = Date.now();
88
+ const rand = Math.floor(Math.random() * 900000000) + 100000000;
89
+ return `_com.samsung.android.email_${timestamp}_${rand}`;
90
+ }
91
+
92
+ case "edomail": {
93
+ const uuid = randomBytes(16).toString("hex").toUpperCase();
94
+ return `EdoMail_${uuid}`;
95
+ }
96
+
97
+ case "hex_segments": {
98
+ const seg1 = randomBytes(4).toString("hex");
99
+ const seg2 = randomBytes(4).toString("hex");
100
+ const seg3 = randomBytes(2).toString("hex");
101
+ return `${seg1}_${seg2}_${seg3}`;
102
+ }
103
+
104
+ case "custom_dashes": {
105
+ const randStr = Math.random().toString(36).substring(2, 15).toUpperCase();
106
+ return `----${randStr}`;
107
+ }
108
+
109
+ default:
110
+ // Default fallback
111
+ return "000000000000" + randomBytes(8).toString("hex");
112
+ }
113
+ }
@@ -0,0 +1,106 @@
1
+ import { randomBytes } from "crypto";
2
+
3
+ export const ALLOWED_MESSAGE_ID_STYLES = [
4
+ "apple_uuid",
5
+ "yahoo_mobile",
6
+ "yahoo_web",
7
+ "gmail_ca",
8
+ "outlook_exchange",
9
+ "outlook_web",
10
+ "samsung_android",
11
+ "generic_uuid",
12
+ "timestamp_dot",
13
+ "generic",
14
+ "none"
15
+ ];
16
+
17
+ function generateRandomString(length) {
18
+ const allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=";
19
+ const bytes = randomBytes(length);
20
+ let result = "";
21
+ for (let i = 0; i < length; i++) {
22
+ result += allowed[bytes[i] % allowed.length];
23
+ }
24
+ return result;
25
+ }
26
+
27
+ export function generateMessageId(style, domain) {
28
+ if (style === "none") return null;
29
+
30
+ const msgDomain = domain || "mail.gmail.com";
31
+
32
+ switch (style) {
33
+ case "apple_uuid": {
34
+ const uuid = randomBytes(16).toString("hex").toUpperCase();
35
+ const formattedUuid = [
36
+ uuid.slice(0, 8),
37
+ uuid.slice(8, 12),
38
+ uuid.slice(12, 16),
39
+ uuid.slice(16, 20),
40
+ uuid.slice(20)
41
+ ].join("-");
42
+ return `<${formattedUuid}@${msgDomain}>`;
43
+ }
44
+
45
+ case "yahoo_mobile": {
46
+ const part1 = Math.floor(Math.random() * 900000000) + 100000000;
47
+ const part2 = Math.floor(Math.random() * 9000000) + 1000000;
48
+ const timestamp = Date.now();
49
+ return `<${part1}.${part2}.${timestamp}@mail.yahoo.com>`;
50
+ }
51
+
52
+ case "yahoo_web": {
53
+ const part1 = Math.floor(Math.random() * 9000000) + 1000000;
54
+ const part2 = Math.floor(Math.random() * 9000000) + 1000000;
55
+ const timestamp = Date.now();
56
+ return `<Part_${part1}_${part2}.${timestamp}@mail.yahoo.com>`;
57
+ }
58
+
59
+ case "gmail_ca": {
60
+ const randStr = generateRandomString(49);
61
+ return `<CA${randStr}@${msgDomain}>`;
62
+ }
63
+
64
+ case "outlook_exchange": {
65
+ const rand1 = randomBytes(12).toString("hex").toUpperCase();
66
+ const rand2 = randomBytes(6).toString("hex").toLowerCase();
67
+ return `<${rand1}apcp@${rand2}.prod.outlook.com>`;
68
+ }
69
+
70
+ case "outlook_web": {
71
+ const rand = randomBytes(16).toString("hex").toUpperCase();
72
+ return `<${rand}@${msgDomain}>`;
73
+ }
74
+
75
+ case "samsung_android": {
76
+ const timestamp = Date.now();
77
+ const rand1 = Math.floor(Math.random() * 9000000) + 1000000;
78
+ const rand2 = Math.floor(Math.random() * 900000000) + 100000000;
79
+ return `<${timestamp}.${rand1}.${rand2}@com.samsung.android.email>`;
80
+ }
81
+
82
+ case "generic_uuid": {
83
+ const uuid = randomBytes(16).toString("hex").toLowerCase();
84
+ const formattedUuid = [
85
+ uuid.slice(0, 8),
86
+ uuid.slice(8, 12),
87
+ uuid.slice(12, 16),
88
+ uuid.slice(16, 20),
89
+ uuid.slice(20)
90
+ ].join("-");
91
+ return `<${formattedUuid}@${msgDomain}>`;
92
+ }
93
+
94
+ case "timestamp_dot": {
95
+ const timestamp = Date.now();
96
+ const rand = Math.floor(Math.random() * 900000) + 100000;
97
+ return `<${timestamp}.${rand}@${msgDomain}>`;
98
+ }
99
+
100
+ case "generic":
101
+ default: {
102
+ const randStr = generateRandomString(40);
103
+ return `<${randStr}@${msgDomain}>`;
104
+ }
105
+ }
106
+ }