@remit/mailbox-service 0.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.
Files changed (124) hide show
  1. package/README.md +147 -0
  2. package/package.json +65 -0
  3. package/src/account-credentials.ts +128 -0
  4. package/src/adversarial-mime.e2e.test.ts +311 -0
  5. package/src/attribute-mapper.test.ts +78 -0
  6. package/src/attribute-mapper.ts +147 -0
  7. package/src/body-part-mapper.property.test.ts +354 -0
  8. package/src/body-part-mapper.ts +360 -0
  9. package/src/body-sync-queue.test.ts +124 -0
  10. package/src/body-sync-queue.ts +131 -0
  11. package/src/body-sync-terminal.test.ts +265 -0
  12. package/src/body-sync-terminal.ts +172 -0
  13. package/src/body-sync.ts +1202 -0
  14. package/src/connection-factory.ts +102 -0
  15. package/src/connection-test.ts +208 -0
  16. package/src/credentials.test.ts +181 -0
  17. package/src/filters/match.test.ts +181 -0
  18. package/src/filters/match.ts +143 -0
  19. package/src/filters/pipeline.ts +243 -0
  20. package/src/flag-push-terminal.test.ts +156 -0
  21. package/src/flag-push-terminal.ts +106 -0
  22. package/src/flag-push.test.ts +408 -0
  23. package/src/flag-push.ts +198 -0
  24. package/src/flag-queue.ts +500 -0
  25. package/src/heuristics/classifyByHeaders.test.ts +578 -0
  26. package/src/heuristics/classifyByHeaders.ts +369 -0
  27. package/src/heuristics/classifyPlacement.ts +96 -0
  28. package/src/heuristics/socialDomains.ts +23 -0
  29. package/src/heuristics/transactionalDomains.ts +26 -0
  30. package/src/imapflow-connect-retry.test.ts +108 -0
  31. package/src/imapflow-connection.e2e.test.ts +355 -0
  32. package/src/imapflow-connection.integ.test.ts +327 -0
  33. package/src/imapflow-connection.test.ts +161 -0
  34. package/src/imapflow-connection.ts +1374 -0
  35. package/src/index.ts +214 -0
  36. package/src/mailbox-cursor-rebuild.test.ts +171 -0
  37. package/src/mailbox-cursor-rebuild.ts +146 -0
  38. package/src/mailbox-cursor.test.ts +249 -0
  39. package/src/mailbox-cursor.ts +177 -0
  40. package/src/mailbox-management.test.ts +103 -0
  41. package/src/mailbox-management.ts +235 -0
  42. package/src/mailbox-queue.ts +258 -0
  43. package/src/mailbox-sync.test.ts +153 -0
  44. package/src/mailbox-sync.ts +590 -0
  45. package/src/message-move.ts +825 -0
  46. package/src/message-parser.ts +26 -0
  47. package/src/message-sync.ts +1021 -0
  48. package/src/mime-walker.test.ts +311 -0
  49. package/src/mime-walker.ts +305 -0
  50. package/src/outbox-queue.ts +294 -0
  51. package/src/pass-through-unit-of-work.ts +17 -0
  52. package/src/placement-move-terminal.test.ts +179 -0
  53. package/src/placement-move-terminal.ts +104 -0
  54. package/src/placement-move.ts +224 -0
  55. package/src/snippet.test.ts +213 -0
  56. package/src/snippet.ts +152 -0
  57. package/src/stale-message-reconcile.test.ts +82 -0
  58. package/src/stale-message-reconcile.ts +55 -0
  59. package/src/test-helpers/isolated-mailbox.ts +44 -0
  60. package/src/test-helpers/mailfuzz-connection.ts +27 -0
  61. package/src/test-helpers/mime-tree-generator.ts +461 -0
  62. package/src/text/normalizer.test.ts +223 -0
  63. package/src/text/normalizer.ts +140 -0
  64. package/src/types/mailparser-augment.d.ts +15 -0
  65. package/src/types/natural-porter-stemmer.d.ts +50 -0
  66. package/src/types/stopword.d.ts +69 -0
  67. package/src/types.ts +349 -0
  68. package/test/fixtures/mime/01-flat-text-plain.bodyparts.json +8 -0
  69. package/test/fixtures/mime/01-flat-text-plain.eml +10 -0
  70. package/test/fixtures/mime/01-flat-text-plain.expected.json +10 -0
  71. package/test/fixtures/mime/02-flat-text-html.bodyparts.json +8 -0
  72. package/test/fixtures/mime/02-flat-text-html.eml +10 -0
  73. package/test/fixtures/mime/02-flat-text-html.expected.json +10 -0
  74. package/test/fixtures/mime/03-alternative-text-html.bodyparts.json +20 -0
  75. package/test/fixtures/mime/03-alternative-text-html.eml +19 -0
  76. package/test/fixtures/mime/03-alternative-text-html.expected.json +16 -0
  77. package/test/fixtures/mime/04-mixed-html-pdf-attachment.bodyparts.json +22 -0
  78. package/test/fixtures/mime/04-mixed-html-pdf-attachment.eml +20 -0
  79. package/test/fixtures/mime/04-mixed-html-pdf-attachment.expected.json +16 -0
  80. package/test/fixtures/mime/05-mixed-html-octet-stream-pdf.bodyparts.json +22 -0
  81. package/test/fixtures/mime/05-mixed-html-octet-stream-pdf.eml +21 -0
  82. package/test/fixtures/mime/05-mixed-html-octet-stream-pdf.expected.json +16 -0
  83. package/test/fixtures/mime/06-related-html-cid-image.bodyparts.json +23 -0
  84. package/test/fixtures/mime/06-related-html-cid-image.eml +21 -0
  85. package/test/fixtures/mime/06-related-html-cid-image.expected.json +16 -0
  86. package/test/fixtures/mime/07-mixed-alternative-related-attachment.bodyparts.json +55 -0
  87. package/test/fixtures/mime/07-mixed-alternative-related-attachment.eml +47 -0
  88. package/test/fixtures/mime/07-mixed-alternative-related-attachment.expected.json +34 -0
  89. package/test/fixtures/mime/08-two-pdfs-same-filename.bodyparts.json +30 -0
  90. package/test/fixtures/mime/08-two-pdfs-same-filename.eml +26 -0
  91. package/test/fixtures/mime/08-two-pdfs-same-filename.expected.json +22 -0
  92. package/test/fixtures/mime/09-attachment-no-filename.bodyparts.json +21 -0
  93. package/test/fixtures/mime/09-attachment-no-filename.eml +20 -0
  94. package/test/fixtures/mime/09-attachment-no-filename.expected.json +16 -0
  95. package/test/fixtures/mime/10-calendar-invite.bodyparts.json +22 -0
  96. package/test/fixtures/mime/10-calendar-invite.eml +33 -0
  97. package/test/fixtures/mime/10-calendar-invite.expected.json +16 -0
  98. package/test/fixtures/mime/11-quoted-printable-text.bodyparts.json +8 -0
  99. package/test/fixtures/mime/11-quoted-printable-text.eml +10 -0
  100. package/test/fixtures/mime/11-quoted-printable-text.expected.json +10 -0
  101. package/test/fixtures/mime/12-base64-binary.bodyparts.json +22 -0
  102. package/test/fixtures/mime/12-base64-binary.eml +20 -0
  103. package/test/fixtures/mime/12-base64-binary.expected.json +16 -0
  104. package/test/fixtures/mime/13-message-rfc822-forward.bodyparts.json +22 -0
  105. package/test/fixtures/mime/13-message-rfc822-forward.eml +28 -0
  106. package/test/fixtures/mime/13-message-rfc822-forward.expected.json +16 -0
  107. package/test/fixtures/mime/14-deeply-nested-5-levels.bodyparts.json +38 -0
  108. package/test/fixtures/mime/14-deeply-nested-5-levels.eml +34 -0
  109. package/test/fixtures/mime/14-deeply-nested-5-levels.expected.json +10 -0
  110. package/test/fixtures/mime/15-empty-text-part.bodyparts.json +21 -0
  111. package/test/fixtures/mime/15-empty-text-part.eml +18 -0
  112. package/test/fixtures/mime/15-empty-text-part.expected.json +16 -0
  113. package/test/fixtures/mime/16-empty-non-text-part.bodyparts.json +30 -0
  114. package/test/fixtures/mime/16-empty-non-text-part.eml +24 -0
  115. package/test/fixtures/mime/16-empty-non-text-part.expected.json +22 -0
  116. package/test/fixtures/mime-adversarial-e2e/01-octet-stream-pdf.eml +21 -0
  117. package/test/fixtures/mime-adversarial-e2e/02-nested-related-cid-image.eml +31 -0
  118. package/test/fixtures/mime-adversarial-e2e/03-calendar-invite.eml +33 -0
  119. package/test/fixtures/mime-adversarial-e2e/04-forwarded-rfc822-with-attachments.eml +42 -0
  120. package/test/fixtures/mime-adversarial-e2e/05-tnef-winmail.eml +25 -0
  121. package/test/fixtures/mime-adversarial-e2e/06-8bit-non-ascii.eml +15 -0
  122. package/test/fixtures/mime-adversarial-e2e/07-quoted-printable-soft-breaks.eml +17 -0
  123. package/test/fixtures/mime-adversarial-e2e/08-alternative-empty-text.eml +18 -0
  124. package/tsconfig.json +8 -0
@@ -0,0 +1,30 @@
1
+ [
2
+ {
3
+ "partPath": "0",
4
+ "isMultipart": true,
5
+ "mediaType": "MULTIPART",
6
+ "mediaSubtype": "mixed"
7
+ },
8
+ {
9
+ "partPath": "1",
10
+ "isMultipart": false,
11
+ "mediaType": "TEXT",
12
+ "mediaSubtype": "html"
13
+ },
14
+ {
15
+ "partPath": "2",
16
+ "isMultipart": false,
17
+ "mediaType": "APPLICATION",
18
+ "mediaSubtype": "octet-stream",
19
+ "sizeOctets": 0
20
+ },
21
+ {
22
+ "partPath": "3",
23
+ "isMultipart": false,
24
+ "mediaType": "APPLICATION",
25
+ "mediaSubtype": "pdf",
26
+ "disposition": "attachment",
27
+ "dispositionFilename": "real.pdf",
28
+ "sizeOctets": 9
29
+ }
30
+ ]
@@ -0,0 +1,24 @@
1
+ From: alice@example.org
2
+ To: bob@example.org
3
+ Subject: Empty non-text part fixture 16
4
+ Date: Thu, 29 May 2026 09:00:00 +0000
5
+ Message-Id: <fixture-16@example.org>
6
+ MIME-Version: 1.0
7
+ Content-Type: multipart/mixed; boundary="=_boundary_16_="
8
+
9
+ --=_boundary_16_=
10
+ Content-Type: text/html; charset=us-ascii
11
+ Content-Transfer-Encoding: 7bit
12
+
13
+ <html><body><p>HTML body alongside an empty octet-stream part and one real PDF.</p></body></html>
14
+ --=_boundary_16_=
15
+ Content-Type: application/octet-stream
16
+ Content-Transfer-Encoding: 7bit
17
+
18
+ --=_boundary_16_=
19
+ Content-Type: application/pdf; name=real.pdf
20
+ Content-Transfer-Encoding: base64
21
+ Content-Disposition: attachment; filename=real.pdf
22
+
23
+ JVBERi0xLjQK
24
+ --=_boundary_16_=--
@@ -0,0 +1,22 @@
1
+ {
2
+ "pairs": [
3
+ {
4
+ "partPath": "1",
5
+ "contentType": "text/html",
6
+ "contentSha256": "d7608876ac9c289794b8690e2979fba30e6451c7b5dd7337b34d4f4d577e3185",
7
+ "contentLength": 97
8
+ },
9
+ {
10
+ "partPath": "2",
11
+ "contentType": "application/octet-stream",
12
+ "contentSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
13
+ "contentLength": 0
14
+ },
15
+ {
16
+ "partPath": "3",
17
+ "contentType": "application/pdf",
18
+ "contentSha256": "e5c62df5dab5c87b6a015ef3d43597074d1eec433b15f51aec63b8582d0e4ab4",
19
+ "contentLength": 9
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,21 @@
1
+ From: alice@example.com
2
+ To: bob@example.com
3
+ Subject: Adversarial fixture 01 octet-stream PDF (Odido shape, #394)
4
+ Date: Fri, 29 May 2026 09:00:00 +0000
5
+ Message-Id: <adv-01-octet-stream-pdf@example.com>
6
+ MIME-Version: 1.0
7
+ Content-Type: multipart/mixed; boundary="=_adv01_="
8
+
9
+ --=_adv01_=
10
+ Content-Type: text/html; charset=us-ascii
11
+ Content-Transfer-Encoding: 7bit
12
+
13
+ <html><body><p>Your contract PDF is attached.</p></body></html>
14
+ --=_adv01_=
15
+ Content-Type: application/octet-stream; name="contract.pdf"
16
+ Content-Transfer-Encoding: base64
17
+ Content-Disposition: attachment; filename="contract.pdf"
18
+
19
+ JVBERi0xLjQKJWZha2UgcGRmIGZvciBhZHZlcnNhcmlhbCBmaXh0dXJlIDAxIG9j
20
+ dGV0LXN0cmVhbQolRU9GCg==
21
+ --=_adv01_=--
@@ -0,0 +1,31 @@
1
+ From: alice@example.com
2
+ To: bob@example.com
3
+ Subject: Adversarial fixture 02 nested related html + cid image
4
+ Date: Fri, 29 May 2026 09:00:00 +0000
5
+ Message-Id: <adv-02-nested-related@example.com>
6
+ MIME-Version: 1.0
7
+ Content-Type: multipart/mixed; boundary="=_adv02_outer_="
8
+
9
+ --=_adv02_outer_=
10
+ Content-Type: multipart/related; boundary="=_adv02_inner_="; type="text/html"
11
+
12
+ --=_adv02_inner_=
13
+ Content-Type: text/html; charset=us-ascii
14
+ Content-Transfer-Encoding: 7bit
15
+
16
+ <html><body><p>Hi bob,</p><p>Inline logo: <img src="cid:logo@example.com" alt="logo"></p><p>-- alice</p></body></html>
17
+ --=_adv02_inner_=
18
+ Content-Type: image/png; name="logo.png"
19
+ Content-Transfer-Encoding: base64
20
+ Content-Disposition: inline; filename="logo.png"
21
+ Content-ID: <logo@example.com>
22
+
23
+ iVBORw0KGgpmaXh0dXJlMDJjaWRpbWFnZWJ5dGVz
24
+ --=_adv02_inner_=--
25
+ --=_adv02_outer_=
26
+ Content-Type: application/pdf; name="invoice.pdf"
27
+ Content-Transfer-Encoding: base64
28
+ Content-Disposition: attachment; filename="invoice.pdf"
29
+
30
+ JVBERi0xLjQKJWFkdiAwMiBmaXh0dXJlIGludm9pY2UKJUVPRgo=
31
+ --=_adv02_outer_=--
@@ -0,0 +1,33 @@
1
+ From: alice@example.com
2
+ To: bob@example.com
3
+ Subject: Adversarial fixture 03 calendar invite + html
4
+ Date: Fri, 29 May 2026 09:00:00 +0000
5
+ Message-Id: <adv-03-calendar@example.com>
6
+ MIME-Version: 1.0
7
+ Content-Type: multipart/mixed; boundary="=_adv03_="
8
+
9
+ --=_adv03_=
10
+ Content-Type: text/html; charset=us-ascii
11
+ Content-Transfer-Encoding: 7bit
12
+
13
+ <html><body><p>Hi bob,</p><p>Meeting invite for our sync on Monday is attached.</p><p>-- alice</p></body></html>
14
+ --=_adv03_=
15
+ Content-Type: text/calendar; method=REQUEST; charset=us-ascii; name="invite.ics"
16
+ Content-Transfer-Encoding: 7bit
17
+ Content-Disposition: attachment; filename="invite.ics"
18
+
19
+ BEGIN:VCALENDAR
20
+ VERSION:2.0
21
+ PRODID:-//Remit//Adversarial Fixture 03//EN
22
+ METHOD:REQUEST
23
+ BEGIN:VEVENT
24
+ UID:adv-03-event@example.com
25
+ DTSTAMP:20260529T090000Z
26
+ DTSTART:20260601T140000Z
27
+ DTEND:20260601T150000Z
28
+ SUMMARY:Adversarial fixture 03 sync
29
+ ORGANIZER:mailto:alice@example.com
30
+ ATTENDEE:mailto:bob@example.com
31
+ END:VEVENT
32
+ END:VCALENDAR
33
+ --=_adv03_=--
@@ -0,0 +1,42 @@
1
+ From: alice@example.com
2
+ To: bob@example.com
3
+ Subject: Adversarial fixture 04 forwarded rfc822 with inner attachment
4
+ Date: Fri, 29 May 2026 09:00:00 +0000
5
+ Message-Id: <adv-04-forwarded@example.com>
6
+ MIME-Version: 1.0
7
+ Content-Type: multipart/mixed; boundary="=_adv04_outer_="
8
+
9
+ --=_adv04_outer_=
10
+ Content-Type: text/plain; charset=us-ascii
11
+ Content-Transfer-Encoding: 7bit
12
+
13
+ Hi bob,
14
+
15
+ Forwarding the message below for your records. Note the original carries its own attachment.
16
+
17
+ -- alice
18
+ --=_adv04_outer_=
19
+ Content-Type: message/rfc822
20
+ Content-Disposition: attachment; filename="forwarded.eml"
21
+
22
+ From: carol@example.com
23
+ To: alice@example.com
24
+ Subject: Original message with attachment for fixture 04
25
+ Date: Thu, 28 May 2026 12:00:00 +0000
26
+ Message-Id: <adv-04-inner@example.com>
27
+ MIME-Version: 1.0
28
+ Content-Type: multipart/mixed; boundary="=_adv04_inner_="
29
+
30
+ --=_adv04_inner_=
31
+ Content-Type: text/plain; charset=us-ascii
32
+ Content-Transfer-Encoding: 7bit
33
+
34
+ Original message body before forwarding.
35
+ --=_adv04_inner_=
36
+ Content-Type: application/pdf; name="original.pdf"
37
+ Content-Transfer-Encoding: base64
38
+ Content-Disposition: attachment; filename="original.pdf"
39
+
40
+ JVBERi0xLjQKJWFkdiAwNCBpbm5lciBwZGYKJUVPRgo=
41
+ --=_adv04_inner_=--
42
+ --=_adv04_outer_=--
@@ -0,0 +1,25 @@
1
+ X-Remit-Fixture-Note: Synthetic TNEF stub for fixture 05. mailparser surfaces this as an application/ms-tnef attachment with filename "winmail.dat" — which is the #402 contract. The trailing BODYSTRUCTURE round-trip via imapflow is skipped for this shape (see EXPECTATIONS in adversarial-mime.e2e.test.ts). TODO(#402): replace with a real-world winmail.dat capture once we have one to ship.
2
+ From: alice@example.com
3
+ To: bob@example.com
4
+ Subject: Adversarial fixture 05 Outlook TNEF winmail.dat
5
+ Date: Fri, 29 May 2026 09:00:00 +0000
6
+ Message-Id: <adv-05-tnef@example.com>
7
+ MIME-Version: 1.0
8
+ Content-Type: multipart/mixed; boundary="=_adv05_="
9
+
10
+ --=_adv05_=
11
+ Content-Type: text/plain; charset=us-ascii
12
+ Content-Transfer-Encoding: 7bit
13
+
14
+ Hi bob,
15
+
16
+ This message carries the Microsoft TNEF (winmail.dat) shape that some Outlook senders still produce. Remit should list the attachment without crashing the body sync.
17
+
18
+ -- alice
19
+ --=_adv05_=
20
+ Content-Type: application/ms-tnef; name="winmail.dat"
21
+ Content-Transfer-Encoding: base64
22
+ Content-Disposition: attachment; filename="winmail.dat"
23
+
24
+ eJ8+IgEAAAABAAAACAAAAAAAAAA=
25
+ --=_adv05_=--
@@ -0,0 +1,15 @@
1
+ From: alice@example.com
2
+ To: bob@example.com
3
+ Subject: =?UTF-8?B?Q2Fmw6kgZmHDp2FkZSAtIGFkdmVyc2FyaWFsIGZpeHR1cmUgMDY=?=
4
+ Date: Fri, 29 May 2026 09:00:00 +0000
5
+ Message-Id: <adv-06-8bit@example.com>
6
+ MIME-Version: 1.0
7
+ Content-Type: text/plain; charset=utf-8
8
+ Content-Transfer-Encoding: 8bit
9
+
10
+ Hallo bob,
11
+
12
+ De café op de hoek heeft een prachtige façade. We’ll meet there — ben je vrij?
13
+
14
+ Groetjes,
15
+ alice
@@ -0,0 +1,17 @@
1
+ From: alice@example.com
2
+ To: bob@example.com
3
+ Subject: Adversarial fixture 07 quoted-printable with soft line breaks
4
+ Date: Fri, 29 May 2026 09:00:00 +0000
5
+ Message-Id: <adv-07-qp@example.com>
6
+ MIME-Version: 1.0
7
+ Content-Type: text/plain; charset=utf-8
8
+ Content-Transfer-Encoding: quoted-printable
9
+
10
+ Hi bob,
11
+
12
+ This paragraph deliberately uses quoted-printable soft line breaks so a sing=
13
+ le logical line is wrapped across multiple physical lines without inserting=
14
+ a real newline. The body-sync pipeline must reassemble it back into one l=
15
+ ogical line when rendering.
16
+
17
+ Also includes a non-ASCII token: caf=C3=A9 r=C3=A9union =E2=80=94 alice
@@ -0,0 +1,18 @@
1
+ From: alice@example.com
2
+ To: bob@example.com
3
+ Subject: Adversarial fixture 08 alternative with empty text/plain
4
+ Date: Fri, 29 May 2026 09:00:00 +0000
5
+ Message-Id: <adv-08-empty-text-alt@example.com>
6
+ MIME-Version: 1.0
7
+ Content-Type: multipart/alternative; boundary="=_adv08_="
8
+
9
+ --=_adv08_=
10
+ Content-Type: text/plain; charset=us-ascii
11
+ Content-Transfer-Encoding: 7bit
12
+
13
+ --=_adv08_=
14
+ Content-Type: text/html; charset=us-ascii
15
+ Content-Transfer-Encoding: 7bit
16
+
17
+ <html><body><p>Hi bob,</p><p>The text/plain sibling of this multipart/alternative is intentionally empty. The HTML part must still render.</p><p>-- alice</p></body></html>
18
+ --=_adv08_=--
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src/**/*.ts"]
8
+ }