@bobfrankston/mailx 1.0.12 → 1.0.13

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 (237) hide show
  1. package/bin/mailx.js +47 -29
  2. package/client/app.js +93 -13
  3. package/client/components/folder-tree.js +84 -3
  4. package/client/components/message-list.js +134 -8
  5. package/client/components/message-viewer.js +130 -13
  6. package/client/compose/compose.html +4 -4
  7. package/client/compose/compose.js +53 -34
  8. package/client/index.html +33 -9
  9. package/client/lib/api-client.js +102 -30
  10. package/client/lib/mailxapi.js +123 -0
  11. package/client/package.json +1 -1
  12. package/client/styles/components.css +188 -15
  13. package/client/styles/layout.css +2 -1
  14. package/killmail.cmd +6 -0
  15. package/launch.ps1 +47 -5
  16. package/launcher/bin/mailx-app.exe +0 -0
  17. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Breadcrumbs +0 -0
  18. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/metadata +0 -0
  19. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/settings.dat +0 -0
  20. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/throttle_store.dat +1 -0
  21. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/CrashpadMetrics-active.pma +0 -0
  22. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/BrowsingTopicsSiteData +0 -0
  23. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Cache/No_Vary_Search/journal.baj +1 -0
  24. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/DIPS +0 -0
  25. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/DashTrackerDatabase +0 -0
  26. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/EdgeJourneys/EdgeJourneys.db +0 -0
  27. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/LOCK +0 -0
  28. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/LOG +3 -0
  29. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/MANIFEST-000001 +0 -0
  30. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/LOCK +0 -0
  31. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/LOG +3 -0
  32. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/MANIFEST-000001 +0 -0
  33. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/LOCK +0 -0
  34. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/LOG +3 -0
  35. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/MANIFEST-000001 +0 -0
  36. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ExtensionActivityComp +0 -0
  37. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ExtensionActivityEdge +0 -0
  38. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Favicons +0 -0
  39. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/History +0 -0
  40. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/History-journal +0 -0
  41. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/LOCK +0 -0
  42. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/LOG +3 -0
  43. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/MANIFEST-000001 +0 -0
  44. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOCK +0 -0
  45. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOG +3 -0
  46. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/MANIFEST-000001 +0 -0
  47. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Login Data +0 -0
  48. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Login Data For Account +0 -0
  49. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network/Cookies +0 -0
  50. NEL +0 -0
  51. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network/Trust Tokens +0 -0
  52. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network Action Predictor +0 -0
  53. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Preferences +1 -0
  54. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Safe Browsing Network/Safe Browsing Cookies +0 -0
  55. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ServerCertificate +0 -0
  56. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/LOCK +0 -0
  57. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/LOG +3 -0
  58. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/MANIFEST-000001 +0 -0
  59. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Shared Dictionary/db +0 -0
  60. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/SharedStorage +0 -0
  61. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Shortcuts +0 -0
  62. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOCK +0 -0
  63. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOG +3 -0
  64. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/MANIFEST-000001 +0 -0
  65. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOCK +0 -0
  66. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOG +3 -0
  67. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/MANIFEST-000001 +0 -0
  68. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Top Sites +0 -0
  69. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Vpn Tokens +0 -0
  70. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Web Data +0 -0
  71. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Web Data-journal +0 -0
  72. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager +0 -0
  73. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager-journal +0 -0
  74. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/heavy_ad_intervention_opt_out.db +0 -0
  75. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/LOCK +0 -0
  76. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/LOG +3 -0
  77. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/MANIFEST-000001 +0 -0
  78. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOCK +0 -0
  79. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOG +3 -0
  80. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/MANIFEST-000001 +0 -0
  81. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/DeferredBrowserMetrics/BrowserMetrics-69CAD063-BE24.pma +0 -0
  82. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Local State +1 -0
  83. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Variations +1 -0
  84. package/launcher/bin/mailx-app.old.exe +0 -0
  85. package/package.json +1 -1
  86. package/packages/mailx-api/index.js +79 -26
  87. package/packages/mailx-core/index.d.ts +129 -0
  88. package/packages/mailx-core/index.js +323 -0
  89. package/packages/mailx-core/ipc.d.ts +13 -0
  90. package/packages/mailx-core/ipc.js +56 -0
  91. package/packages/mailx-core/package.json +18 -0
  92. package/packages/mailx-imap/index.d.ts +5 -1
  93. package/packages/mailx-imap/index.js +76 -14
  94. package/packages/mailx-server/index.js +42 -31
  95. package/packages/mailx-server/package.json +1 -2
  96. package/packages/mailx-settings/index.d.ts +1 -1
  97. package/packages/mailx-settings/index.js +21 -12
  98. package/packages/mailx-store/db.d.ts +5 -1
  99. package/packages/mailx-store/db.js +64 -12
  100. package/packages/mailx-store/file-store.d.ts +2 -8
  101. package/packages/mailx-store/file-store.js +7 -31
  102. package/packages/mailx-types/index.d.ts +3 -1
  103. package/.tswalk.json +0 -7396
  104. package/launcher/release.cmd +0 -4
  105. package/mailx.json +0 -9
  106. package/packages/mailx-api/node_modules/nodemailer/.ncurc.js +0 -9
  107. package/packages/mailx-api/node_modules/nodemailer/.prettierignore +0 -8
  108. package/packages/mailx-api/node_modules/nodemailer/.prettierrc +0 -12
  109. package/packages/mailx-api/node_modules/nodemailer/.prettierrc.js +0 -10
  110. package/packages/mailx-api/node_modules/nodemailer/.release-please-config.json +0 -9
  111. package/packages/mailx-api/node_modules/nodemailer/LICENSE +0 -16
  112. package/packages/mailx-api/node_modules/nodemailer/README.md +0 -86
  113. package/packages/mailx-api/node_modules/nodemailer/SECURITY.txt +0 -22
  114. package/packages/mailx-api/node_modules/nodemailer/eslint.config.js +0 -88
  115. package/packages/mailx-api/node_modules/nodemailer/lib/addressparser/index.js +0 -383
  116. package/packages/mailx-api/node_modules/nodemailer/lib/base64/index.js +0 -139
  117. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/index.js +0 -253
  118. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
  119. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  120. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/sign.js +0 -117
  121. package/packages/mailx-api/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
  122. package/packages/mailx-api/node_modules/nodemailer/lib/fetch/index.js +0 -280
  123. package/packages/mailx-api/node_modules/nodemailer/lib/json-transport/index.js +0 -82
  124. package/packages/mailx-api/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
  125. package/packages/mailx-api/node_modules/nodemailer/lib/mailer/index.js +0 -441
  126. package/packages/mailx-api/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
  127. package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
  128. package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
  129. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
  130. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
  131. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
  132. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
  133. package/packages/mailx-api/node_modules/nodemailer/lib/nodemailer.js +0 -157
  134. package/packages/mailx-api/node_modules/nodemailer/lib/punycode/index.js +0 -460
  135. package/packages/mailx-api/node_modules/nodemailer/lib/qp/index.js +0 -227
  136. package/packages/mailx-api/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
  137. package/packages/mailx-api/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
  138. package/packages/mailx-api/node_modules/nodemailer/lib/shared/index.js +0 -754
  139. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
  140. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
  141. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
  142. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
  143. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
  144. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
  145. package/packages/mailx-api/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
  146. package/packages/mailx-api/node_modules/nodemailer/lib/well-known/index.js +0 -47
  147. package/packages/mailx-api/node_modules/nodemailer/lib/well-known/services.json +0 -611
  148. package/packages/mailx-api/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
  149. package/packages/mailx-api/node_modules/nodemailer/package.json +0 -47
  150. package/packages/mailx-imap/node_modules/nodemailer/.ncurc.js +0 -9
  151. package/packages/mailx-imap/node_modules/nodemailer/.prettierignore +0 -8
  152. package/packages/mailx-imap/node_modules/nodemailer/.prettierrc +0 -12
  153. package/packages/mailx-imap/node_modules/nodemailer/.prettierrc.js +0 -10
  154. package/packages/mailx-imap/node_modules/nodemailer/.release-please-config.json +0 -9
  155. package/packages/mailx-imap/node_modules/nodemailer/LICENSE +0 -16
  156. package/packages/mailx-imap/node_modules/nodemailer/README.md +0 -86
  157. package/packages/mailx-imap/node_modules/nodemailer/SECURITY.txt +0 -22
  158. package/packages/mailx-imap/node_modules/nodemailer/eslint.config.js +0 -88
  159. package/packages/mailx-imap/node_modules/nodemailer/lib/addressparser/index.js +0 -383
  160. package/packages/mailx-imap/node_modules/nodemailer/lib/base64/index.js +0 -139
  161. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/index.js +0 -253
  162. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
  163. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  164. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/sign.js +0 -117
  165. package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
  166. package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/index.js +0 -280
  167. package/packages/mailx-imap/node_modules/nodemailer/lib/json-transport/index.js +0 -82
  168. package/packages/mailx-imap/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
  169. package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/index.js +0 -441
  170. package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
  171. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
  172. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
  173. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
  174. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
  175. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
  176. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
  177. package/packages/mailx-imap/node_modules/nodemailer/lib/nodemailer.js +0 -157
  178. package/packages/mailx-imap/node_modules/nodemailer/lib/punycode/index.js +0 -460
  179. package/packages/mailx-imap/node_modules/nodemailer/lib/qp/index.js +0 -227
  180. package/packages/mailx-imap/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
  181. package/packages/mailx-imap/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
  182. package/packages/mailx-imap/node_modules/nodemailer/lib/shared/index.js +0 -754
  183. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
  184. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
  185. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
  186. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
  187. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
  188. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
  189. package/packages/mailx-imap/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
  190. package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/index.js +0 -47
  191. package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/services.json +0 -611
  192. package/packages/mailx-imap/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
  193. package/packages/mailx-imap/node_modules/nodemailer/package.json +0 -47
  194. package/packages/mailx-send/node_modules/nodemailer/.ncurc.js +0 -9
  195. package/packages/mailx-send/node_modules/nodemailer/.prettierignore +0 -8
  196. package/packages/mailx-send/node_modules/nodemailer/.prettierrc +0 -12
  197. package/packages/mailx-send/node_modules/nodemailer/.prettierrc.js +0 -10
  198. package/packages/mailx-send/node_modules/nodemailer/.release-please-config.json +0 -9
  199. package/packages/mailx-send/node_modules/nodemailer/LICENSE +0 -16
  200. package/packages/mailx-send/node_modules/nodemailer/README.md +0 -86
  201. package/packages/mailx-send/node_modules/nodemailer/SECURITY.txt +0 -22
  202. package/packages/mailx-send/node_modules/nodemailer/eslint.config.js +0 -88
  203. package/packages/mailx-send/node_modules/nodemailer/lib/addressparser/index.js +0 -383
  204. package/packages/mailx-send/node_modules/nodemailer/lib/base64/index.js +0 -139
  205. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/index.js +0 -253
  206. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
  207. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  208. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/sign.js +0 -117
  209. package/packages/mailx-send/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
  210. package/packages/mailx-send/node_modules/nodemailer/lib/fetch/index.js +0 -280
  211. package/packages/mailx-send/node_modules/nodemailer/lib/json-transport/index.js +0 -82
  212. package/packages/mailx-send/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
  213. package/packages/mailx-send/node_modules/nodemailer/lib/mailer/index.js +0 -441
  214. package/packages/mailx-send/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
  215. package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
  216. package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
  217. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
  218. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
  219. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
  220. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
  221. package/packages/mailx-send/node_modules/nodemailer/lib/nodemailer.js +0 -157
  222. package/packages/mailx-send/node_modules/nodemailer/lib/punycode/index.js +0 -460
  223. package/packages/mailx-send/node_modules/nodemailer/lib/qp/index.js +0 -227
  224. package/packages/mailx-send/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
  225. package/packages/mailx-send/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
  226. package/packages/mailx-send/node_modules/nodemailer/lib/shared/index.js +0 -754
  227. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
  228. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
  229. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
  230. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
  231. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
  232. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
  233. package/packages/mailx-send/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
  234. package/packages/mailx-send/node_modules/nodemailer/lib/well-known/index.js +0 -47
  235. package/packages/mailx-send/node_modules/nodemailer/lib/well-known/services.json +0 -611
  236. package/packages/mailx-send/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
  237. package/packages/mailx-send/node_modules/nodemailer/package.json +0 -47
@@ -0,0 +1,323 @@
1
+ /**
2
+ * @bobfrankston/mailx-core
3
+ * Core mail functions — callable directly via IPC or wrapped by Express.
4
+ * No HTTP, no Express, no WebSocket. Just plain async functions.
5
+ */
6
+ import { MailxDB } from "@bobfrankston/mailx-store";
7
+ import { ImapManager } from "@bobfrankston/mailx-imap";
8
+ import { loadSettings, saveSettings, loadAllowlist, saveAllowlist, getConfigDir, getStorePath, initLocalConfig } from "@bobfrankston/mailx-settings";
9
+ import { simpleParser } from "mailparser";
10
+ // ── Initialization ──
11
+ let db;
12
+ let imapManager;
13
+ const eventHandlers = [];
14
+ export function onEvent(handler) {
15
+ eventHandlers.push(handler);
16
+ }
17
+ function emit(event) {
18
+ for (const h of eventHandlers) {
19
+ try {
20
+ h(event);
21
+ }
22
+ catch { /* ignore handler errors */ }
23
+ }
24
+ }
25
+ export async function initialize() {
26
+ initLocalConfig();
27
+ const dbDir = getConfigDir();
28
+ db = new MailxDB(dbDir);
29
+ imapManager = new ImapManager(db);
30
+ // Seed contacts
31
+ const seeded = db.seedContactsFromMessages();
32
+ if (seeded > 0)
33
+ console.log(` Seeded ${seeded} contacts`);
34
+ // Search index — only if empty
35
+ let ftsCount = 0;
36
+ try {
37
+ ftsCount = db.db.prepare("SELECT COUNT(*) as cnt FROM messages_fts").get()?.cnt || 0;
38
+ }
39
+ catch { /* */ }
40
+ if (ftsCount === 0) {
41
+ const indexed = db.rebuildSearchIndex();
42
+ if (indexed > 0)
43
+ console.log(` Search index: ${indexed} messages`);
44
+ }
45
+ // Add accounts
46
+ const settings = loadSettings();
47
+ for (const account of settings.accounts) {
48
+ if (!account.enabled)
49
+ continue;
50
+ try {
51
+ await imapManager.addAccount(account);
52
+ console.log(` Account added: ${account.name} (${account.id})`);
53
+ }
54
+ catch (e) {
55
+ console.error(` Failed to add account ${account.id}: ${e.message}`);
56
+ }
57
+ }
58
+ // Wire events to push notifications
59
+ imapManager.on("syncProgress", (accountId, phase, progress) => {
60
+ emit({ type: "syncProgress", accountId, phase, progress });
61
+ });
62
+ imapManager.on("folderCountsChanged", (accountId, counts) => {
63
+ emit({ type: "folderCountsChanged", accountId, counts });
64
+ });
65
+ // Initial sync + IDLE
66
+ if (settings.accounts.some(a => a.enabled)) {
67
+ console.log(" Starting initial sync...");
68
+ imapManager.syncAll().then(async () => {
69
+ console.log(" Initial sync complete");
70
+ await imapManager.startWatching();
71
+ imapManager.syncAllContacts().catch(e => console.error(` Google Contacts sync error: ${e.message}`));
72
+ }).catch(e => {
73
+ console.error(` Initial sync error: ${e.message}`);
74
+ });
75
+ }
76
+ imapManager.startPeriodicSync(settings.sync.intervalMinutes);
77
+ imapManager.startOutboxWorker();
78
+ }
79
+ export async function shutdown() {
80
+ imapManager?.stopPeriodicSync();
81
+ imapManager?.stopOutboxWorker();
82
+ await imapManager?.shutdown();
83
+ db?.close();
84
+ }
85
+ // ── HTML Sanitization ──
86
+ function sanitizeHtml(html) {
87
+ let hasRemoteContent = false;
88
+ let clean = html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "");
89
+ clean = clean.replace(/\s+on\w+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, "");
90
+ clean = clean.replace(/<img\b([^>]*)\bsrc\s*=\s*("[^"]*"|'[^']*')/gi, (match, before, src) => {
91
+ const url = src.slice(1, -1);
92
+ if (url.startsWith("data:") || url.startsWith("cid:"))
93
+ return match;
94
+ hasRemoteContent = true;
95
+ return `<img${before}src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Crect fill='%23888' width='20' height='20' rx='3'/%3E%3Ctext x='10' y='14' text-anchor='middle' fill='white' font-size='12'%3E⊘%3C/text%3E%3C/svg%3E" data-blocked-src=${src} title="Remote image blocked"`;
96
+ });
97
+ clean = clean.replace(/<link\b[^>]*rel\s*=\s*["']stylesheet["'][^>]*>/gi, (match) => {
98
+ hasRemoteContent = true;
99
+ return `<!-- blocked: ${match.replace(/--/g, "")} -->`;
100
+ });
101
+ clean = clean.replace(/url\s*\(\s*(['"]?)(https?:\/\/[^)]+)\1\s*\)/gi, (_match, _q, url) => {
102
+ hasRemoteContent = true;
103
+ return `url("") /* blocked: ${url} */`;
104
+ });
105
+ clean = clean.replace(/<\/?form\b[^>]*>/gi, "");
106
+ clean = clean.replace(/<iframe\b[^>]*>[\s\S]*?<\/iframe>/gi, "");
107
+ return { html: clean, hasRemoteContent };
108
+ }
109
+ // ── API Functions ──
110
+ export function getAccounts() {
111
+ return db.getAccounts();
112
+ }
113
+ export function getFolders(params) {
114
+ return db.getFolders(params.accountId);
115
+ }
116
+ export function getMessages(params) {
117
+ return db.getMessages({
118
+ accountId: params.accountId,
119
+ folderId: params.folderId,
120
+ page: params.page || 1,
121
+ pageSize: params.pageSize || 50,
122
+ sort: params.sort || "date",
123
+ sortDir: params.sortDir || "desc",
124
+ search: params.search,
125
+ });
126
+ }
127
+ export function getUnifiedInbox(params) {
128
+ return db.getUnifiedInbox(params.page, params.pageSize);
129
+ }
130
+ export async function getMessage(params) {
131
+ const { accountId, uid } = params;
132
+ let allowRemote = params.allowRemote || false;
133
+ const envelope = db.getMessageByUid(accountId, uid, params.folderId);
134
+ if (!envelope)
135
+ throw new Error("Message not found");
136
+ let bodyHtml = "";
137
+ let bodyText = "";
138
+ let hasRemoteContent = false;
139
+ let attachments = [];
140
+ let deliveredTo = "";
141
+ let returnPath = "";
142
+ let listUnsubscribe = "";
143
+ const raw = await imapManager.fetchMessageBody(accountId, envelope.folderId, envelope.uid);
144
+ if (raw) {
145
+ const parsed = await simpleParser(raw);
146
+ bodyHtml = parsed.html || "";
147
+ bodyText = parsed.text || "";
148
+ attachments = (parsed.attachments || []).map((a, i) => ({
149
+ id: i,
150
+ filename: a.filename || `attachment-${i}`,
151
+ mimeType: a.contentType || "application/octet-stream",
152
+ size: a.size || 0,
153
+ contentId: a.contentId || "",
154
+ }));
155
+ // Extract useful headers for the UI
156
+ const hdr = (key) => {
157
+ const v = parsed.headers.get(key);
158
+ if (!v)
159
+ return "";
160
+ if (typeof v === "string")
161
+ return v;
162
+ if (typeof v === "object" && "text" in v)
163
+ return v.text || "";
164
+ if (typeof v === "object" && "value" in v)
165
+ return String(v.value);
166
+ return String(v);
167
+ };
168
+ deliveredTo = hdr("delivered-to");
169
+ returnPath = hdr("return-path").replace(/[<>]/g, "");
170
+ listUnsubscribe = hdr("list-unsubscribe");
171
+ }
172
+ if (bodyHtml && !allowRemote) {
173
+ const allowList = loadAllowlist();
174
+ const senderAddr = envelope.from?.address || "";
175
+ const senderDomain = senderAddr.split("@")[1] || "";
176
+ const toAddrs = (envelope.to || []).map((a) => a.address);
177
+ const isAllowed = allowList.senders.includes(senderAddr) ||
178
+ allowList.domains.includes(senderDomain) ||
179
+ toAddrs.some((a) => allowList.recipients?.includes(a));
180
+ if (isAllowed) {
181
+ allowRemote = true;
182
+ }
183
+ else {
184
+ const result = sanitizeHtml(bodyHtml);
185
+ bodyHtml = result.html;
186
+ hasRemoteContent = result.hasRemoteContent;
187
+ }
188
+ }
189
+ // Build .eml file path for "View Source"
190
+ const storePath = getStorePath();
191
+ const emlPath = `${storePath}/${accountId}/${envelope.folderId}/${envelope.uid}.eml`;
192
+ return { ...envelope, bodyHtml, bodyText, hasRemoteContent, remoteAllowed: allowRemote, attachments, deliveredTo, returnPath, listUnsubscribe, emlPath };
193
+ }
194
+ export async function updateFlags(params) {
195
+ const envelope = db.getMessageByUid(params.accountId, params.uid);
196
+ await imapManager.updateFlagsLocal(params.accountId, params.uid, envelope?.folderId || 0, params.flags);
197
+ }
198
+ export async function deleteMessage(params) {
199
+ const envelope = db.getMessageByUid(params.accountId, params.uid);
200
+ if (!envelope)
201
+ throw new Error("Message not found");
202
+ await imapManager.trashMessage(params.accountId, envelope.folderId, envelope.uid);
203
+ }
204
+ export async function undeleteMessage(params) {
205
+ await imapManager.undeleteMessage(params.accountId, params.uid, params.folderId);
206
+ }
207
+ export async function sendMessage(params) {
208
+ const settings = loadSettings();
209
+ const account = settings.accounts.find(a => a.id === params.from);
210
+ if (!account)
211
+ throw new Error(`Unknown account: ${params.from}`);
212
+ const to = params.to.map(a => a.name ? `${a.name} <${a.address}>` : a.address).join(", ");
213
+ const cc = params.cc?.map(a => a.name ? `${a.name} <${a.address}>` : a.address).join(", ");
214
+ const bcc = params.bcc?.map(a => a.name ? `${a.name} <${a.address}>` : a.address).join(", ");
215
+ const headers = [
216
+ `From: ${account.name} <${account.email}>`,
217
+ `To: ${to}`,
218
+ cc ? `Cc: ${cc}` : null,
219
+ bcc ? `Bcc: ${bcc}` : null,
220
+ `Subject: ${params.subject}`,
221
+ `Date: ${new Date().toUTCString()}`,
222
+ params.inReplyTo ? `In-Reply-To: ${params.inReplyTo}` : null,
223
+ params.references?.length ? `References: ${params.references.join(" ")}` : null,
224
+ `MIME-Version: 1.0`,
225
+ `Content-Type: text/html; charset=UTF-8`,
226
+ ].filter(h => h !== null).join("\r\n");
227
+ const rawMessage = `${headers}\r\n\r\n${params.bodyHtml || params.bodyText || ""}`;
228
+ imapManager.queueOutgoingLocal(account.id, rawMessage);
229
+ for (const addr of params.to)
230
+ db.recordSentAddress(addr.name, addr.address);
231
+ if (params.cc)
232
+ for (const addr of params.cc)
233
+ db.recordSentAddress(addr.name, addr.address);
234
+ if (params.bcc)
235
+ for (const addr of params.bcc)
236
+ db.recordSentAddress(addr.name, addr.address);
237
+ }
238
+ export async function saveDraft(params) {
239
+ const settings = loadSettings();
240
+ const account = settings.accounts.find(a => a.id === params.accountId);
241
+ if (!account)
242
+ throw new Error(`Unknown account: ${params.accountId}`);
243
+ const headers = [
244
+ `From: ${account.name} <${account.email}>`,
245
+ params.to ? `To: ${params.to}` : null,
246
+ params.cc ? `Cc: ${params.cc}` : null,
247
+ `Subject: ${params.subject || "(no subject)"}`,
248
+ `Date: ${new Date().toUTCString()}`,
249
+ `MIME-Version: 1.0`,
250
+ `Content-Type: text/html; charset=UTF-8`,
251
+ ].filter(h => h !== null).join("\r\n");
252
+ const raw = `${headers}\r\n\r\n${params.bodyHtml || params.bodyText || ""}`;
253
+ return await imapManager.saveDraft(params.accountId, raw, params.previousDraftUid);
254
+ }
255
+ export async function deleteDraft(params) {
256
+ await imapManager.deleteDraft(params.accountId, params.draftUid);
257
+ }
258
+ export function searchMessages(params) {
259
+ if (!params.query.trim())
260
+ return { items: [], total: 0, page: 1, pageSize: 50 };
261
+ return db.searchMessages(params.query, params.page || 1, params.pageSize || 50);
262
+ }
263
+ export function searchContacts(params) {
264
+ if (params.query.length < 1)
265
+ return [];
266
+ return db.searchContacts(params.query);
267
+ }
268
+ export async function syncAll() {
269
+ await imapManager.syncAll();
270
+ }
271
+ export function getSyncPending() {
272
+ return { pending: db.getTotalPendingSyncCount() };
273
+ }
274
+ export function allowRemoteContent(params) {
275
+ const list = loadAllowlist();
276
+ if (params.type === "sender" && !list.senders.includes(params.value))
277
+ list.senders.push(params.value);
278
+ else if (params.type === "domain" && !list.domains.includes(params.value))
279
+ list.domains.push(params.value);
280
+ else if (params.type === "recipient") {
281
+ if (!list.recipients)
282
+ list.recipients = [];
283
+ if (!list.recipients.includes(params.value))
284
+ list.recipients.push(params.value);
285
+ }
286
+ saveAllowlist(list);
287
+ }
288
+ export function getSettings() {
289
+ return loadSettings();
290
+ }
291
+ export function saveSettingsData(data) {
292
+ saveSettings(data);
293
+ }
294
+ export function rebuildSearchIndex() {
295
+ return db.rebuildSearchIndex();
296
+ }
297
+ export function seedContacts() {
298
+ return db.seedContactsFromMessages();
299
+ }
300
+ export async function syncGoogleContacts() {
301
+ await imapManager.syncAllContacts();
302
+ }
303
+ export function getVersion() {
304
+ return { server: "1.0.0", client: "1.0.0" }; // Updated by build
305
+ }
306
+ // ── Action dispatcher for IPC ──
307
+ const actions = {
308
+ getAccounts, getFolders, getMessages, getUnifiedInbox, getMessage,
309
+ updateFlags, deleteMessage, undeleteMessage, sendMessage,
310
+ saveDraft, deleteDraft, searchMessages, searchContacts,
311
+ syncAll, getSyncPending, allowRemoteContent,
312
+ getSettings, saveSettingsData, rebuildSearchIndex,
313
+ seedContacts, syncGoogleContacts, getVersion,
314
+ };
315
+ /** Dispatch an action by name — used by IPC and Express wrapper */
316
+ export async function dispatch(action, params = {}) {
317
+ const fn = actions[action];
318
+ if (!fn)
319
+ throw new Error(`Unknown action: ${action}`);
320
+ return await fn(params);
321
+ }
322
+ export { db, imapManager };
323
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * IPC protocol — stdin/stdout JSON lines for Rust↔Node communication.
3
+ * Rust launcher sends JSON requests, Node dispatches to core functions,
4
+ * sends JSON responses back. Events pushed to stdout as well.
5
+ *
6
+ * Protocol:
7
+ * Request: { "_action": "getMessages", "_cbid": "5", "accountId": "bobma", ... }
8
+ * Response: { "_type": "response", "_cbid": "5", "result": { ... } }
9
+ * Error: { "_type": "error", "_cbid": "5", "error": "message" }
10
+ * Event: { "_type": "event", "data": { "type": "folderCountsChanged", ... } }
11
+ */
12
+ export declare function startIPC(): Promise<void>;
13
+ //# sourceMappingURL=ipc.d.ts.map
@@ -0,0 +1,56 @@
1
+ /**
2
+ * IPC protocol — stdin/stdout JSON lines for Rust↔Node communication.
3
+ * Rust launcher sends JSON requests, Node dispatches to core functions,
4
+ * sends JSON responses back. Events pushed to stdout as well.
5
+ *
6
+ * Protocol:
7
+ * Request: { "_action": "getMessages", "_cbid": "5", "accountId": "bobma", ... }
8
+ * Response: { "_type": "response", "_cbid": "5", "result": { ... } }
9
+ * Error: { "_type": "error", "_cbid": "5", "error": "message" }
10
+ * Event: { "_type": "event", "data": { "type": "folderCountsChanged", ... } }
11
+ */
12
+ import * as readline from "node:readline";
13
+ import { dispatch, initialize, onEvent } from "./index.js";
14
+ export async function startIPC() {
15
+ // Initialize core
16
+ await initialize();
17
+ // Push events to stdout
18
+ onEvent((event) => {
19
+ const line = JSON.stringify({ _type: "event", data: event });
20
+ process.stdout.write(line + "\n");
21
+ });
22
+ // Read JSON lines from stdin
23
+ const rl = readline.createInterface({ input: process.stdin, terminal: false });
24
+ rl.on("line", async (line) => {
25
+ let msg;
26
+ try {
27
+ msg = JSON.parse(line);
28
+ }
29
+ catch {
30
+ return; // Skip malformed JSON
31
+ }
32
+ const { _action, _cbid, ...params } = msg;
33
+ if (!_action || !_cbid)
34
+ return;
35
+ try {
36
+ const result = await dispatch(_action, params);
37
+ process.stdout.write(JSON.stringify({ _type: "response", _cbid, result }) + "\n");
38
+ }
39
+ catch (e) {
40
+ process.stdout.write(JSON.stringify({ _type: "error", _cbid, error: e.message }) + "\n");
41
+ }
42
+ });
43
+ rl.on("close", () => {
44
+ console.error("IPC stdin closed, shutting down");
45
+ process.exit(0);
46
+ });
47
+ console.error("IPC ready");
48
+ }
49
+ // If run directly, start IPC mode
50
+ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("ipc.js")) {
51
+ startIPC().catch(e => {
52
+ console.error(`IPC startup error: ${e.message}`);
53
+ process.exit(1);
54
+ });
55
+ }
56
+ //# sourceMappingURL=ipc.js.map
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@bobfrankston/mailx-core",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc"
9
+ },
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "@bobfrankston/mailx-types": "file:../mailx-types",
13
+ "@bobfrankston/mailx-store": "file:../mailx-store",
14
+ "@bobfrankston/mailx-imap": "file:../mailx-imap",
15
+ "@bobfrankston/mailx-settings": "file:../mailx-settings",
16
+ "mailparser": "^3.7.2"
17
+ }
18
+ }
@@ -28,6 +28,8 @@ export declare class ImapManager extends EventEmitter {
28
28
  private syncing;
29
29
  private inboxSyncing;
30
30
  constructor(db: MailxDB);
31
+ /** Get OAuth access token for an account (for SMTP auth) */
32
+ getOAuthToken(accountId: string): Promise<string | null>;
31
33
  /** Create a fresh ImapClient for an account (disposable, single-use) */
32
34
  private createClient;
33
35
  /** Register an account */
@@ -59,6 +61,8 @@ export declare class ImapManager extends EventEmitter {
59
61
  trashMessage(accountId: string, folderId: number, uid: number): Promise<void>;
60
62
  /** Move a message between folders — local-first, queues IMAP sync */
61
63
  moveMessage(accountId: string, uid: number, fromFolderId: number, toFolderId: number): Promise<void>;
64
+ /** Move message across accounts using iflow's moveMessageToServer */
65
+ moveMessageCrossAccount(fromAccountId: string, uid: number, fromFolderId: number, toAccountId: string, toFolderId: number): Promise<void>;
62
66
  /** Undelete — move from Trash back to original folder */
63
67
  undeleteMessage(accountId: string, uid: number, originalFolderId: number): Promise<void>;
64
68
  /** Update flags — local-first, queues IMAP sync */
@@ -88,7 +92,7 @@ export declare class ImapManager extends EventEmitter {
88
92
  private processLocalQueue;
89
93
  /** Process Outbox — send pending messages with flag-based interlock */
90
94
  processOutbox(accountId: string): Promise<void>;
91
- /** Start background Outbox worker — checks every 10 seconds */
95
+ /** Start background Outbox worker — runs immediately then every 10 seconds */
92
96
  startOutboxWorker(): void;
93
97
  /** Stop Outbox worker */
94
98
  stopOutboxWorker(): void;
@@ -55,6 +55,13 @@ export class ImapManager extends EventEmitter {
55
55
  const storePath = getStorePath();
56
56
  this.bodyStore = new FileMessageStore(storePath);
57
57
  }
58
+ /** Get OAuth access token for an account (for SMTP auth) */
59
+ async getOAuthToken(accountId) {
60
+ const config = this.configs.get(accountId);
61
+ if (!config || !config.tokenProvider)
62
+ return null;
63
+ return config.tokenProvider();
64
+ }
58
65
  /** Create a fresh ImapClient for an account (disposable, single-use) */
59
66
  createClient(accountId) {
60
67
  const config = this.configs.get(accountId);
@@ -106,12 +113,7 @@ export class ImapManager extends EventEmitter {
106
113
  this.db.upsertFolder(accountId, folder.path, folder.name || folder.path.split(folder.delimiter || "/").pop() || folder.path, specialUse, folder.delimiter || "/");
107
114
  }
108
115
  this.emit("syncProgress", accountId, "folders", 100);
109
- const dbFolders = this.db.getFolders(accountId);
110
- // Register folder names for human-readable store paths
111
- for (const f of dbFolders) {
112
- this.bodyStore.registerFolder(f.id, f.path);
113
- }
114
- return dbFolders;
116
+ return this.db.getFolders(accountId);
115
117
  }
116
118
  /** Sync messages for a specific folder */
117
119
  async syncFolder(accountId, folderId, client) {
@@ -271,8 +273,6 @@ export class ImapManager extends EventEmitter {
271
273
  const folders = await this.syncFolders(accountId, client);
272
274
  await client.logout();
273
275
  client = null;
274
- // Fresh client for message sync (getFolderList corrupts imapflow state)
275
- client = this.createClient(accountId);
276
276
  // INBOX first so it's available fastest
277
277
  folders.sort((a, b) => {
278
278
  if (a.specialUse === "inbox")
@@ -281,11 +281,22 @@ export class ImapManager extends EventEmitter {
281
281
  return 1;
282
282
  return 0;
283
283
  });
284
+ // Fresh client per folder — IMAP connections drop mid-sync on large accounts
284
285
  for (const folder of folders) {
285
286
  try {
287
+ client = this.createClient(accountId);
286
288
  await this.syncFolder(accountId, folder.id, client);
289
+ await client.logout();
290
+ client = null;
287
291
  }
288
292
  catch (e) {
293
+ if (client) {
294
+ try {
295
+ await client.logout();
296
+ }
297
+ catch { /* ignore */ }
298
+ client = null;
299
+ }
289
300
  if (e.responseText?.includes("doesn't exist")) {
290
301
  console.log(` Removing non-existent folder: ${folder.path}`);
291
302
  this.db.deleteFolder(folder.id);
@@ -295,8 +306,6 @@ export class ImapManager extends EventEmitter {
295
306
  }
296
307
  }
297
308
  }
298
- await client.logout();
299
- client = null;
300
309
  this.emit("syncComplete", accountId);
301
310
  }
302
311
  catch (e) {
@@ -493,6 +502,38 @@ export class ImapManager extends EventEmitter {
493
502
  // Try immediate sync
494
503
  this.processSyncActions(accountId).catch(() => { });
495
504
  }
505
+ /** Move message across accounts using iflow's moveMessageToServer */
506
+ async moveMessageCrossAccount(fromAccountId, uid, fromFolderId, toAccountId, toFolderId) {
507
+ const fromFolders = this.db.getFolders(fromAccountId);
508
+ const fromFolder = fromFolders.find(f => f.id === fromFolderId);
509
+ if (!fromFolder)
510
+ throw new Error(`Source folder ${fromFolderId} not found`);
511
+ const toFolders = this.db.getFolders(toAccountId);
512
+ const toFolder = toFolders.find(f => f.id === toFolderId);
513
+ if (!toFolder)
514
+ throw new Error(`Target folder ${toFolderId} not found`);
515
+ const sourceClient = this.createClient(fromAccountId);
516
+ const targetClient = this.createClient(toAccountId);
517
+ try {
518
+ const msg = await sourceClient.fetchMessageByUid(fromFolder.path, uid, { source: true });
519
+ if (!msg)
520
+ throw new Error(`Message UID ${uid} not found in ${fromFolder.path}`);
521
+ await sourceClient.moveMessageToServer(msg, fromFolder.path, targetClient, toFolder.path);
522
+ // Remove from local DB
523
+ this.db.deleteMessage(fromAccountId, uid);
524
+ console.log(` Cross-account move: ${fromAccountId}/${fromFolder.path} UID ${uid} → ${toAccountId}/${toFolder.path}`);
525
+ }
526
+ finally {
527
+ try {
528
+ await sourceClient.logout();
529
+ }
530
+ catch { /* ignore */ }
531
+ try {
532
+ await targetClient.logout();
533
+ }
534
+ catch { /* ignore */ }
535
+ }
536
+ }
496
537
  /** Undelete — move from Trash back to original folder */
497
538
  async undeleteMessage(accountId, uid, originalFolderId) {
498
539
  const trash = this.findFolder(accountId, "trash");
@@ -817,13 +858,21 @@ export class ImapManager extends EventEmitter {
817
858
  }
818
859
  // Send via SMTP
819
860
  try {
861
+ let smtpAuth;
862
+ if (account.smtp.auth === "password") {
863
+ smtpAuth = { user: account.smtp.user, pass: account.smtp.password };
864
+ }
865
+ else if (account.smtp.auth === "oauth2") {
866
+ const accessToken = await this.getOAuthToken(accountId);
867
+ if (!accessToken)
868
+ throw new Error("OAuth token not available — re-authenticate");
869
+ smtpAuth = { type: "OAuth2", user: account.smtp.user, accessToken };
870
+ }
820
871
  const transport = createTransport({
821
872
  host: account.smtp.host,
822
873
  port: account.smtp.port,
823
874
  secure: account.smtp.port === 465,
824
- auth: account.smtp.auth === "password"
825
- ? { user: account.smtp.user, pass: account.smtp.password }
826
- : undefined,
875
+ auth: smtpAuth,
827
876
  tls: { rejectUnauthorized: false },
828
877
  });
829
878
  // Parse recipients from raw message headers for SMTP envelope
@@ -874,10 +923,23 @@ export class ImapManager extends EventEmitter {
874
923
  catch { /* ignore */ }
875
924
  }
876
925
  }
877
- /** Start background Outbox worker — checks every 10 seconds */
926
+ /** Start background Outbox worker — runs immediately then every 10 seconds */
878
927
  startOutboxWorker() {
879
928
  if (this.outboxInterval)
880
929
  return;
930
+ // Run once immediately on startup
931
+ const processAll = async () => {
932
+ for (const [accountId] of this.configs) {
933
+ try {
934
+ await this.processLocalQueue(accountId);
935
+ await this.processOutbox(accountId);
936
+ }
937
+ catch (e) {
938
+ console.error(` [outbox] Error for ${accountId}: ${e.message}`);
939
+ }
940
+ }
941
+ };
942
+ setTimeout(() => processAll(), 3000); // 3s after startup (let connections settle)
881
943
  this.outboxInterval = setInterval(async () => {
882
944
  for (const [accountId] of this.configs) {
883
945
  try {