emailengine-app 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (405) hide show
  1. package/.eslintrc +14 -0
  2. package/.github/CODE_OF_CONDUCT.md +76 -0
  3. package/.github/FUNDING.yml +4 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. package/.github/contributing.md +17 -0
  7. package/.ncurc.js +10 -0
  8. package/.prettierrc.js +8 -0
  9. package/Dockerfile +17 -0
  10. package/Gruntfile.js +16 -0
  11. package/LICENSE.txt +661 -0
  12. package/README.md +524 -0
  13. package/bin/emailengine.js +14 -0
  14. package/config/default.toml +39 -0
  15. package/docker-compose.yml +47 -0
  16. package/encrypt.js +179 -0
  17. package/examples/api.md +137 -0
  18. package/examples/auth-server.js +104 -0
  19. package/getswagger.sh +8 -0
  20. package/lib/account.js +562 -0
  21. package/lib/append-list.js +67 -0
  22. package/lib/bounce-detect.js +380 -0
  23. package/lib/connection.js +1753 -0
  24. package/lib/consts.js +22 -0
  25. package/lib/db.js +72 -0
  26. package/lib/encrypt.js +100 -0
  27. package/lib/enum-message-flags.js +6 -0
  28. package/lib/get-raw-email.js +292 -0
  29. package/lib/get-secret.js +83 -0
  30. package/lib/logger.js +35 -0
  31. package/lib/lua/s-list-accounts.lua +51 -0
  32. package/lib/lua/z-expunge.lua +20 -0
  33. package/lib/lua/z-get-by-uid.lua +16 -0
  34. package/lib/lua/z-get-mailbox-id.lua +15 -0
  35. package/lib/lua/z-get-mailbox-path.lua +4 -0
  36. package/lib/lua/z-get.lua +15 -0
  37. package/lib/lua/z-push.lua +14 -0
  38. package/lib/lua/z-set.lua +17 -0
  39. package/lib/mailbox.js +1545 -0
  40. package/lib/message-port-stream.js +79 -0
  41. package/lib/schemas.js +311 -0
  42. package/lib/settings.js +63 -0
  43. package/lib/tools.js +488 -0
  44. package/package.json +79 -0
  45. package/scan.js +111 -0
  46. package/server.js +672 -0
  47. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.css +3872 -0
  48. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.css.map +1 -0
  49. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.min.css +7 -0
  50. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.min.css.map +1 -0
  51. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.css +325 -0
  52. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.css.map +1 -0
  53. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.min.css +8 -0
  54. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.min.css.map +1 -0
  55. package/static/bootstrap-4.6.0-dist/css/bootstrap.css +10298 -0
  56. package/static/bootstrap-4.6.0-dist/css/bootstrap.css.map +1 -0
  57. package/static/bootstrap-4.6.0-dist/css/bootstrap.min.css +7 -0
  58. package/static/bootstrap-4.6.0-dist/css/bootstrap.min.css.map +1 -0
  59. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.js +7045 -0
  60. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.js.map +1 -0
  61. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.min.js +7 -0
  62. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.min.js.map +1 -0
  63. package/static/bootstrap-4.6.0-dist/js/bootstrap.js +4432 -0
  64. package/static/bootstrap-4.6.0-dist/js/bootstrap.js.map +1 -0
  65. package/static/bootstrap-4.6.0-dist/js/bootstrap.min.js +7 -0
  66. package/static/bootstrap-4.6.0-dist/js/bootstrap.min.js.map +1 -0
  67. package/static/css/callout.css +63 -0
  68. package/static/css/emailengine.css +33 -0
  69. package/static/favicon/android-chrome-192x192.png +0 -0
  70. package/static/favicon/android-chrome-512x512.png +0 -0
  71. package/static/favicon/apple-touch-icon.png +0 -0
  72. package/static/favicon/favicon-16x16.png +0 -0
  73. package/static/favicon/favicon-32x32.png +0 -0
  74. package/static/favicon/manifest.json +20 -0
  75. package/static/favicon.ico +0 -0
  76. package/static/icons/alarm-fill.svg +3 -0
  77. package/static/icons/alarm.svg +7 -0
  78. package/static/icons/alert-circle-fill.svg +3 -0
  79. package/static/icons/alert-circle.svg +4 -0
  80. package/static/icons/alert-octagon-fill.svg +3 -0
  81. package/static/icons/alert-octagon.svg +5 -0
  82. package/static/icons/alert-square-fill.svg +3 -0
  83. package/static/icons/alert-square.svg +5 -0
  84. package/static/icons/alert-triangle-fill.svg +3 -0
  85. package/static/icons/alert-triangle.svg +5 -0
  86. package/static/icons/archive-fill.svg +3 -0
  87. package/static/icons/archive.svg +4 -0
  88. package/static/icons/arrow-bar-bottom.svg +4 -0
  89. package/static/icons/arrow-bar-left.svg +4 -0
  90. package/static/icons/arrow-bar-right.svg +4 -0
  91. package/static/icons/arrow-bar-up.svg +4 -0
  92. package/static/icons/arrow-clockwise.svg +4 -0
  93. package/static/icons/arrow-counterclockwise.svg +4 -0
  94. package/static/icons/arrow-down-left.svg +4 -0
  95. package/static/icons/arrow-down-right.svg +4 -0
  96. package/static/icons/arrow-down-short.svg +4 -0
  97. package/static/icons/arrow-down.svg +4 -0
  98. package/static/icons/arrow-left-right.svg +5 -0
  99. package/static/icons/arrow-left-short.svg +4 -0
  100. package/static/icons/arrow-left.svg +4 -0
  101. package/static/icons/arrow-repeat.svg +5 -0
  102. package/static/icons/arrow-right-short.svg +4 -0
  103. package/static/icons/arrow-right.svg +4 -0
  104. package/static/icons/arrow-up-down.svg +5 -0
  105. package/static/icons/arrow-up-left.svg +4 -0
  106. package/static/icons/arrow-up-right.svg +4 -0
  107. package/static/icons/arrow-up-short.svg +4 -0
  108. package/static/icons/arrow-up.svg +4 -0
  109. package/static/icons/arrows-angle-contract.svg +5 -0
  110. package/static/icons/arrows-angle-expand.svg +5 -0
  111. package/static/icons/arrows-collapse.svg +5 -0
  112. package/static/icons/arrows-expand.svg +5 -0
  113. package/static/icons/arrows-fullscreen.svg +7 -0
  114. package/static/icons/at.svg +3 -0
  115. package/static/icons/award.svg +4 -0
  116. package/static/icons/backspace-fill.svg +3 -0
  117. package/static/icons/backspace-reverse-fill.svg +3 -0
  118. package/static/icons/backspace-reverse.svg +5 -0
  119. package/static/icons/backspace.svg +5 -0
  120. package/static/icons/bar-chart-fill.svg +5 -0
  121. package/static/icons/bar-chart.svg +3 -0
  122. package/static/icons/battery-charging.svg +5 -0
  123. package/static/icons/battery-full.svg +4 -0
  124. package/static/icons/battery.svg +4 -0
  125. package/static/icons/bell-fill.svg +3 -0
  126. package/static/icons/bell.svg +4 -0
  127. package/static/icons/blockquote-left.svg +4 -0
  128. package/static/icons/blockquote-right.svg +4 -0
  129. package/static/icons/book-half-fill.svg +4 -0
  130. package/static/icons/book.svg +4 -0
  131. package/static/icons/bookmark-fill.svg +3 -0
  132. package/static/icons/bookmark.svg +3 -0
  133. package/static/icons/bootstrap-fill.svg +3 -0
  134. package/static/icons/bootstrap-reboot.svg +3 -0
  135. package/static/icons/bootstrap.svg +4 -0
  136. package/static/icons/box-arrow-bottom-left.svg +4 -0
  137. package/static/icons/box-arrow-bottom-right.svg +4 -0
  138. package/static/icons/box-arrow-down.svg +5 -0
  139. package/static/icons/box-arrow-left.svg +5 -0
  140. package/static/icons/box-arrow-right.svg +5 -0
  141. package/static/icons/box-arrow-up-left.svg +4 -0
  142. package/static/icons/box-arrow-up-right.svg +4 -0
  143. package/static/icons/box-arrow-up.svg +5 -0
  144. package/static/icons/braces.svg +3 -0
  145. package/static/icons/brightness-fill-high.svg +4 -0
  146. package/static/icons/brightness-fill-low.svg +11 -0
  147. package/static/icons/brightness-high.svg +3 -0
  148. package/static/icons/brightness-low.svg +11 -0
  149. package/static/icons/brush.svg +4 -0
  150. package/static/icons/bucket-fill.svg +4 -0
  151. package/static/icons/bucket.svg +4 -0
  152. package/static/icons/building.svg +5 -0
  153. package/static/icons/bullseye.svg +6 -0
  154. package/static/icons/calendar-fill.svg +4 -0
  155. package/static/icons/calendar.svg +4 -0
  156. package/static/icons/camera-video-fill.svg +4 -0
  157. package/static/icons/camera-video.svg +4 -0
  158. package/static/icons/camera.svg +5 -0
  159. package/static/icons/capslock-fill.svg +3 -0
  160. package/static/icons/capslock.svg +3 -0
  161. package/static/icons/chat-fill.svg +3 -0
  162. package/static/icons/chat.svg +3 -0
  163. package/static/icons/check-box.svg +4 -0
  164. package/static/icons/check-circle.svg +4 -0
  165. package/static/icons/check.svg +3 -0
  166. package/static/icons/chevron-compact-down.svg +3 -0
  167. package/static/icons/chevron-compact-left.svg +3 -0
  168. package/static/icons/chevron-compact-right.svg +3 -0
  169. package/static/icons/chevron-compact-up.svg +3 -0
  170. package/static/icons/chevron-down.svg +3 -0
  171. package/static/icons/chevron-left.svg +3 -0
  172. package/static/icons/chevron-right.svg +3 -0
  173. package/static/icons/chevron-up.svg +3 -0
  174. package/static/icons/circle-fill.svg +3 -0
  175. package/static/icons/circle-half.svg +3 -0
  176. package/static/icons/circle-slash.svg +3 -0
  177. package/static/icons/circle.svg +3 -0
  178. package/static/icons/clock-fill.svg +3 -0
  179. package/static/icons/clock.svg +4 -0
  180. package/static/icons/cloud-download.svg +5 -0
  181. package/static/icons/cloud-fill.svg +3 -0
  182. package/static/icons/cloud-upload.svg +5 -0
  183. package/static/icons/cloud.svg +3 -0
  184. package/static/icons/code-slash.svg +3 -0
  185. package/static/icons/code.svg +3 -0
  186. package/static/icons/columns-gutters.svg +3 -0
  187. package/static/icons/columns.svg +4 -0
  188. package/static/icons/command.svg +4 -0
  189. package/static/icons/compass.svg +5 -0
  190. package/static/icons/cone-striped.svg +4 -0
  191. package/static/icons/cone.svg +4 -0
  192. package/static/icons/controller.svg +5 -0
  193. package/static/icons/credit-card.svg +5 -0
  194. package/static/icons/cursor-fill.svg +3 -0
  195. package/static/icons/cursor.svg +3 -0
  196. package/static/icons/dash.svg +3 -0
  197. package/static/icons/diamond-half.svg +3 -0
  198. package/static/icons/diamond.svg +3 -0
  199. package/static/icons/display-fill.svg +5 -0
  200. package/static/icons/display.svg +4 -0
  201. package/static/icons/document-code.svg +4 -0
  202. package/static/icons/document-diff.svg +5 -0
  203. package/static/icons/document-richtext.svg +4 -0
  204. package/static/icons/document-spreadsheet.svg +5 -0
  205. package/static/icons/document-text.svg +4 -0
  206. package/static/icons/document.svg +3 -0
  207. package/static/icons/documents-alt.svg +4 -0
  208. package/static/icons/documents.svg +4 -0
  209. package/static/icons/dot.svg +3 -0
  210. package/static/icons/download.svg +5 -0
  211. package/static/icons/egg-fried.svg +4 -0
  212. package/static/icons/eject-fill.svg +3 -0
  213. package/static/icons/eject.svg +3 -0
  214. package/static/icons/envelope-fill.svg +3 -0
  215. package/static/icons/envelope-open-fill.svg +3 -0
  216. package/static/icons/envelope-open.svg +5 -0
  217. package/static/icons/envelope.svg +4 -0
  218. package/static/icons/eye-fill.svg +4 -0
  219. package/static/icons/eye-slash-fill.svg +5 -0
  220. package/static/icons/eye-slash.svg +6 -0
  221. package/static/icons/eye.svg +4 -0
  222. package/static/icons/filter.svg +3 -0
  223. package/static/icons/flag-fill.svg +4 -0
  224. package/static/icons/flag.svg +4 -0
  225. package/static/icons/folder-fill.svg +3 -0
  226. package/static/icons/folder-symlink-fill.svg +3 -0
  227. package/static/icons/folder-symlink.svg +5 -0
  228. package/static/icons/folder.svg +4 -0
  229. package/static/icons/fonts.svg +3 -0
  230. package/static/icons/forward-fill.svg +3 -0
  231. package/static/icons/forward.svg +3 -0
  232. package/static/icons/gear-fill.svg +3 -0
  233. package/static/icons/gear-wide-connected.svg +4 -0
  234. package/static/icons/gear-wide.svg +3 -0
  235. package/static/icons/gear.svg +4 -0
  236. package/static/icons/geo.svg +5 -0
  237. package/static/icons/graph-down.svg +5 -0
  238. package/static/icons/graph-up.svg +5 -0
  239. package/static/icons/grid-fill.svg +6 -0
  240. package/static/icons/grid.svg +3 -0
  241. package/static/icons/hammer.svg +4 -0
  242. package/static/icons/hash.svg +3 -0
  243. package/static/icons/heart-fill.svg +3 -0
  244. package/static/icons/heart.svg +3 -0
  245. package/static/icons/house-fill.svg +4 -0
  246. package/static/icons/house.svg +4 -0
  247. package/static/icons/image-alt.svg +4 -0
  248. package/static/icons/image-fill.svg +3 -0
  249. package/static/icons/image.svg +5 -0
  250. package/static/icons/images.svg +5 -0
  251. package/static/icons/inbox-fill.svg +4 -0
  252. package/static/icons/inbox.svg +4 -0
  253. package/static/icons/inboxes-fill.svg +4 -0
  254. package/static/icons/inboxes.svg +4 -0
  255. package/static/icons/info-fill.svg +3 -0
  256. package/static/icons/info-square-fill.svg +3 -0
  257. package/static/icons/info-square.svg +5 -0
  258. package/static/icons/info.svg +5 -0
  259. package/static/icons/justify-left.svg +3 -0
  260. package/static/icons/justify-right.svg +3 -0
  261. package/static/icons/justify.svg +3 -0
  262. package/static/icons/kanban-fill.svg +3 -0
  263. package/static/icons/kanban.svg +6 -0
  264. package/static/icons/laptop.svg +4 -0
  265. package/static/icons/layout-sidebar-reverse.svg +4 -0
  266. package/static/icons/layout-sidebar.svg +4 -0
  267. package/static/icons/layout-split.svg +3 -0
  268. package/static/icons/list-check.svg +3 -0
  269. package/static/icons/list-ol.svg +4 -0
  270. package/static/icons/list-task.svg +5 -0
  271. package/static/icons/list-ul.svg +3 -0
  272. package/static/icons/list.svg +3 -0
  273. package/static/icons/lock-fill.svg +4 -0
  274. package/static/icons/lock.svg +3 -0
  275. package/static/icons/map.svg +3 -0
  276. package/static/icons/mic.svg +4 -0
  277. package/static/icons/moon.svg +3 -0
  278. package/static/icons/music-player-fill.svg +4 -0
  279. package/static/icons/music-player.svg +5 -0
  280. package/static/icons/option.svg +3 -0
  281. package/static/icons/outlet.svg +5 -0
  282. package/static/icons/pause-fill.svg +3 -0
  283. package/static/icons/pause.svg +3 -0
  284. package/static/icons/pen.svg +5 -0
  285. package/static/icons/pencil.svg +4 -0
  286. package/static/icons/people-fill.svg +3 -0
  287. package/static/icons/people.svg +3 -0
  288. package/static/icons/person-fill.svg +3 -0
  289. package/static/icons/person.svg +3 -0
  290. package/static/icons/phone-landscape.svg +4 -0
  291. package/static/icons/phone.svg +4 -0
  292. package/static/icons/pie-chart-fill.svg +3 -0
  293. package/static/icons/pie-chart.svg +4 -0
  294. package/static/icons/play-fill.svg +3 -0
  295. package/static/icons/play.svg +3 -0
  296. package/static/icons/plug.svg +4 -0
  297. package/static/icons/plus.svg +4 -0
  298. package/static/icons/power.svg +4 -0
  299. package/static/icons/question-fill.svg +3 -0
  300. package/static/icons/question-square-fill.svg +3 -0
  301. package/static/icons/question-square.svg +4 -0
  302. package/static/icons/question.svg +4 -0
  303. package/static/icons/reply-all-fill.svg +4 -0
  304. package/static/icons/reply-all.svg +4 -0
  305. package/static/icons/reply-fill.svg +3 -0
  306. package/static/icons/reply.svg +3 -0
  307. package/static/icons/screwdriver.svg +3 -0
  308. package/static/icons/search.svg +4 -0
  309. package/static/icons/shield-fill.svg +3 -0
  310. package/static/icons/shield-lock-fill.svg +3 -0
  311. package/static/icons/shield-lock.svg +5 -0
  312. package/static/icons/shield-shaded.svg +4 -0
  313. package/static/icons/shield.svg +3 -0
  314. package/static/icons/shift-fill.svg +3 -0
  315. package/static/icons/shift.svg +3 -0
  316. package/static/icons/skip-backward-fill.svg +4 -0
  317. package/static/icons/skip-backward.svg +3 -0
  318. package/static/icons/skip-end-fill.svg +5 -0
  319. package/static/icons/skip-end.svg +4 -0
  320. package/static/icons/skip-forward-fill.svg +5 -0
  321. package/static/icons/skip-forward.svg +3 -0
  322. package/static/icons/skip-start-fill.svg +4 -0
  323. package/static/icons/skip-start.svg +4 -0
  324. package/static/icons/speaker.svg +4 -0
  325. package/static/icons/square-fill.svg +3 -0
  326. package/static/icons/square-half.svg +3 -0
  327. package/static/icons/square.svg +3 -0
  328. package/static/icons/star-fill.svg +3 -0
  329. package/static/icons/star-half.svg +3 -0
  330. package/static/icons/star.svg +3 -0
  331. package/static/icons/stop-fill.svg +3 -0
  332. package/static/icons/stop.svg +3 -0
  333. package/static/icons/stopwatch-fill.svg +3 -0
  334. package/static/icons/stopwatch.svg +5 -0
  335. package/static/icons/sun.svg +4 -0
  336. package/static/icons/table.svg +7 -0
  337. package/static/icons/tablet-landscape.svg +4 -0
  338. package/static/icons/tablet.svg +4 -0
  339. package/static/icons/tag-fill.svg +3 -0
  340. package/static/icons/tag.svg +4 -0
  341. package/static/icons/terminal-fill.svg +3 -0
  342. package/static/icons/terminal.svg +4 -0
  343. package/static/icons/text-center.svg +3 -0
  344. package/static/icons/text-indent-left.svg +3 -0
  345. package/static/icons/text-indent-right.svg +3 -0
  346. package/static/icons/text-left.svg +3 -0
  347. package/static/icons/text-right.svg +3 -0
  348. package/static/icons/three-dots-vertical.svg +3 -0
  349. package/static/icons/three-dots.svg +3 -0
  350. package/static/icons/toggle-off.svg +3 -0
  351. package/static/icons/toggle-on.svg +3 -0
  352. package/static/icons/toggles.svg +4 -0
  353. package/static/icons/tools.svg +4 -0
  354. package/static/icons/trash-fill.svg +3 -0
  355. package/static/icons/trash.svg +4 -0
  356. package/static/icons/triangle-fill.svg +3 -0
  357. package/static/icons/triangle-half.svg +3 -0
  358. package/static/icons/triangle.svg +3 -0
  359. package/static/icons/trophy.svg +6 -0
  360. package/static/icons/tv-fill.svg +3 -0
  361. package/static/icons/tv.svg +3 -0
  362. package/static/icons/type-bold.svg +3 -0
  363. package/static/icons/type-h1.svg +3 -0
  364. package/static/icons/type-h2.svg +3 -0
  365. package/static/icons/type-h3.svg +3 -0
  366. package/static/icons/type-italic.svg +3 -0
  367. package/static/icons/type-strikethrough.svg +4 -0
  368. package/static/icons/type-underline.svg +4 -0
  369. package/static/icons/type.svg +3 -0
  370. package/static/icons/unlock-fill.svg +4 -0
  371. package/static/icons/unlock.svg +3 -0
  372. package/static/icons/upload.svg +4 -0
  373. package/static/icons/volume-down-fill.svg +4 -0
  374. package/static/icons/volume-down.svg +4 -0
  375. package/static/icons/volume-mute-fill.svg +4 -0
  376. package/static/icons/volume-mute.svg +4 -0
  377. package/static/icons/volume-up-fill.svg +6 -0
  378. package/static/icons/volume-up.svg +6 -0
  379. package/static/icons/wallet.svg +3 -0
  380. package/static/icons/watch.svg +5 -0
  381. package/static/icons/wifi.svg +5 -0
  382. package/static/icons/window.svg +5 -0
  383. package/static/icons/wrench.svg +3 -0
  384. package/static/icons/x-circle-fill.svg +3 -0
  385. package/static/icons/x-circle.svg +5 -0
  386. package/static/icons/x-octagon-fill.svg +3 -0
  387. package/static/icons/x-octagon.svg +4 -0
  388. package/static/icons/x-square-fill.svg +3 -0
  389. package/static/icons/x-square.svg +4 -0
  390. package/static/icons/x.svg +4 -0
  391. package/static/index.html +752 -0
  392. package/static/js/emailengine.js +581 -0
  393. package/static/js/jquery-3.4.1.slim.min.js +2 -0
  394. package/static/js/moment-with-locales-2.24.0.min.js +1 -0
  395. package/static/js/popper.min.js +5 -0
  396. package/static/logo.png +0 -0
  397. package/systemd/emailengine.service +89 -0
  398. package/systemd/nginx-proxy.conf +77 -0
  399. package/views/error.hbs +2 -0
  400. package/workers/api.js +2266 -0
  401. package/workers/arena.js +89 -0
  402. package/workers/imap.js +611 -0
  403. package/workers/smtp.js +278 -0
  404. package/workers/submit.js +214 -0
  405. package/workers/webhooks.js +134 -0
@@ -0,0 +1,278 @@
1
+ 'use strict';
2
+
3
+ const { parentPort } = require('worker_threads');
4
+ const config = require('wild-config');
5
+ const logger = require('../lib/logger');
6
+ const { SMTPServer } = require('smtp-server');
7
+ const util = require('util');
8
+ const { redis } = require('../lib/db');
9
+ const { Account } = require('../lib/account');
10
+ const { getDuration } = require('../lib/tools');
11
+ const getSecret = require('../lib/get-secret');
12
+ const packageData = require('../package.json');
13
+
14
+ config.smtp = config.smtp || {
15
+ enabled: false,
16
+ port: 2525,
17
+ host: '127.0.0.1',
18
+ secret: '',
19
+ proxy: false
20
+ };
21
+
22
+ config.service = config.service || {};
23
+
24
+ const MAX_SIZE = 20 * 1024 * 1024;
25
+ const DEFAULT_EENGINE_TIMEOUT = 10 * 1000;
26
+
27
+ const EENGINE_TIMEOUT = getDuration(process.env.EENGINE_TIMEOUT || config.service.commandTimeout) || DEFAULT_EENGINE_TIMEOUT;
28
+
29
+ const SMTP_PORT = (process.env.EENGINE_SMTP_PORT && Number(process.env.EENGINE_SMTP_PORT)) || config.smtp.port || 2525;
30
+ const SMTP_HOST = process.env.EENGINE_SMTP_HOST || config.smtp.host || '127.0.0.1';
31
+ const SMTP_SECRET = process.env.EENGINE_SMTP_SECRET || config.smtp.secret;
32
+ const SMTP_PROXY = process.env.EENGINE_SMTP_PROXY
33
+ ? /^\s*(true|y|yes|1)\s*$/i.test(process.env.EENGINE_SMTP_PROXY)
34
+ : config.smtp.proxy === true || /^\s*(true|y|yes|1)\s*$/i.test(config.smtp.proxy);
35
+
36
+ const ACCOUNT_CACHE = new WeakMap();
37
+
38
+ let callQueue = new Map();
39
+ let mids = 0;
40
+
41
+ async function call(message, transferList) {
42
+ return new Promise((resolve, reject) => {
43
+ let mid = `${Date.now()}:${++mids}`;
44
+
45
+ let timer = setTimeout(() => {
46
+ let err = new Error('Timeout waiting for command response');
47
+ err.statusCode = 504;
48
+ err.code = 'Timeout';
49
+ reject(err);
50
+ }, message.timeout || EENGINE_TIMEOUT);
51
+
52
+ callQueue.set(mid, { resolve, reject, timer });
53
+
54
+ parentPort.postMessage(
55
+ {
56
+ cmd: 'call',
57
+ mid,
58
+ message
59
+ },
60
+ transferList
61
+ );
62
+ });
63
+ }
64
+
65
+ async function metrics(logger, key, method, ...args) {
66
+ try {
67
+ parentPort.postMessage({
68
+ cmd: 'metrics',
69
+ key,
70
+ method,
71
+ args
72
+ });
73
+ } catch (err) {
74
+ logger.error({ msg: 'Failed to post metrics to parent', err });
75
+ }
76
+ }
77
+
78
+ const smtpLogger = {};
79
+ for (let level of ['trace', 'debug', 'info', 'warn', 'error', 'fatal']) {
80
+ smtpLogger[level] = (data, message, ...args) => {
81
+ if (args && args.length) {
82
+ message = util.format(message, ...args);
83
+ }
84
+ data.msg = message;
85
+ data.sub = 'smtp-server';
86
+ if (typeof logger[level] === 'function') {
87
+ logger[level](data);
88
+ } else {
89
+ logger.debug(data);
90
+ }
91
+ };
92
+ }
93
+
94
+ async function onAuth(auth, session) {
95
+ if (SMTP_SECRET && auth.password !== SMTP_SECRET) {
96
+ throw new Error('Failed to authenticate user');
97
+ }
98
+
99
+ let accountObject = new Account({ account: auth.username, redis, call, secret: await getSecret() });
100
+ let accountData;
101
+ try {
102
+ accountData = await accountObject.loadAccountData();
103
+ } catch (err) {
104
+ let respErr = new Error('Failed to authenticate user');
105
+
106
+ if (!err.output || err.output.statusCode !== 404) {
107
+ // only log non-obvious errors
108
+ logger.error({ msg: 'Failed to load account data', account: auth.username, err });
109
+ respErr.statusCode = 454;
110
+ }
111
+
112
+ throw respErr;
113
+ }
114
+
115
+ if (!accountData) {
116
+ throw new Error('Failed to authenticate user');
117
+ }
118
+
119
+ ACCOUNT_CACHE.set(session, accountObject);
120
+ return { user: accountData.account };
121
+ }
122
+
123
+ async function init() {
124
+ let serverOptions = {
125
+ disabledCommands: ['STARTTLS'],
126
+ allowInsecureAuth: true,
127
+ logger: smtpLogger,
128
+ disableReverseLookup: true,
129
+ banner: 'EmailEngine MSA',
130
+ size: MAX_SIZE,
131
+ useProxy: SMTP_PROXY
132
+ };
133
+
134
+ serverOptions.onAuth = (auth, session, callback) => {
135
+ onAuth(auth, session)
136
+ .then(res => callback(null, res))
137
+ .catch(err => callback(err));
138
+ };
139
+
140
+ serverOptions.onData = (stream, session, callback) => {
141
+ let chunks = [];
142
+ let chunklen = 0;
143
+
144
+ stream.on('readable', () => {
145
+ let chunk;
146
+ while ((chunk = stream.read()) !== null) {
147
+ if (!stream.sizeExceeded) {
148
+ chunks.push(chunk);
149
+ chunklen += chunk.length;
150
+ }
151
+ }
152
+ });
153
+
154
+ stream.on('end', () => {
155
+ let err;
156
+ if (stream.sizeExceeded) {
157
+ err = new Error('Message exceeds fixed maximum message size');
158
+ err.responseCode = 552;
159
+ return callback(err);
160
+ }
161
+ let accountObject = ACCOUNT_CACHE.get(session);
162
+ if (!accountObject) {
163
+ err = new Error('Faild to get account data');
164
+ err.responseCode = 451;
165
+ return callback(err);
166
+ }
167
+
168
+ let message = Buffer.concat(chunks, chunklen);
169
+
170
+ let payload = {
171
+ envelope: {
172
+ from: session.envelope.mailFrom.address,
173
+ to: session.envelope.rcptTo.map(entry => entry.address)
174
+ },
175
+ raw: message
176
+ };
177
+
178
+ accountObject
179
+ .queueMessage(payload)
180
+ .then(res => {
181
+ // queued for later
182
+ metrics(logger, 'events', 'inc', {
183
+ event: 'smtpSubmitQueued'
184
+ });
185
+
186
+ logger.info({
187
+ msg: 'Message queued',
188
+ account: session.user,
189
+ messageId: res.messageId,
190
+ sendAt: res.sendAt,
191
+ queueId: res.queueId
192
+ });
193
+
194
+ return callback(null, `Message queued for delivery as ${res.queueId} (${new Date(res.sendAt).toISOString()})`);
195
+ })
196
+ .catch(err => {
197
+ metrics(logger, 'events', 'inc', {
198
+ event: 'smtpSubmitFail'
199
+ });
200
+ logger.error({ msg: 'Failed to submit message', account: session.user, err });
201
+ callback(err);
202
+ });
203
+ });
204
+ };
205
+
206
+ const server = new SMTPServer(serverOptions);
207
+
208
+ return await new Promise((resolve, reject) => {
209
+ server.once('error', err => reject(err));
210
+ server.listen(SMTP_PORT, SMTP_HOST, () => {
211
+ server.on('error', err => {
212
+ logger.error({
213
+ msg: 'SMTP Server Error',
214
+ err
215
+ });
216
+ });
217
+ resolve();
218
+ });
219
+ });
220
+ }
221
+
222
+ async function onCommand(command) {
223
+ logger.debug({ msg: 'Unhandled command', command });
224
+ }
225
+
226
+ parentPort.on('message', message => {
227
+ if (message && message.cmd === 'resp' && message.mid && callQueue.has(message.mid)) {
228
+ let { resolve, reject, timer } = callQueue.get(message.mid);
229
+ clearTimeout(timer);
230
+ callQueue.delete(message.mid);
231
+ if (message.error) {
232
+ let err = new Error(message.error);
233
+ if (message.code) {
234
+ err.code = message.code;
235
+ }
236
+ if (message.statusCode) {
237
+ err.statusCode = message.statusCode;
238
+ }
239
+ return reject(err);
240
+ } else {
241
+ return resolve(message.response);
242
+ }
243
+ }
244
+
245
+ if (message && message.cmd === 'call' && message.mid) {
246
+ return onCommand(message.message)
247
+ .then(response => {
248
+ parentPort.postMessage({
249
+ cmd: 'resp',
250
+ mid: message.mid,
251
+ response
252
+ });
253
+ })
254
+ .catch(err => {
255
+ parentPort.postMessage({
256
+ cmd: 'resp',
257
+ mid: message.mid,
258
+ error: err.message,
259
+ code: err.code,
260
+ statusCode: err.statusCode
261
+ });
262
+ });
263
+ }
264
+ });
265
+
266
+ init()
267
+ .then(() => {
268
+ logger.debug({
269
+ msg: 'Started SMTP server thread',
270
+ port: SMTP_PORT,
271
+ host: SMTP_HOST,
272
+ version: packageData.version
273
+ });
274
+ })
275
+ .catch(err => {
276
+ logger.error(err);
277
+ setImmediate(() => process.exit(3));
278
+ });
@@ -0,0 +1,214 @@
1
+ 'use strict';
2
+
3
+ const { parentPort } = require('worker_threads');
4
+ const config = require('wild-config');
5
+ const logger = require('../lib/logger');
6
+ const util = require('util');
7
+ const { redis, submitQueue, notifyQueue } = require('../lib/db');
8
+ const { Account } = require('../lib/account');
9
+ const { getDuration } = require('../lib/tools');
10
+ const getSecret = require('../lib/get-secret');
11
+ const packageData = require('../package.json');
12
+ const msgpack = require('msgpack5')();
13
+
14
+ const { EMAIL_FAILED_NOTIFY } = require('../lib/consts');
15
+
16
+ config.smtp = config.smtp || {
17
+ port: 2525,
18
+ host: '127.0.0.1'
19
+ };
20
+
21
+ config.queues = config.queues || {
22
+ submit: 1
23
+ };
24
+
25
+ config.service = config.service || {};
26
+
27
+ const DEFAULT_EENGINE_TIMEOUT = 10 * 1000;
28
+
29
+ const EENGINE_TIMEOUT = getDuration(process.env.EENGINE_TIMEOUT || config.service.commandTimeout) || DEFAULT_EENGINE_TIMEOUT;
30
+
31
+ const SUBMIT_QC = (process.env.EENGINE_SUBMIT_QC && Number(process.env.EENGINE_SUBMIT_QC)) || config.queues.submit || 1;
32
+
33
+ let callQueue = new Map();
34
+ let mids = 0;
35
+
36
+ async function call(message, transferList) {
37
+ return new Promise((resolve, reject) => {
38
+ let mid = `${Date.now()}:${++mids}`;
39
+
40
+ let timer = setTimeout(() => {
41
+ let err = new Error('Timeout waiting for command response');
42
+ err.statusCode = 504;
43
+ err.code = 'Timeout';
44
+ reject(err);
45
+ }, message.timeout || EENGINE_TIMEOUT);
46
+
47
+ callQueue.set(mid, { resolve, reject, timer });
48
+
49
+ parentPort.postMessage(
50
+ {
51
+ cmd: 'call',
52
+ mid,
53
+ message
54
+ },
55
+ transferList
56
+ );
57
+ });
58
+ }
59
+
60
+ async function metrics(logger, key, method, ...args) {
61
+ try {
62
+ parentPort.postMessage({
63
+ cmd: 'metrics',
64
+ key,
65
+ method,
66
+ args
67
+ });
68
+ } catch (err) {
69
+ logger.error({ msg: 'Failed to post metrics to parent', err });
70
+ }
71
+ }
72
+
73
+ async function notify(account, event, data) {
74
+ metrics(logger, 'events', 'inc', {
75
+ event
76
+ });
77
+
78
+ let payload = {
79
+ account,
80
+ date: new Date().toISOString()
81
+ };
82
+
83
+ if (event) {
84
+ payload.event = event;
85
+ }
86
+
87
+ if (data) {
88
+ payload.data = data;
89
+ }
90
+
91
+ await notifyQueue.add(event, payload, {
92
+ removeOnComplete: true,
93
+ removeOnFail: true,
94
+ attempts: 5,
95
+ backoff: {
96
+ type: 'exponential',
97
+ delay: 2000
98
+ }
99
+ });
100
+ }
101
+
102
+ const smtpLogger = {};
103
+ for (let level of ['trace', 'debug', 'info', 'warn', 'error', 'fatal']) {
104
+ smtpLogger[level] = (data, message, ...args) => {
105
+ if (args && args.length) {
106
+ message = util.format(message, ...args);
107
+ }
108
+ data.msg = message;
109
+ data.sub = 'smtp-server';
110
+ if (typeof logger[level] === 'function') {
111
+ logger[level](data);
112
+ } else {
113
+ logger.debug(data);
114
+ }
115
+ };
116
+ }
117
+
118
+ submitQueue.process('*', SUBMIT_QC, async job => {
119
+ let queueEntryBuf = await redis.hgetBuffer(`iaq:${job.data.account}`, job.data.qId);
120
+
121
+ let queueEntry;
122
+ try {
123
+ queueEntry = msgpack.decode(queueEntryBuf);
124
+ } catch (err) {
125
+ logger.error({ msg: 'Failed to parse queued email entry', job: job.data, err });
126
+ return;
127
+ }
128
+
129
+ if (!queueEntry) {
130
+ //could be expired?
131
+ return false;
132
+ }
133
+
134
+ let accountObject = new Account({ account: job.data.account, redis, call, secret: await getSecret() });
135
+
136
+ let res = await accountObject.submitMessage(queueEntry);
137
+
138
+ logger.info({ msg: 'Submitted queued message for delivery', account: queueEntry.account, queueId: job.data.qId, messageId: job.data.messageId, res });
139
+ });
140
+
141
+ submitQueue.on('completed', async job => {
142
+ if (job.data && job.data.account && job.data.qId) {
143
+ try {
144
+ await redis.hdel(`iaq:${job.data.account}`, job.data.qId);
145
+ } catch (err) {
146
+ logger.error({ msg: 'Failed to remove queue entry', account: job.data.account, queueId: job.data.qId, messageId: job.data.messageId, err });
147
+ }
148
+ }
149
+ });
150
+
151
+ submitQueue.on('failed', async job => {
152
+ if (job.finishedOn) {
153
+ // this was final attempt, remove it
154
+ if (job.data && job.data.account && job.data.qId) {
155
+ try {
156
+ await redis.hdel(`iaq:${job.data.account}`, job.data.qId);
157
+ } catch (err) {
158
+ logger.error({ msg: 'Failed to remove queue entry', account: job.data.account, queueId: job.data.qId, messageId: job.data.messageId, err });
159
+ }
160
+ // report as failed
161
+ await notify(job.data.account, EMAIL_FAILED_NOTIFY, {
162
+ messageId: job.data.messageId,
163
+ queueId: job.data.qId,
164
+ error: job.stacktrace && job.stacktrace[0] && job.stacktrace[0].split('\n').shift()
165
+ });
166
+ }
167
+ }
168
+ });
169
+
170
+ async function onCommand(command) {
171
+ logger.debug({ msg: 'Unhandled command', command });
172
+ }
173
+
174
+ parentPort.on('message', message => {
175
+ if (message && message.cmd === 'resp' && message.mid && callQueue.has(message.mid)) {
176
+ let { resolve, reject, timer } = callQueue.get(message.mid);
177
+ clearTimeout(timer);
178
+ callQueue.delete(message.mid);
179
+ if (message.error) {
180
+ let err = new Error(message.error);
181
+ if (message.code) {
182
+ err.code = message.code;
183
+ }
184
+ if (message.statusCode) {
185
+ err.statusCode = message.statusCode;
186
+ }
187
+ return reject(err);
188
+ } else {
189
+ return resolve(message.response);
190
+ }
191
+ }
192
+
193
+ if (message && message.cmd === 'call' && message.mid) {
194
+ return onCommand(message.message)
195
+ .then(response => {
196
+ parentPort.postMessage({
197
+ cmd: 'resp',
198
+ mid: message.mid,
199
+ response
200
+ });
201
+ })
202
+ .catch(err => {
203
+ parentPort.postMessage({
204
+ cmd: 'resp',
205
+ mid: message.mid,
206
+ error: err.message,
207
+ code: err.code,
208
+ statusCode: err.statusCode
209
+ });
210
+ });
211
+ }
212
+ });
213
+
214
+ logger.info({ msg: 'Started SMTP submission worker thread', version: packageData.version });
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+
3
+ const { parentPort } = require('worker_threads');
4
+ const config = require('wild-config');
5
+ const fetch = require('node-fetch');
6
+ const { redis, notifyQueue } = require('../lib/db');
7
+ const settings = require('../lib/settings');
8
+ const logger = require('../lib/logger');
9
+ const packageData = require('../package.json');
10
+ const { EMAIL_SENT_NOTIFY, EMAIL_FAILED_NOTIFY, EMAIL_BOUNCE_NOTIFY } = require('../lib/consts');
11
+ const he = require('he');
12
+
13
+ config.queues = config.queues || {
14
+ notify: 1
15
+ };
16
+
17
+ const NOTIFY_QC = (process.env.EENGINE_NOTIFY_QC && Number(process.env.EENGINE_NOTIFY_QC)) || config.queues.notify || 1;
18
+
19
+ function getAccountKey(account) {
20
+ return `iad:${account}`;
21
+ }
22
+
23
+ async function metrics(logger, key, method, ...args) {
24
+ try {
25
+ parentPort.postMessage({
26
+ cmd: 'metrics',
27
+ key,
28
+ method,
29
+ args
30
+ });
31
+ } catch (err) {
32
+ logger.error({ msg: 'Failed to post metrics to parent', err });
33
+ }
34
+ }
35
+
36
+ notifyQueue.process('*', NOTIFY_QC, async job => {
37
+ // validate if we should even process this webhook
38
+
39
+ let accountExists = await redis.exists(getAccountKey(job.data.account));
40
+ if (!accountExists) {
41
+ logger.debug({ msg: 'Account is not enabled', action: 'webhook', event: job.name, account: job.data.account });
42
+ return;
43
+ }
44
+ let webhooks = await settings.get('webhooks');
45
+ if (!webhooks) {
46
+ // logger.debug({ msg: 'Webhook URL is not set', action: 'webhook', event: job.name, account: job.data.account });
47
+ return;
48
+ }
49
+
50
+ let webhookEvents = await settings.get('webhookEvents');
51
+ if (webhookEvents && !webhookEvents.includes('*') && !webhookEvents.includes(job.name)) {
52
+ logger.trace({ msg: 'Webhook event not in whitelist', action: 'webhook', event: job.name, account: job.data.account, webhookEvents, data: job.data });
53
+ return;
54
+ }
55
+
56
+ logger.trace({ msg: 'Received new notification', webhooks, event: job.name, data: job.data });
57
+ if (!job.data.path && ![EMAIL_SENT_NOTIFY, EMAIL_FAILED_NOTIFY, EMAIL_BOUNCE_NOTIFY].includes(job.data.event)) {
58
+ // ignore non-message related events
59
+ return;
60
+ }
61
+
62
+ let headers = {
63
+ 'Content-Type': 'application/json',
64
+ 'User-Agent': `${packageData.name}/${packageData.version} (+https://emailengine.app)`
65
+ };
66
+
67
+ let parsed = new URL(webhooks);
68
+ let username, password;
69
+
70
+ if (parsed.username) {
71
+ username = he.decode(parsed.username);
72
+ parsed.username = '';
73
+ }
74
+
75
+ if (parsed.password) {
76
+ password = he.decode(parsed.password);
77
+ parsed.password = '';
78
+ }
79
+
80
+ if (username || password) {
81
+ headers.Authorization = `Basic ${Buffer.from(he.encode(username || '') + ':' + he.encode(password || '')).toString('base64')}`;
82
+ }
83
+
84
+ let start = Date.now();
85
+ let duration;
86
+ try {
87
+ let res;
88
+
89
+ try {
90
+ res = await fetch(parsed.toString(), {
91
+ method: 'post',
92
+ body: JSON.stringify(job.data),
93
+ headers
94
+ });
95
+ duration = Date.now() - start;
96
+ } catch (err) {
97
+ duration = Date.now() - start;
98
+ throw err;
99
+ }
100
+
101
+ if (!res.ok) {
102
+ let err = new Error(`Invalid response: ${res.status} ${res.statusText}`);
103
+ err.status = res.status;
104
+ throw err;
105
+ }
106
+
107
+ metrics(logger, 'webhooks', 'inc', {
108
+ event: job.name,
109
+ status: 'success'
110
+ });
111
+ } catch (err) {
112
+ if (err.status === 410 || err.status === 404) {
113
+ // disable webhook
114
+ logger.error({ msg: 'Webhooks were disabled by server', webhooks, event: job.name, err });
115
+ await settings.set('webhooks', '');
116
+ return;
117
+ }
118
+
119
+ logger.error({ msg: 'Failed posting webhook', webhooks, event: job.name, err });
120
+
121
+ metrics(logger, 'webhooks', 'inc', {
122
+ event: job.name,
123
+ status: 'fail'
124
+ });
125
+
126
+ throw err;
127
+ } finally {
128
+ if (duration) {
129
+ metrics(logger, 'webhook_req', 'observe', duration);
130
+ }
131
+ }
132
+ });
133
+
134
+ logger.info({ msg: 'Started Webhooks worker thread', version: packageData.version });