@bobfrankston/mailx 1.0.9 → 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 (246) hide show
  1. package/bin/mailx.js +47 -29
  2. package/bin/postinstall.js +41 -0
  3. package/client/app.js +93 -13
  4. package/client/components/folder-tree.js +84 -3
  5. package/client/components/message-list.js +134 -8
  6. package/client/components/message-viewer.js +130 -13
  7. package/client/compose/compose.html +4 -4
  8. package/client/compose/compose.js +53 -34
  9. package/client/index.html +33 -9
  10. package/client/lib/api-client.js +102 -30
  11. package/client/lib/mailxapi.js +123 -0
  12. package/client/package.json +5 -1
  13. package/client/styles/components.css +188 -15
  14. package/client/styles/layout.css +2 -1
  15. package/killmail.cmd +6 -0
  16. package/launch.ps1 +48 -3
  17. package/launcher/bin/mailx-app.exe +0 -0
  18. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Breadcrumbs +0 -0
  19. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/metadata +0 -0
  20. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/settings.dat +0 -0
  21. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/throttle_store.dat +1 -0
  22. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/CrashpadMetrics-active.pma +0 -0
  23. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/BrowsingTopicsSiteData +0 -0
  24. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Cache/No_Vary_Search/journal.baj +1 -0
  25. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/DIPS +0 -0
  26. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/DashTrackerDatabase +0 -0
  27. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/EdgeJourneys/EdgeJourneys.db +0 -0
  28. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/LOCK +0 -0
  29. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/LOG +3 -0
  30. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/MANIFEST-000001 +0 -0
  31. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/LOCK +0 -0
  32. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/LOG +3 -0
  33. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/MANIFEST-000001 +0 -0
  34. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/LOCK +0 -0
  35. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/LOG +3 -0
  36. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/MANIFEST-000001 +0 -0
  37. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ExtensionActivityComp +0 -0
  38. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ExtensionActivityEdge +0 -0
  39. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Favicons +0 -0
  40. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/History +0 -0
  41. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/History-journal +0 -0
  42. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/LOCK +0 -0
  43. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/LOG +3 -0
  44. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/MANIFEST-000001 +0 -0
  45. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOCK +0 -0
  46. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOG +3 -0
  47. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/MANIFEST-000001 +0 -0
  48. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Login Data +0 -0
  49. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Login Data For Account +0 -0
  50. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network/Cookies +0 -0
  51. NEL +0 -0
  52. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network/Trust Tokens +0 -0
  53. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network Action Predictor +0 -0
  54. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Preferences +1 -0
  55. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Safe Browsing Network/Safe Browsing Cookies +0 -0
  56. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ServerCertificate +0 -0
  57. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/LOCK +0 -0
  58. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/LOG +3 -0
  59. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/MANIFEST-000001 +0 -0
  60. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Shared Dictionary/db +0 -0
  61. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/SharedStorage +0 -0
  62. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Shortcuts +0 -0
  63. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOCK +0 -0
  64. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOG +3 -0
  65. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/MANIFEST-000001 +0 -0
  66. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOCK +0 -0
  67. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOG +3 -0
  68. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/MANIFEST-000001 +0 -0
  69. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Top Sites +0 -0
  70. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Vpn Tokens +0 -0
  71. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Web Data +0 -0
  72. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Web Data-journal +0 -0
  73. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager +0 -0
  74. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager-journal +0 -0
  75. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/heavy_ad_intervention_opt_out.db +0 -0
  76. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/LOCK +0 -0
  77. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/LOG +3 -0
  78. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/MANIFEST-000001 +0 -0
  79. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOCK +0 -0
  80. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOG +3 -0
  81. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/MANIFEST-000001 +0 -0
  82. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/DeferredBrowserMetrics/BrowserMetrics-69CAD063-BE24.pma +0 -0
  83. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Local State +1 -0
  84. package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Variations +1 -0
  85. package/launcher/bin/mailx-app.old.exe +0 -0
  86. package/launcher/mailx.ico +0 -0
  87. package/npmg.bat +6 -0
  88. package/package.json +9 -3
  89. package/packages/mailx-api/index.js +79 -26
  90. package/packages/mailx-api/package.json +5 -1
  91. package/packages/mailx-compose/package.json +5 -1
  92. package/packages/mailx-core/index.d.ts +129 -0
  93. package/packages/mailx-core/index.js +323 -0
  94. package/packages/mailx-core/ipc.d.ts +13 -0
  95. package/packages/mailx-core/ipc.js +56 -0
  96. package/packages/mailx-core/package.json +18 -0
  97. package/packages/mailx-imap/index.d.ts +5 -1
  98. package/packages/mailx-imap/index.js +76 -14
  99. package/packages/mailx-imap/package.json +5 -1
  100. package/packages/mailx-send/package.json +12 -2
  101. package/packages/mailx-server/index.js +42 -31
  102. package/packages/mailx-server/package.json +5 -2
  103. package/packages/mailx-settings/index.d.ts +1 -1
  104. package/packages/mailx-settings/index.js +21 -12
  105. package/packages/mailx-settings/package.json +5 -1
  106. package/packages/mailx-store/db.d.ts +5 -1
  107. package/packages/mailx-store/db.js +64 -12
  108. package/packages/mailx-store/file-store.d.ts +2 -8
  109. package/packages/mailx-store/file-store.js +7 -31
  110. package/packages/mailx-store/package.json +5 -1
  111. package/packages/mailx-types/index.d.ts +3 -1
  112. package/packages/mailx-types/package.json +6 -2
  113. package/.tswalk.json +0 -3366
  114. package/mailx.json +0 -9
  115. package/packages/mailx-api/node_modules/nodemailer/.ncurc.js +0 -9
  116. package/packages/mailx-api/node_modules/nodemailer/.prettierignore +0 -8
  117. package/packages/mailx-api/node_modules/nodemailer/.prettierrc +0 -12
  118. package/packages/mailx-api/node_modules/nodemailer/.prettierrc.js +0 -10
  119. package/packages/mailx-api/node_modules/nodemailer/.release-please-config.json +0 -9
  120. package/packages/mailx-api/node_modules/nodemailer/LICENSE +0 -16
  121. package/packages/mailx-api/node_modules/nodemailer/README.md +0 -86
  122. package/packages/mailx-api/node_modules/nodemailer/SECURITY.txt +0 -22
  123. package/packages/mailx-api/node_modules/nodemailer/eslint.config.js +0 -88
  124. package/packages/mailx-api/node_modules/nodemailer/lib/addressparser/index.js +0 -383
  125. package/packages/mailx-api/node_modules/nodemailer/lib/base64/index.js +0 -139
  126. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/index.js +0 -253
  127. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
  128. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  129. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/sign.js +0 -117
  130. package/packages/mailx-api/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
  131. package/packages/mailx-api/node_modules/nodemailer/lib/fetch/index.js +0 -280
  132. package/packages/mailx-api/node_modules/nodemailer/lib/json-transport/index.js +0 -82
  133. package/packages/mailx-api/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
  134. package/packages/mailx-api/node_modules/nodemailer/lib/mailer/index.js +0 -441
  135. package/packages/mailx-api/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
  136. package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
  137. package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
  138. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
  139. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
  140. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
  141. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
  142. package/packages/mailx-api/node_modules/nodemailer/lib/nodemailer.js +0 -157
  143. package/packages/mailx-api/node_modules/nodemailer/lib/punycode/index.js +0 -460
  144. package/packages/mailx-api/node_modules/nodemailer/lib/qp/index.js +0 -227
  145. package/packages/mailx-api/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
  146. package/packages/mailx-api/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
  147. package/packages/mailx-api/node_modules/nodemailer/lib/shared/index.js +0 -754
  148. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
  149. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
  150. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
  151. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
  152. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
  153. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
  154. package/packages/mailx-api/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
  155. package/packages/mailx-api/node_modules/nodemailer/lib/well-known/index.js +0 -47
  156. package/packages/mailx-api/node_modules/nodemailer/lib/well-known/services.json +0 -611
  157. package/packages/mailx-api/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
  158. package/packages/mailx-api/node_modules/nodemailer/package.json +0 -47
  159. package/packages/mailx-imap/node_modules/nodemailer/.ncurc.js +0 -9
  160. package/packages/mailx-imap/node_modules/nodemailer/.prettierignore +0 -8
  161. package/packages/mailx-imap/node_modules/nodemailer/.prettierrc +0 -12
  162. package/packages/mailx-imap/node_modules/nodemailer/.prettierrc.js +0 -10
  163. package/packages/mailx-imap/node_modules/nodemailer/.release-please-config.json +0 -9
  164. package/packages/mailx-imap/node_modules/nodemailer/LICENSE +0 -16
  165. package/packages/mailx-imap/node_modules/nodemailer/README.md +0 -86
  166. package/packages/mailx-imap/node_modules/nodemailer/SECURITY.txt +0 -22
  167. package/packages/mailx-imap/node_modules/nodemailer/eslint.config.js +0 -88
  168. package/packages/mailx-imap/node_modules/nodemailer/lib/addressparser/index.js +0 -383
  169. package/packages/mailx-imap/node_modules/nodemailer/lib/base64/index.js +0 -139
  170. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/index.js +0 -253
  171. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
  172. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  173. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/sign.js +0 -117
  174. package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
  175. package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/index.js +0 -280
  176. package/packages/mailx-imap/node_modules/nodemailer/lib/json-transport/index.js +0 -82
  177. package/packages/mailx-imap/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
  178. package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/index.js +0 -441
  179. package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
  180. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
  181. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
  182. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
  183. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
  184. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
  185. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
  186. package/packages/mailx-imap/node_modules/nodemailer/lib/nodemailer.js +0 -157
  187. package/packages/mailx-imap/node_modules/nodemailer/lib/punycode/index.js +0 -460
  188. package/packages/mailx-imap/node_modules/nodemailer/lib/qp/index.js +0 -227
  189. package/packages/mailx-imap/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
  190. package/packages/mailx-imap/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
  191. package/packages/mailx-imap/node_modules/nodemailer/lib/shared/index.js +0 -754
  192. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
  193. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
  194. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
  195. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
  196. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
  197. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
  198. package/packages/mailx-imap/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
  199. package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/index.js +0 -47
  200. package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/services.json +0 -611
  201. package/packages/mailx-imap/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
  202. package/packages/mailx-imap/node_modules/nodemailer/package.json +0 -47
  203. package/packages/mailx-send/node_modules/nodemailer/.ncurc.js +0 -9
  204. package/packages/mailx-send/node_modules/nodemailer/.prettierignore +0 -8
  205. package/packages/mailx-send/node_modules/nodemailer/.prettierrc +0 -12
  206. package/packages/mailx-send/node_modules/nodemailer/.prettierrc.js +0 -10
  207. package/packages/mailx-send/node_modules/nodemailer/.release-please-config.json +0 -9
  208. package/packages/mailx-send/node_modules/nodemailer/LICENSE +0 -16
  209. package/packages/mailx-send/node_modules/nodemailer/README.md +0 -86
  210. package/packages/mailx-send/node_modules/nodemailer/SECURITY.txt +0 -22
  211. package/packages/mailx-send/node_modules/nodemailer/eslint.config.js +0 -88
  212. package/packages/mailx-send/node_modules/nodemailer/lib/addressparser/index.js +0 -383
  213. package/packages/mailx-send/node_modules/nodemailer/lib/base64/index.js +0 -139
  214. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/index.js +0 -253
  215. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
  216. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  217. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/sign.js +0 -117
  218. package/packages/mailx-send/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
  219. package/packages/mailx-send/node_modules/nodemailer/lib/fetch/index.js +0 -280
  220. package/packages/mailx-send/node_modules/nodemailer/lib/json-transport/index.js +0 -82
  221. package/packages/mailx-send/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
  222. package/packages/mailx-send/node_modules/nodemailer/lib/mailer/index.js +0 -441
  223. package/packages/mailx-send/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
  224. package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
  225. package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
  226. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
  227. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
  228. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
  229. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
  230. package/packages/mailx-send/node_modules/nodemailer/lib/nodemailer.js +0 -157
  231. package/packages/mailx-send/node_modules/nodemailer/lib/punycode/index.js +0 -460
  232. package/packages/mailx-send/node_modules/nodemailer/lib/qp/index.js +0 -227
  233. package/packages/mailx-send/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
  234. package/packages/mailx-send/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
  235. package/packages/mailx-send/node_modules/nodemailer/lib/shared/index.js +0 -754
  236. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
  237. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
  238. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
  239. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
  240. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
  241. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
  242. package/packages/mailx-send/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
  243. package/packages/mailx-send/node_modules/nodemailer/lib/well-known/index.js +0 -47
  244. package/packages/mailx-send/node_modules/nodemailer/lib/well-known/services.json +0 -611
  245. package/packages/mailx-send/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
  246. package/packages/mailx-send/node_modules/nodemailer/package.json +0 -47
@@ -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 {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -15,5 +15,9 @@
15
15
  "@bobfrankston/iflow": "file:../../../MailApps/iflow",
16
16
  "@bobfrankston/oauthsupport": "file:../../../../projects/oauth/oauthsupport",
17
17
  "nodemailer": "^7.0.0"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/BobFrankston/mailx-imap.git"
18
22
  }
19
23
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-send",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Queue-based mail sender with SMTP and OAuth2 support",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -13,7 +13,13 @@
13
13
  "build": "tsc",
14
14
  "release": "npmglobalize"
15
15
  },
16
- "keywords": ["email", "smtp", "queue", "oauth2", "gmail"],
16
+ "keywords": [
17
+ "email",
18
+ "smtp",
19
+ "queue",
20
+ "oauth2",
21
+ "gmail"
22
+ ],
17
23
  "author": "Bob Frankston",
18
24
  "license": "MIT",
19
25
  "dependencies": {
@@ -23,5 +29,9 @@
23
29
  "devDependencies": {
24
30
  "@types/nodemailer": "^6.4.0",
25
31
  "@types/node": "^22.0.0"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/BobFrankston/mailx-send.git"
26
36
  }
27
37
  }
@@ -11,10 +11,10 @@ import { ImapManager } from "@bobfrankston/mailx-imap";
11
11
  import { createApiRouter } from "@bobfrankston/mailx-api";
12
12
  import { loadSettings, getConfigDir, initLocalConfig } from "@bobfrankston/mailx-settings";
13
13
  import { ports } from "@bobfrankston/miscinfo";
14
- import { InitServerAsync, InitServerOptions } from "@bobfrankston/certsupport";
14
+ import { createServer } from "node:http";
15
15
  const PORT = ports.mailx;
16
16
  // ── File logging ──
17
- const logDir = path.join(process.env.USERPROFILE || process.env.HOME || ".", ".mailx");
17
+ const logDir = path.join(process.env.USERPROFILE || process.env.HOME || ".", ".mailx", "logs");
18
18
  fs.mkdirSync(logDir, { recursive: true });
19
19
  // Rotate: delete logs older than 7 days
20
20
  try {
@@ -53,7 +53,19 @@ const db = new MailxDB(dbDir);
53
53
  const imapManager = new ImapManager(db);
54
54
  // ── Express App ──
55
55
  const app = express();
56
- app.use(express.json());
56
+ app.use(express.json({ limit: "Infinity" }));
57
+ // Request logging
58
+ app.use((req, res, next) => {
59
+ const start = Date.now();
60
+ res.on("finish", () => {
61
+ const ms = Date.now() - start;
62
+ // Skip noisy polling endpoints
63
+ if (req.path === "/api/sync/pending")
64
+ return;
65
+ console.log(` ${req.method} ${req.path} ${res.statusCode} ${ms}ms`);
66
+ });
67
+ next();
68
+ });
57
69
  // Serve client static files
58
70
  const clientDir = path.join(import.meta.dirname, "..", "..", "client");
59
71
  const rootDir = path.join(import.meta.dirname, "..", "..");
@@ -97,15 +109,14 @@ ${accountInfo.map((a) => `<tr><td>${a.name}</td><td>${a.folders}</td><td>${a.inb
97
109
  <p style="margin-top:2rem;font-size:0.8rem"><a href="/">Open mailx</a> | Auto-refreshes every 10s</p>
98
110
  </body></html>`);
99
111
  });
100
- // Dev: restart server + reload clients
112
+ // Restart server + reload clients
101
113
  app.post("/api/restart", (req, res) => {
102
114
  res.json({ ok: true });
103
115
  broadcast({ type: "reload" });
104
- // Touch the server file to trigger node --watch restart
105
- const serverFile = path.join(import.meta.dirname, "index.js");
106
- setTimeout(() => {
107
- const now = new Date();
108
- fs.utimesSync(serverFile, now, now);
116
+ // Graceful shutdown node --watch will auto-restart
117
+ setTimeout(async () => {
118
+ console.log(" Restart requested via API");
119
+ await shutdown();
109
120
  }, 500);
110
121
  });
111
122
  // SPA fallback
@@ -113,8 +124,13 @@ app.get("*", (req, res) => {
113
124
  if (!req.path.startsWith("/api"))
114
125
  res.sendFile(path.join(clientDir, "index.html"));
115
126
  });
116
- // ── HTTPS Server (certsupport with SNI) ──
117
- const certspath = path.join(import.meta.dirname, "certs");
127
+ // JSON error handler all errors return JSON, never HTML
128
+ app.use((err, _req, res, _next) => {
129
+ console.error(`ERROR ${err.message}`);
130
+ const status = err.status || err.statusCode || 500;
131
+ res.status(status).json({ error: err.message || "Internal server error" });
132
+ });
133
+ // ── HTTP Server ──
118
134
  let server;
119
135
  let wss;
120
136
  const clients = new Set();
@@ -147,19 +163,19 @@ async function start() {
147
163
  const seeded = db.seedContactsFromMessages();
148
164
  if (seeded > 0)
149
165
  console.log(` Seeded ${seeded} contacts`);
150
- // Search index — only rebuild if empty (new DB or after reset)
151
- try {
152
- const ftsCount = db.db.prepare("SELECT COUNT(*) as cnt FROM messages_fts").get();
153
- if (!ftsCount?.cnt) {
166
+ // Search index — rebuild in background after server starts (non-blocking)
167
+ setTimeout(() => {
168
+ let ftsCount = 0;
169
+ try {
170
+ ftsCount = db.db.prepare("SELECT COUNT(*) as cnt FROM messages_fts").get()?.cnt || 0;
171
+ }
172
+ catch { /* */ }
173
+ if (ftsCount === 0) {
154
174
  const indexed = db.rebuildSearchIndex();
155
- console.log(` Search index built: ${indexed} messages`);
175
+ if (indexed > 0)
176
+ console.log(` Search index: ${indexed} messages`);
156
177
  }
157
- }
158
- catch {
159
- // FTS table might not exist yet
160
- const indexed = db.rebuildSearchIndex();
161
- console.log(` Search index built: ${indexed} messages`);
162
- }
178
+ }, 5000);
163
179
  // Add configured accounts
164
180
  for (const account of settings.accounts) {
165
181
  if (!account.enabled)
@@ -191,17 +207,12 @@ async function start() {
191
207
  imapManager.startOutboxWorker();
192
208
  // Start server — localhost only by default, --external for network access
193
209
  const externalAccess = process.argv.includes("--external");
194
- const opts = new InitServerOptions({
195
- port: PORT,
196
- hostname: externalAccess ? undefined : "127.0.0.1",
197
- app,
198
- certspath,
199
- msger: (msg) => console.log(` [cert] ${msg}`),
200
- });
201
- server = await InitServerAsync(opts);
210
+ const hostname = externalAccess ? "0.0.0.0" : "127.0.0.1";
211
+ server = createServer(app);
202
212
  wss = new WebSocketServer({ server });
203
213
  wireWebSocket();
204
- console.log(`mailx server running on port ${PORT} (http+https)`);
214
+ await new Promise((resolve) => server.listen(PORT, hostname, resolve));
215
+ console.log(`mailx server running on http://${hostname}:${PORT}`);
205
216
  }
206
217
  // ── Graceful Shutdown ──
207
218
  async function shutdown() {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-server",
3
- "version": "1.0.3",
3
+ "version": "1.0.7",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -14,12 +14,15 @@
14
14
  "@bobfrankston/mailx-imap": "file:../mailx-imap",
15
15
  "@bobfrankston/mailx-api": "file:../mailx-api",
16
16
  "@bobfrankston/mailx-settings": "file:../mailx-settings",
17
- "@bobfrankston/certsupport": "file:../../../../projects/nodejs/certsupport",
18
17
  "express": "^4.21.0",
19
18
  "ws": "^8.18.0"
20
19
  },
21
20
  "devDependencies": {
22
21
  "@types/express": "^5.0.0",
23
22
  "@types/ws": "^8.5.13"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/BobFrankston/mailx-server.git"
24
27
  }
25
28
  }
@@ -8,7 +8,7 @@
8
8
  * allowlist.jsonc — remote content allow-list
9
9
  *
10
10
  * Local overrides (~/.mailx/):
11
- * config.json — pointer to shared dir + local-only settings (storePath, historyDays)
11
+ * config.jsonc — pointer to shared dir + local-only settings (storePath, historyDays)
12
12
  * accounts.jsonc — cached copy, fallback when shared unavailable
13
13
  * preferences.jsonc — local overrides merged on top of shared
14
14
  * allowlist.jsonc — cached copy
@@ -8,7 +8,7 @@
8
8
  * allowlist.jsonc — remote content allow-list
9
9
  *
10
10
  * Local overrides (~/.mailx/):
11
- * config.json — pointer to shared dir + local-only settings (storePath, historyDays)
11
+ * config.jsonc — pointer to shared dir + local-only settings (storePath, historyDays)
12
12
  * accounts.jsonc — cached copy, fallback when shared unavailable
13
13
  * preferences.jsonc — local overrides merged on top of shared
14
14
  * allowlist.jsonc — cached copy
@@ -20,17 +20,17 @@ import * as path from "node:path";
20
20
  import { parse as parseJsonc } from "jsonc-parser";
21
21
  // ── Paths ──
22
22
  const LOCAL_DIR = path.join(process.env.USERPROFILE || process.env.HOME || ".", ".mailx");
23
- const LOCAL_CONFIG_PATH = path.join(LOCAL_DIR, "config.json");
23
+ const LOCAL_CONFIG_PATH = path.join(LOCAL_DIR, "config.jsonc");
24
+ const LEGACY_CONFIG_PATH = path.join(LOCAL_DIR, "config.json");
24
25
  const DEFAULT_STORE_PATH = path.join(LOCAL_DIR, "mailxstore");
25
26
  function readLocalConfig() {
26
- if (!fs.existsSync(LOCAL_CONFIG_PATH))
27
- return {};
28
- try {
29
- return JSON.parse(fs.readFileSync(LOCAL_CONFIG_PATH, "utf-8"));
27
+ // Migrate config.json → config.jsonc
28
+ if (!fs.existsSync(LOCAL_CONFIG_PATH) && fs.existsSync(LEGACY_CONFIG_PATH)) {
29
+ fs.renameSync(LEGACY_CONFIG_PATH, LOCAL_CONFIG_PATH);
30
30
  }
31
- catch {
31
+ if (!fs.existsSync(LOCAL_CONFIG_PATH))
32
32
  return {};
33
- }
33
+ return readJsonc(LOCAL_CONFIG_PATH) || {};
34
34
  }
35
35
  function getSharedDir() {
36
36
  const config = readLocalConfig();
@@ -42,14 +42,23 @@ function getSharedDir() {
42
42
  return LOCAL_DIR;
43
43
  }
44
44
  // ── File helpers ──
45
+ /** Read JSON or JSONC file. If exact path not found, tries .json/.jsonc variant. */
45
46
  function readJsonc(filePath) {
46
- if (!fs.existsSync(filePath))
47
- return null;
47
+ let actual = filePath;
48
+ if (!fs.existsSync(actual)) {
49
+ // Try alternate extension
50
+ if (actual.endsWith(".jsonc"))
51
+ actual = actual.replace(/\.jsonc$/, ".json");
52
+ else if (actual.endsWith(".json"))
53
+ actual = actual.replace(/\.json$/, ".jsonc");
54
+ if (!fs.existsSync(actual))
55
+ return null;
56
+ }
48
57
  try {
49
- return parseJsonc(fs.readFileSync(filePath, "utf-8"));
58
+ return parseJsonc(fs.readFileSync(actual, "utf-8"));
50
59
  }
51
60
  catch (e) {
52
- console.error(`Failed to read ${filePath}: ${e.message}`);
61
+ console.error(`Failed to read ${actual}: ${e.message}`);
53
62
  return null;
54
63
  }
55
64
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-settings",
3
- "version": "0.1.0",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -12,5 +12,9 @@
12
12
  "dependencies": {
13
13
  "@bobfrankston/mailx-types": "file:../mailx-types",
14
14
  "jsonc-parser": "^3.3.1"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/BobFrankston/mailx-settings.git"
15
19
  }
16
20
  }
@@ -44,7 +44,9 @@ export declare class MailxDB {
44
44
  bodyPath: string;
45
45
  }): number;
46
46
  getMessages(query: MessageQuery): PagedResult<MessageEnvelope>;
47
- getMessageByUid(accountId: string, uid: number): MessageEnvelope;
47
+ /** Unified inbox: all inbox folders across accounts, sorted by date, paginated in SQL */
48
+ getUnifiedInbox(page?: number, pageSize?: number): PagedResult<MessageEnvelope>;
49
+ getMessageByUid(accountId: string, uid: number, folderId?: number): MessageEnvelope;
48
50
  getMessageBodyPath(accountId: string, uid: number): string;
49
51
  updateMessageFlags(accountId: string, uid: number, flags: string[]): void;
50
52
  getHighestUid(accountId: string, folderId: number): number;
@@ -52,6 +54,8 @@ export declare class MailxDB {
52
54
  getUidsForFolder(accountId: string, folderId: number): number[];
53
55
  /** Delete a message by account + UID */
54
56
  deleteMessage(accountId: string, uid: number): void;
57
+ /** Recalculate folder total/unread counts from actual messages */
58
+ recalcFolderCounts(folderId: number): void;
55
59
  /** Bulk insert within a transaction for sync performance */
56
60
  beginTransaction(): void;
57
61
  commitTransaction(): void;
@@ -241,8 +241,43 @@ export class MailxDB {
241
241
  }));
242
242
  return { items, total, page, pageSize };
243
243
  }
244
- getMessageByUid(accountId, uid) {
245
- const r = this.db.prepare("SELECT * FROM messages WHERE account_id = ? AND uid = ?").get(accountId, uid);
244
+ /** Unified inbox: all inbox folders across accounts, sorted by date, paginated in SQL */
245
+ getUnifiedInbox(page = 1, pageSize = 50) {
246
+ const offset = (page - 1) * pageSize;
247
+ // Find all inbox folder IDs
248
+ const inboxRows = this.db.prepare("SELECT id FROM folders WHERE special_use = 'inbox'").all();
249
+ if (inboxRows.length === 0)
250
+ return { items: [], total: 0, page, pageSize };
251
+ const placeholders = inboxRows.map(() => "?").join(",");
252
+ const folderIds = inboxRows.map((r) => r.id);
253
+ const total = this.db.prepare(`SELECT COUNT(*) as cnt FROM messages WHERE folder_id IN (${placeholders})`).get(...folderIds).cnt;
254
+ const rows = this.db.prepare(`SELECT * FROM messages WHERE folder_id IN (${placeholders}) ORDER BY date DESC LIMIT ? OFFSET ?`).all(...folderIds, pageSize, offset);
255
+ const items = rows.map(r => ({
256
+ id: r.id,
257
+ accountId: r.account_id,
258
+ folderId: r.folder_id,
259
+ uid: r.uid,
260
+ messageId: r.message_id || "",
261
+ inReplyTo: r.in_reply_to || "",
262
+ references: JSON.parse(r.refs || "[]"),
263
+ date: r.date,
264
+ subject: r.subject,
265
+ from: { name: r.from_name, address: r.from_address },
266
+ to: JSON.parse(r.to_json),
267
+ cc: JSON.parse(r.cc_json),
268
+ flags: JSON.parse(r.flags_json),
269
+ size: r.size,
270
+ hasAttachments: !!r.has_attachments,
271
+ preview: r.preview
272
+ }));
273
+ return { items, total, page, pageSize };
274
+ }
275
+ getMessageByUid(accountId, uid, folderId) {
276
+ const sql = folderId != null
277
+ ? "SELECT * FROM messages WHERE account_id = ? AND uid = ? AND folder_id = ?"
278
+ : "SELECT * FROM messages WHERE account_id = ? AND uid = ?";
279
+ const params = folderId != null ? [accountId, uid, folderId] : [accountId, uid];
280
+ const r = this.db.prepare(sql).get(...params);
246
281
  if (!r)
247
282
  return null;
248
283
  return {
@@ -282,7 +317,19 @@ export class MailxDB {
282
317
  }
283
318
  /** Delete a message by account + UID */
284
319
  deleteMessage(accountId, uid) {
320
+ // Get folderId before deleting so we can update counts
321
+ const msg = this.db.prepare("SELECT folder_id FROM messages WHERE account_id = ? AND uid = ?").get(accountId, uid);
285
322
  this.db.prepare("DELETE FROM messages WHERE account_id = ? AND uid = ?").run(accountId, uid);
323
+ // Refresh folder counts
324
+ if (msg)
325
+ this.recalcFolderCounts(msg.folder_id);
326
+ }
327
+ /** Recalculate folder total/unread counts from actual messages */
328
+ recalcFolderCounts(folderId) {
329
+ const counts = this.db.prepare(`SELECT COUNT(*) as total,
330
+ SUM(CASE WHEN flags_json NOT LIKE '%\\\\Seen%' THEN 1 ELSE 0 END) as unread
331
+ FROM messages WHERE folder_id = ?`).get(folderId);
332
+ this.updateFolderCounts(folderId, counts?.total || 0, counts?.unread || 0);
286
333
  }
287
334
  /** Bulk insert within a transaction for sync performance */
288
335
  beginTransaction() { this.db.exec("BEGIN"); }
@@ -401,19 +448,24 @@ export class MailxDB {
401
448
  subject, from_name, from_address, to_text, cc_text, body_text,
402
449
  content=messages, content_rowid=id
403
450
  )`);
451
+ // Use a single transaction + prepared statement for speed (~50x faster than individual inserts)
452
+ const insert = this.db.prepare("INSERT INTO messages_fts (rowid, subject, from_name, from_address, to_text, cc_text, body_text) VALUES (?, ?, ?, ?, ?, ?, ?)");
404
453
  const rows = this.db.prepare("SELECT id, subject, from_name, from_address, to_json, cc_json, preview FROM messages").all();
405
454
  let count = 0;
406
- for (const r of rows) {
407
- const to = JSON.parse(r.to_json || "[]");
408
- const cc = JSON.parse(r.cc_json || "[]");
409
- const toText = to.map((a) => `${a.name} ${a.address}`).join(" ");
410
- const ccText = cc.map((a) => `${a.name} ${a.address}`).join(" ");
411
- try {
412
- this.db.prepare("INSERT INTO messages_fts (rowid, subject, from_name, from_address, to_text, cc_text, body_text) VALUES (?, ?, ?, ?, ?, ?, ?)").run(r.id, r.subject, r.from_name, r.from_address, toText, ccText, r.preview);
413
- count++;
455
+ const batchInsert = this.db.transaction(() => {
456
+ for (const r of rows) {
457
+ const to = JSON.parse(r.to_json || "[]");
458
+ const cc = JSON.parse(r.cc_json || "[]");
459
+ const toText = to.map((a) => `${a.name} ${a.address}`).join(" ");
460
+ const ccText = cc.map((a) => `${a.name} ${a.address}`).join(" ");
461
+ try {
462
+ insert.run(r.id, r.subject, r.from_name, r.from_address, toText, ccText, r.preview);
463
+ count++;
464
+ }
465
+ catch { /* skip duplicates */ }
414
466
  }
415
- catch { /* skip duplicates */ }
416
- }
467
+ });
468
+ batchInsert();
417
469
  return count;
418
470
  }
419
471
  // ── Sync Actions ──
@@ -1,19 +1,13 @@
1
1
  /**
2
2
  * File-per-message body storage backend.
3
- * Messages stored as: {basePath}/{accountId}/{folderName}/{uid}.eml
4
- * Legacy: also checks {basePath}/{accountId}/{folderId}/{uid}.eml
3
+ * Messages stored as: {basePath}/{accountId}/{folderId}/{uid}.eml
4
+ * Folder IDs are numeric (from SQLite), not names.
5
5
  */
6
6
  import type { MessageStore } from "@bobfrankston/mailx-types";
7
7
  export declare class FileMessageStore implements MessageStore {
8
8
  private basePath;
9
- private folderNames;
10
9
  constructor(basePath: string);
11
- /** Register folder ID → path mapping for human-readable directory names */
12
- registerFolder(folderId: number, folderPath: string): void;
13
- private folderDir;
14
10
  private messagePath;
15
- /** Check legacy path (numeric folder ID) */
16
- private legacyPath;
17
11
  putMessage(accountId: string, folderId: number, uid: number, raw: Buffer): Promise<string>;
18
12
  getMessage(accountId: string, folderId: number, uid: number): Promise<Buffer>;
19
13
  deleteMessage(accountId: string, folderId: number, uid: number): Promise<void>;
@@ -1,34 +1,17 @@
1
1
  /**
2
2
  * File-per-message body storage backend.
3
- * Messages stored as: {basePath}/{accountId}/{folderName}/{uid}.eml
4
- * Legacy: also checks {basePath}/{accountId}/{folderId}/{uid}.eml
3
+ * Messages stored as: {basePath}/{accountId}/{folderId}/{uid}.eml
4
+ * Folder IDs are numeric (from SQLite), not names.
5
5
  */
6
6
  import * as fs from "node:fs";
7
7
  import * as path from "node:path";
8
- /** Sanitize folder path for use as directory name — replace delimiters with _ */
9
- function sanitizeFolderName(folderPath) {
10
- return folderPath.replace(/[\/\\.:]/g, "_");
11
- }
12
8
  export class FileMessageStore {
13
9
  basePath;
14
- folderNames = new Map();
15
10
  constructor(basePath) {
16
11
  this.basePath = basePath;
17
12
  fs.mkdirSync(basePath, { recursive: true });
18
13
  }
19
- /** Register folder ID → path mapping for human-readable directory names */
20
- registerFolder(folderId, folderPath) {
21
- this.folderNames.set(folderId, sanitizeFolderName(folderPath));
22
- }
23
- folderDir(accountId, folderId) {
24
- const name = this.folderNames.get(folderId) || String(folderId);
25
- return path.join(this.basePath, accountId, name);
26
- }
27
14
  messagePath(accountId, folderId, uid) {
28
- return path.join(this.folderDir(accountId, folderId), `${uid}.eml`);
29
- }
30
- /** Check legacy path (numeric folder ID) */
31
- legacyPath(accountId, folderId, uid) {
32
15
  return path.join(this.basePath, accountId, String(folderId), `${uid}.eml`);
33
16
  }
34
17
  async putMessage(accountId, folderId, uid, raw) {
@@ -38,22 +21,15 @@ export class FileMessageStore {
38
21
  return filePath;
39
22
  }
40
23
  async getMessage(accountId, folderId, uid) {
41
- const filePath = this.messagePath(accountId, folderId, uid);
42
- if (fs.existsSync(filePath))
43
- return fs.readFileSync(filePath);
44
- // Fallback to legacy path
45
- const legacy = this.legacyPath(accountId, folderId, uid);
46
- return fs.readFileSync(legacy);
24
+ return fs.readFileSync(this.messagePath(accountId, folderId, uid));
47
25
  }
48
26
  async deleteMessage(accountId, folderId, uid) {
49
- for (const p of [this.messagePath(accountId, folderId, uid), this.legacyPath(accountId, folderId, uid)]) {
50
- if (fs.existsSync(p))
51
- fs.unlinkSync(p);
52
- }
27
+ const filePath = this.messagePath(accountId, folderId, uid);
28
+ if (fs.existsSync(filePath))
29
+ fs.unlinkSync(filePath);
53
30
  }
54
31
  async hasMessage(accountId, folderId, uid) {
55
- return fs.existsSync(this.messagePath(accountId, folderId, uid)) ||
56
- fs.existsSync(this.legacyPath(accountId, folderId, uid));
32
+ return fs.existsSync(this.messagePath(accountId, folderId, uid));
57
33
  }
58
34
  }
59
35
  //# sourceMappingURL=file-store.js.map