emailengine-app 1.14.8 → 2.60.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2139) hide show
  1. package/.env.development +49 -0
  2. package/.env.example +82 -0
  3. package/.env.production +87 -0
  4. package/.eslintignore +1 -0
  5. package/.github/workflows/deploy.yml +104 -0
  6. package/.github/workflows/release.yaml +107 -0
  7. package/.github/workflows/test.yml +82 -0
  8. package/.ncurc.js +19 -5
  9. package/.prettierignore +44 -0
  10. package/CHANGELOG.md +1110 -0
  11. package/DOCKER_DEPLOYMENT.md +495 -0
  12. package/Dockerfile +53 -6
  13. package/Dockerfile-legacy +18 -0
  14. package/Fluid-Attacks-Results.csv +1 -0
  15. package/Gruntfile.js +46 -5
  16. package/LICENSE_EMAILENGINE.txt +110 -0
  17. package/README.md +72 -344
  18. package/app.json +40 -0
  19. package/bin/emailengine.js +283 -38
  20. package/config/default.toml +9 -11
  21. package/config/test.toml +45 -0
  22. package/copy-static-files.sh +34 -0
  23. package/data/google-crawlers.json +797 -0
  24. package/docker-compose.yml +103 -31
  25. package/encrypt.js +85 -10
  26. package/eslint.config.js +110 -0
  27. package/examples/auth-server.js +121 -69
  28. package/examples/grafana-dashboard.json +2375 -0
  29. package/help.txt +84 -0
  30. package/install.sh +426 -0
  31. package/lib/account.js +2348 -124
  32. package/lib/add-trackers.js +119 -0
  33. package/lib/api-routes/bull-board-routes.js +60 -0
  34. package/lib/api-routes/chat-routes.js +519 -0
  35. package/lib/api-routes/template-routes.js +490 -0
  36. package/lib/append-list.js +9 -2
  37. package/lib/arf-detect.js +200 -0
  38. package/lib/autodetect-imap-settings.js +781 -0
  39. package/lib/bounce-detect.js +280 -37
  40. package/lib/capa.js +97 -0
  41. package/lib/consts.js +210 -1
  42. package/lib/db.js +227 -8
  43. package/lib/document-store.js +54 -0
  44. package/lib/email-client/base-client.js +3677 -0
  45. package/lib/email-client/gmail-client.js +2796 -0
  46. package/lib/email-client/imap/mailbox.js +3721 -0
  47. package/lib/email-client/imap/subconnection.js +269 -0
  48. package/lib/email-client/imap-client.js +2628 -0
  49. package/lib/email-client/outlook-client.js +3805 -0
  50. package/lib/encrypt.js +85 -14
  51. package/lib/es.js +784 -0
  52. package/lib/feature-flags.js +42 -0
  53. package/lib/gateway.js +271 -0
  54. package/lib/generate-text-preview.js +56 -0
  55. package/lib/get-raw-email.js +302 -42
  56. package/lib/get-secret.js +23 -67
  57. package/lib/headers-rewriter.js +33 -0
  58. package/lib/imapproxy/imap-core/index.js +4 -0
  59. package/lib/imapproxy/imap-core/lib/commands/append.js +187 -0
  60. package/lib/imapproxy/imap-core/lib/commands/authenticate-plain.js +145 -0
  61. package/lib/imapproxy/imap-core/lib/commands/capability.js +13 -0
  62. package/lib/imapproxy/imap-core/lib/commands/check.js +10 -0
  63. package/lib/imapproxy/imap-core/lib/commands/close.js +44 -0
  64. package/lib/imapproxy/imap-core/lib/commands/compress.js +102 -0
  65. package/lib/imapproxy/imap-core/lib/commands/copy.js +109 -0
  66. package/lib/imapproxy/imap-core/lib/commands/create.js +93 -0
  67. package/lib/imapproxy/imap-core/lib/commands/delete.js +84 -0
  68. package/lib/imapproxy/imap-core/lib/commands/enable.js +36 -0
  69. package/lib/imapproxy/imap-core/lib/commands/expunge.js +68 -0
  70. package/lib/imapproxy/imap-core/lib/commands/fetch.js +385 -0
  71. package/lib/imapproxy/imap-core/lib/commands/getquota.js +85 -0
  72. package/lib/imapproxy/imap-core/lib/commands/getquotaroot.js +111 -0
  73. package/lib/imapproxy/imap-core/lib/commands/id.js +111 -0
  74. package/lib/imapproxy/imap-core/lib/commands/idle.js +45 -0
  75. package/lib/imapproxy/imap-core/lib/commands/list.js +218 -0
  76. package/lib/imapproxy/imap-core/lib/commands/login.js +135 -0
  77. package/lib/imapproxy/imap-core/lib/commands/logout.js +26 -0
  78. package/lib/imapproxy/imap-core/lib/commands/lsub.js +102 -0
  79. package/lib/imapproxy/imap-core/lib/commands/move.js +106 -0
  80. package/lib/imapproxy/imap-core/lib/commands/namespace.js +14 -0
  81. package/lib/imapproxy/imap-core/lib/commands/noop.js +10 -0
  82. package/lib/imapproxy/imap-core/lib/commands/rename.js +102 -0
  83. package/lib/imapproxy/imap-core/lib/commands/search.js +306 -0
  84. package/lib/imapproxy/imap-core/lib/commands/select.js +248 -0
  85. package/lib/imapproxy/imap-core/lib/commands/setquota.js +24 -0
  86. package/lib/imapproxy/imap-core/lib/commands/starttls.js +100 -0
  87. package/lib/imapproxy/imap-core/lib/commands/status.js +149 -0
  88. package/lib/imapproxy/imap-core/lib/commands/store.js +208 -0
  89. package/lib/imapproxy/imap-core/lib/commands/subscribe.js +69 -0
  90. package/lib/imapproxy/imap-core/lib/commands/uid-expunge.js +71 -0
  91. package/lib/imapproxy/imap-core/lib/commands/uid-store.js +170 -0
  92. package/lib/imapproxy/imap-core/lib/commands/unselect.js +14 -0
  93. package/lib/imapproxy/imap-core/lib/commands/unsubscribe.js +69 -0
  94. package/lib/imapproxy/imap-core/lib/handler/README.md +146 -0
  95. package/lib/imapproxy/imap-core/lib/handler/imap-compile-stream.js +252 -0
  96. package/lib/imapproxy/imap-core/lib/handler/imap-compiler.js +134 -0
  97. package/lib/imapproxy/imap-core/lib/handler/imap-formal-syntax.js +147 -0
  98. package/lib/imapproxy/imap-core/lib/handler/imap-handler.js +11 -0
  99. package/lib/imapproxy/imap-core/lib/handler/imap-parser.js +678 -0
  100. package/lib/imapproxy/imap-core/lib/imap-command.js +381 -0
  101. package/lib/imapproxy/imap-core/lib/imap-composer.js +71 -0
  102. package/lib/imapproxy/imap-core/lib/imap-connection.js +929 -0
  103. package/lib/imapproxy/imap-core/lib/imap-server.js +426 -0
  104. package/lib/imapproxy/imap-core/lib/imap-stream.js +172 -0
  105. package/lib/imapproxy/imap-core/lib/imap-tools.js +789 -0
  106. package/lib/imapproxy/imap-core/lib/indexer/body-structure.js +295 -0
  107. package/lib/imapproxy/imap-core/lib/indexer/create-envelope.js +103 -0
  108. package/lib/imapproxy/imap-core/lib/indexer/indexer.js +904 -0
  109. package/lib/imapproxy/imap-core/lib/indexer/parse-mime-tree.js +340 -0
  110. package/lib/imapproxy/imap-core/lib/length-limiter.js +76 -0
  111. package/lib/imapproxy/imap-core/lib/parse-date.js +225 -0
  112. package/lib/imapproxy/imap-core/lib/search.js +330 -0
  113. package/lib/imapproxy/imap-core/lib/tls-options.js +69 -0
  114. package/lib/imapproxy/imap-core/memory-notifier.js +129 -0
  115. package/lib/imapproxy/imap-core/test/client.js +46 -0
  116. package/lib/imapproxy/imap-core/test/fixtures/append.eml +1196 -0
  117. package/lib/imapproxy/imap-core/test/fixtures/chunks.js +44 -0
  118. package/lib/imapproxy/imap-core/test/fixtures/fix1.eml +6 -0
  119. package/lib/imapproxy/imap-core/test/fixtures/fix2.eml +599 -0
  120. package/lib/imapproxy/imap-core/test/fixtures/fix3.eml +32 -0
  121. package/lib/imapproxy/imap-core/test/fixtures/fix4.eml +6 -0
  122. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.eml +599 -0
  123. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.js +2740 -0
  124. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.json +1411 -0
  125. package/lib/imapproxy/imap-core/test/fixtures/mimetree.js +85 -0
  126. package/lib/imapproxy/imap-core/test/fixtures/nodemailer.eml +582 -0
  127. package/lib/imapproxy/imap-core/test/fixtures/ryan_finnie_mime_torture.eml +599 -0
  128. package/lib/imapproxy/imap-core/test/fixtures/simple.eml +42 -0
  129. package/lib/imapproxy/imap-core/test/fixtures/simple.json +164 -0
  130. package/lib/imapproxy/imap-core/test/imap-compile-stream-test.js +671 -0
  131. package/lib/imapproxy/imap-core/test/imap-compiler-test.js +272 -0
  132. package/lib/imapproxy/imap-core/test/imap-indexer-test.js +236 -0
  133. package/lib/imapproxy/imap-core/test/imap-parser-test.js +922 -0
  134. package/lib/imapproxy/imap-core/test/memory-notifier.js +129 -0
  135. package/lib/imapproxy/imap-core/test/prepare.sh +74 -0
  136. package/lib/imapproxy/imap-core/test/protocol-test.js +1756 -0
  137. package/lib/imapproxy/imap-core/test/search-test.js +1356 -0
  138. package/lib/imapproxy/imap-core/test/test-client.js +152 -0
  139. package/lib/imapproxy/imap-core/test/test-server.js +623 -0
  140. package/lib/imapproxy/imap-core/test/tools-test.js +22 -0
  141. package/lib/imapproxy/imap-server.js +577 -0
  142. package/lib/lists.js +92 -0
  143. package/lib/llm-pre-process.js +141 -0
  144. package/lib/logger.js +43 -4
  145. package/lib/lua/ee-get-idempotency.lua +74 -0
  146. package/lib/lua/ee-list-add.lua +34 -0
  147. package/lib/lua/ee-list-remove.lua +37 -0
  148. package/lib/lua/h-incrby-exists.lua +28 -0
  149. package/lib/lua/h-push.lua +32 -0
  150. package/lib/lua/h-set-bigger.lua +40 -0
  151. package/lib/lua/h-set-exists.lua +29 -0
  152. package/lib/lua/h-set-new.lua +29 -0
  153. package/lib/lua/h-update-bigger.lua +45 -0
  154. package/lib/lua/s-list-accounts.lua +64 -14
  155. package/lib/lua/z-expunge.lua +86 -10
  156. package/lib/lua/z-get-by-uid.lua +28 -5
  157. package/lib/lua/z-get-mailbox-id.lua +24 -2
  158. package/lib/lua/z-get-mailbox-path.lua +16 -0
  159. package/lib/lua/z-get.lua +27 -4
  160. package/lib/lua/z-set.lua +24 -2
  161. package/lib/metrics-collector.js +209 -0
  162. package/lib/oauth/gmail.js +663 -0
  163. package/lib/oauth/mail-ru.js +310 -0
  164. package/lib/oauth/outlook.js +541 -0
  165. package/lib/oauth/pubsub/google.js +247 -0
  166. package/lib/oauth2-apps.js +1420 -0
  167. package/lib/outbox.js +140 -0
  168. package/lib/payload-examples-documents.json +404 -0
  169. package/lib/payload-examples-webhooks.json +266 -0
  170. package/lib/pre-process.js +193 -0
  171. package/lib/rate-limit.js +32 -0
  172. package/lib/reconnection-manager.js +106 -0
  173. package/lib/redis-scan-delete.js +82 -0
  174. package/lib/redis-url.js +78 -0
  175. package/lib/rewrite-text-nodes.js +267 -0
  176. package/lib/routes-ui.js +10247 -0
  177. package/lib/schemas.js +1576 -187
  178. package/lib/settings.js +263 -12
  179. package/lib/sub-script.js +109 -0
  180. package/lib/templates.js +240 -0
  181. package/lib/threads.js +155 -0
  182. package/lib/tokens.js +353 -0
  183. package/lib/tools.js +1773 -41
  184. package/lib/translations.js +33 -0
  185. package/lib/webhooks.js +605 -0
  186. package/list-generate.js +96 -0
  187. package/package.json +130 -54
  188. package/render.yaml +44 -0
  189. package/sbom.json +1 -0
  190. package/scan.js +14 -2
  191. package/scripts/README.md +50 -0
  192. package/scripts/refresh-test-tokens.js +180 -0
  193. package/server.js +2902 -376
  194. package/setup-production.sh +201 -0
  195. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/css/bootstrap-grid.css +3 -3
  196. package/static/bootstrap-4.6.2-dist/css/bootstrap-grid.css.map +1 -0
  197. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/css/bootstrap-grid.min.css +3 -3
  198. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/css/bootstrap-grid.min.css.map +1 -1
  199. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/css/bootstrap-reboot.css +3 -3
  200. package/static/bootstrap-4.6.2-dist/css/bootstrap-reboot.css.map +1 -0
  201. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/css/bootstrap-reboot.min.css +3 -3
  202. package/static/bootstrap-4.6.2-dist/css/bootstrap-reboot.min.css.map +1 -0
  203. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/css/bootstrap.css +60 -26
  204. package/static/bootstrap-4.6.2-dist/css/bootstrap.css.map +1 -0
  205. package/static/bootstrap-4.6.2-dist/css/bootstrap.min.css +7 -0
  206. package/static/bootstrap-4.6.2-dist/css/bootstrap.min.css.map +1 -0
  207. package/static/bootstrap-4.6.2-dist/js/bootstrap.bundle.js +7155 -0
  208. package/static/bootstrap-4.6.2-dist/js/bootstrap.bundle.js.map +784 -0
  209. package/static/bootstrap-4.6.2-dist/js/bootstrap.bundle.min.js +7 -0
  210. package/static/bootstrap-4.6.2-dist/js/bootstrap.bundle.min.js.map +959 -0
  211. package/static/{bootstrap-4.6.0-dist → bootstrap-4.6.2-dist}/js/bootstrap.js +792 -868
  212. package/static/bootstrap-4.6.2-dist/js/bootstrap.js.map +1 -0
  213. package/static/bootstrap-4.6.2-dist/js/bootstrap.min.js +7 -0
  214. package/static/bootstrap-4.6.2-dist/js/bootstrap.min.js.map +1 -0
  215. package/static/css/app.css +146 -0
  216. package/static/css/arena.css +777 -0
  217. package/static/css/default.min.css +9 -0
  218. package/static/css/highlight.min.css +9 -0
  219. package/static/css/sb-admin-2.min.css +10 -0
  220. package/static/emailengine.ico +0 -0
  221. package/static/favicon/android-chrome-192x192.png +0 -0
  222. package/static/favicon/android-chrome-512x512.png +0 -0
  223. package/static/favicon/apple-touch-icon.png +0 -0
  224. package/static/favicon/favicon-16x16.png +0 -0
  225. package/static/favicon/favicon-32x32.png +0 -0
  226. package/static/favicon/favicon.ico +0 -0
  227. package/static/favicon.ico +0 -0
  228. package/static/fonts/nunito/OFL.txt +93 -0
  229. package/static/fonts/nunito/XRXV3I6Li01BKofINeaBTMnFcQ.woff2 +0 -0
  230. package/static/fonts/nunito-font.css +66 -0
  231. package/static/front/EmailEngine_logo_horiz.png +0 -0
  232. package/static/front/EmailEngine_logo_vert.png +0 -0
  233. package/static/front/index.html +57 -0
  234. package/static/front/logo.png +0 -0
  235. package/static/imap-capabilities-1.csv +71 -0
  236. package/static/index.html +30 -713
  237. package/static/js/ace/README.txt +1 -0
  238. package/static/js/ace/ace.js +23 -0
  239. package/static/js/ace/ext-language_tools.js +8 -0
  240. package/static/js/ace/ext-searchbox.js +8 -0
  241. package/static/js/ace/mode-handlebars.js +8 -0
  242. package/static/js/ace/mode-html.js +8 -0
  243. package/static/js/ace/mode-javascript.js +8 -0
  244. package/static/js/ace/mode-json.js +8 -0
  245. package/static/js/ace/mode-markdown.js +8 -0
  246. package/static/js/ace/snippets/javascript.js +8 -0
  247. package/static/js/ace/snippets/markdown.js +8 -0
  248. package/static/js/ace/theme-kuroir.js +8 -0
  249. package/static/js/ace/theme-xcode.js +8 -0
  250. package/static/js/ace/worker-html.js +1 -0
  251. package/static/js/ace/worker-javascript.js +1 -0
  252. package/static/js/ace/worker-json.js +1 -0
  253. package/static/js/app.js +526 -0
  254. package/static/js/bootstrap-autocomplete.min.js +1 -0
  255. package/static/js/clipboard.min.js +517 -0
  256. package/static/js/ee-client.js +1977 -0
  257. package/static/js/evaluation-worker.js +47 -0
  258. package/static/js/highlight.min.js +1173 -0
  259. package/static/js/jquery-3.6.0.min.js +2 -0
  260. package/static/js/sb-admin-2.min.js +7 -0
  261. package/static/licenses.html +6606 -50
  262. package/static/logo/EmailEngine_logo_horiz.png +0 -0
  263. package/static/logo/EmailEngine_logo_vert.png +0 -0
  264. package/static/logo.png +0 -0
  265. package/static/logo_transparent.png +0 -0
  266. package/static/logo_transparent_small.png +0 -0
  267. package/static/logo_wide.png +0 -0
  268. package/static/preview/header-template.png +0 -0
  269. package/static/preview/render.png +0 -0
  270. package/static/preview/translation.png +0 -0
  271. package/static/providers/google_dark.png +0 -0
  272. package/static/providers/google_dark_edited.png +0 -0
  273. package/static/providers/google_light.png +0 -0
  274. package/static/providers/ms_dark.svg +1 -0
  275. package/static/providers/ms_light.svg +1 -0
  276. package/static/robots.txt +4 -0
  277. package/static/undraw_profile.svg +38 -0
  278. package/static/vendor/fontawesome-free/LICENSE.txt +34 -0
  279. package/static/vendor/fontawesome-free/attribution.js +3 -0
  280. package/static/vendor/fontawesome-free/css/all.css +4619 -0
  281. package/static/vendor/fontawesome-free/css/all.min.css +5 -0
  282. package/static/vendor/fontawesome-free/css/brands.css +15 -0
  283. package/static/vendor/fontawesome-free/css/brands.min.css +5 -0
  284. package/static/vendor/fontawesome-free/css/fontawesome.css +4585 -0
  285. package/static/vendor/fontawesome-free/css/fontawesome.min.css +5 -0
  286. package/static/vendor/fontawesome-free/css/regular.css +15 -0
  287. package/static/vendor/fontawesome-free/css/regular.min.css +5 -0
  288. package/static/vendor/fontawesome-free/css/solid.css +16 -0
  289. package/static/vendor/fontawesome-free/css/solid.min.css +5 -0
  290. package/static/vendor/fontawesome-free/css/svg-with-js.css +371 -0
  291. package/static/vendor/fontawesome-free/css/svg-with-js.min.css +5 -0
  292. package/static/vendor/fontawesome-free/css/v4-shims.css +2172 -0
  293. package/static/vendor/fontawesome-free/css/v4-shims.min.css +5 -0
  294. package/static/vendor/fontawesome-free/js/all.js +4467 -0
  295. package/static/vendor/fontawesome-free/js/all.min.js +5 -0
  296. package/static/vendor/fontawesome-free/js/brands.js +586 -0
  297. package/static/vendor/fontawesome-free/js/brands.min.js +5 -0
  298. package/static/vendor/fontawesome-free/js/conflict-detection.js +998 -0
  299. package/static/vendor/fontawesome-free/js/conflict-detection.min.js +5 -0
  300. package/static/vendor/fontawesome-free/js/fontawesome.js +2483 -0
  301. package/static/vendor/fontawesome-free/js/fontawesome.min.js +5 -0
  302. package/static/vendor/fontawesome-free/js/regular.js +280 -0
  303. package/static/vendor/fontawesome-free/js/regular.min.js +5 -0
  304. package/static/vendor/fontawesome-free/js/solid.js +1130 -0
  305. package/static/vendor/fontawesome-free/js/solid.min.js +5 -0
  306. package/static/vendor/fontawesome-free/js/v4-shims.js +68 -0
  307. package/static/vendor/fontawesome-free/js/v4-shims.min.js +5 -0
  308. package/static/vendor/fontawesome-free/less/_animated.less +19 -0
  309. package/static/vendor/fontawesome-free/less/_bordered-pulled.less +16 -0
  310. package/static/vendor/fontawesome-free/less/_core.less +12 -0
  311. package/static/vendor/fontawesome-free/less/_fixed-width.less +6 -0
  312. package/static/vendor/fontawesome-free/less/_icons.less +1462 -0
  313. package/static/vendor/fontawesome-free/less/_larger.less +27 -0
  314. package/static/vendor/fontawesome-free/less/_list.less +18 -0
  315. package/static/vendor/fontawesome-free/less/_mixins.less +56 -0
  316. package/static/vendor/fontawesome-free/less/_rotated-flipped.less +24 -0
  317. package/static/vendor/fontawesome-free/less/_screen-reader.less +5 -0
  318. package/static/vendor/fontawesome-free/less/_shims.less +2066 -0
  319. package/static/vendor/fontawesome-free/less/_stacked.less +22 -0
  320. package/static/vendor/fontawesome-free/less/_variables.less +1474 -0
  321. package/static/vendor/fontawesome-free/less/brands.less +23 -0
  322. package/static/vendor/fontawesome-free/less/fontawesome.less +16 -0
  323. package/static/vendor/fontawesome-free/less/regular.less +23 -0
  324. package/static/vendor/fontawesome-free/less/solid.less +24 -0
  325. package/static/vendor/fontawesome-free/less/v4-shims.less +6 -0
  326. package/static/vendor/fontawesome-free/metadata/categories.yml +2572 -0
  327. package/static/vendor/fontawesome-free/metadata/icons.yml +21783 -0
  328. package/static/vendor/fontawesome-free/metadata/shims.yml +298 -0
  329. package/static/vendor/fontawesome-free/metadata/sponsors.yml +744 -0
  330. package/static/vendor/fontawesome-free/package.json +58 -0
  331. package/static/vendor/fontawesome-free/scss/_animated.scss +20 -0
  332. package/static/vendor/fontawesome-free/scss/_bordered-pulled.scss +20 -0
  333. package/static/vendor/fontawesome-free/scss/_core.scss +21 -0
  334. package/static/vendor/fontawesome-free/scss/_fixed-width.scss +6 -0
  335. package/static/vendor/fontawesome-free/scss/_icons.scss +1462 -0
  336. package/static/vendor/fontawesome-free/scss/_larger.scss +23 -0
  337. package/static/vendor/fontawesome-free/scss/_list.scss +18 -0
  338. package/static/vendor/fontawesome-free/scss/_mixins.scss +56 -0
  339. package/static/vendor/fontawesome-free/scss/_rotated-flipped.scss +24 -0
  340. package/static/vendor/fontawesome-free/scss/_screen-reader.scss +5 -0
  341. package/static/vendor/fontawesome-free/scss/_shims.scss +2066 -0
  342. package/static/vendor/fontawesome-free/scss/_stacked.scss +31 -0
  343. package/static/vendor/fontawesome-free/scss/_variables.scss +1479 -0
  344. package/static/vendor/fontawesome-free/scss/brands.scss +23 -0
  345. package/static/vendor/fontawesome-free/scss/fontawesome.scss +16 -0
  346. package/static/vendor/fontawesome-free/scss/regular.scss +23 -0
  347. package/static/vendor/fontawesome-free/scss/solid.scss +24 -0
  348. package/static/vendor/fontawesome-free/scss/v4-shims.scss +6 -0
  349. package/static/vendor/fontawesome-free/sprites/brands.svg +1381 -0
  350. package/static/vendor/fontawesome-free/sprites/regular.svg +463 -0
  351. package/static/vendor/fontawesome-free/sprites/solid.svg +3013 -0
  352. package/static/vendor/fontawesome-free/svgs/brands/500px.svg +1 -0
  353. package/static/vendor/fontawesome-free/svgs/brands/accessible-icon.svg +1 -0
  354. package/static/vendor/fontawesome-free/svgs/brands/accusoft.svg +1 -0
  355. package/static/vendor/fontawesome-free/svgs/brands/acquisitions-incorporated.svg +1 -0
  356. package/static/vendor/fontawesome-free/svgs/brands/adn.svg +1 -0
  357. package/static/vendor/fontawesome-free/svgs/brands/adversal.svg +1 -0
  358. package/static/vendor/fontawesome-free/svgs/brands/affiliatetheme.svg +1 -0
  359. package/static/vendor/fontawesome-free/svgs/brands/airbnb.svg +1 -0
  360. package/static/vendor/fontawesome-free/svgs/brands/algolia.svg +1 -0
  361. package/static/vendor/fontawesome-free/svgs/brands/alipay.svg +1 -0
  362. package/static/vendor/fontawesome-free/svgs/brands/amazon-pay.svg +1 -0
  363. package/static/vendor/fontawesome-free/svgs/brands/amazon.svg +1 -0
  364. package/static/vendor/fontawesome-free/svgs/brands/amilia.svg +1 -0
  365. package/static/vendor/fontawesome-free/svgs/brands/android.svg +1 -0
  366. package/static/vendor/fontawesome-free/svgs/brands/angellist.svg +1 -0
  367. package/static/vendor/fontawesome-free/svgs/brands/angrycreative.svg +1 -0
  368. package/static/vendor/fontawesome-free/svgs/brands/angular.svg +1 -0
  369. package/static/vendor/fontawesome-free/svgs/brands/app-store-ios.svg +1 -0
  370. package/static/vendor/fontawesome-free/svgs/brands/app-store.svg +1 -0
  371. package/static/vendor/fontawesome-free/svgs/brands/apper.svg +1 -0
  372. package/static/vendor/fontawesome-free/svgs/brands/apple-pay.svg +1 -0
  373. package/static/vendor/fontawesome-free/svgs/brands/apple.svg +1 -0
  374. package/static/vendor/fontawesome-free/svgs/brands/artstation.svg +1 -0
  375. package/static/vendor/fontawesome-free/svgs/brands/asymmetrik.svg +1 -0
  376. package/static/vendor/fontawesome-free/svgs/brands/atlassian.svg +1 -0
  377. package/static/vendor/fontawesome-free/svgs/brands/audible.svg +1 -0
  378. package/static/vendor/fontawesome-free/svgs/brands/autoprefixer.svg +1 -0
  379. package/static/vendor/fontawesome-free/svgs/brands/avianex.svg +1 -0
  380. package/static/vendor/fontawesome-free/svgs/brands/aviato.svg +1 -0
  381. package/static/vendor/fontawesome-free/svgs/brands/aws.svg +1 -0
  382. package/static/vendor/fontawesome-free/svgs/brands/bandcamp.svg +1 -0
  383. package/static/vendor/fontawesome-free/svgs/brands/battle-net.svg +1 -0
  384. package/static/vendor/fontawesome-free/svgs/brands/behance-square.svg +1 -0
  385. package/static/vendor/fontawesome-free/svgs/brands/behance.svg +1 -0
  386. package/static/vendor/fontawesome-free/svgs/brands/bimobject.svg +1 -0
  387. package/static/vendor/fontawesome-free/svgs/brands/bitbucket.svg +1 -0
  388. package/static/vendor/fontawesome-free/svgs/brands/bitcoin.svg +1 -0
  389. package/static/vendor/fontawesome-free/svgs/brands/bity.svg +1 -0
  390. package/static/vendor/fontawesome-free/svgs/brands/black-tie.svg +1 -0
  391. package/static/vendor/fontawesome-free/svgs/brands/blackberry.svg +1 -0
  392. package/static/vendor/fontawesome-free/svgs/brands/blogger-b.svg +1 -0
  393. package/static/vendor/fontawesome-free/svgs/brands/blogger.svg +1 -0
  394. package/static/vendor/fontawesome-free/svgs/brands/bluetooth-b.svg +1 -0
  395. package/static/vendor/fontawesome-free/svgs/brands/bluetooth.svg +1 -0
  396. package/static/vendor/fontawesome-free/svgs/brands/bootstrap.svg +1 -0
  397. package/static/vendor/fontawesome-free/svgs/brands/btc.svg +1 -0
  398. package/static/vendor/fontawesome-free/svgs/brands/buffer.svg +1 -0
  399. package/static/vendor/fontawesome-free/svgs/brands/buromobelexperte.svg +1 -0
  400. package/static/vendor/fontawesome-free/svgs/brands/buy-n-large.svg +1 -0
  401. package/static/vendor/fontawesome-free/svgs/brands/buysellads.svg +1 -0
  402. package/static/vendor/fontawesome-free/svgs/brands/canadian-maple-leaf.svg +1 -0
  403. package/static/vendor/fontawesome-free/svgs/brands/cc-amazon-pay.svg +1 -0
  404. package/static/vendor/fontawesome-free/svgs/brands/cc-amex.svg +1 -0
  405. package/static/vendor/fontawesome-free/svgs/brands/cc-apple-pay.svg +1 -0
  406. package/static/vendor/fontawesome-free/svgs/brands/cc-diners-club.svg +1 -0
  407. package/static/vendor/fontawesome-free/svgs/brands/cc-discover.svg +1 -0
  408. package/static/vendor/fontawesome-free/svgs/brands/cc-jcb.svg +1 -0
  409. package/static/vendor/fontawesome-free/svgs/brands/cc-mastercard.svg +1 -0
  410. package/static/vendor/fontawesome-free/svgs/brands/cc-paypal.svg +1 -0
  411. package/static/vendor/fontawesome-free/svgs/brands/cc-stripe.svg +1 -0
  412. package/static/vendor/fontawesome-free/svgs/brands/cc-visa.svg +1 -0
  413. package/static/vendor/fontawesome-free/svgs/brands/centercode.svg +1 -0
  414. package/static/vendor/fontawesome-free/svgs/brands/centos.svg +1 -0
  415. package/static/vendor/fontawesome-free/svgs/brands/chrome.svg +1 -0
  416. package/static/vendor/fontawesome-free/svgs/brands/chromecast.svg +1 -0
  417. package/static/vendor/fontawesome-free/svgs/brands/cloudflare.svg +1 -0
  418. package/static/vendor/fontawesome-free/svgs/brands/cloudscale.svg +1 -0
  419. package/static/vendor/fontawesome-free/svgs/brands/cloudsmith.svg +1 -0
  420. package/static/vendor/fontawesome-free/svgs/brands/cloudversify.svg +1 -0
  421. package/static/vendor/fontawesome-free/svgs/brands/codepen.svg +1 -0
  422. package/static/vendor/fontawesome-free/svgs/brands/codiepie.svg +1 -0
  423. package/static/vendor/fontawesome-free/svgs/brands/confluence.svg +1 -0
  424. package/static/vendor/fontawesome-free/svgs/brands/connectdevelop.svg +1 -0
  425. package/static/vendor/fontawesome-free/svgs/brands/contao.svg +1 -0
  426. package/static/vendor/fontawesome-free/svgs/brands/cotton-bureau.svg +1 -0
  427. package/static/vendor/fontawesome-free/svgs/brands/cpanel.svg +1 -0
  428. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-by.svg +1 -0
  429. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-nc-eu.svg +1 -0
  430. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-nc-jp.svg +1 -0
  431. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-nc.svg +1 -0
  432. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-nd.svg +1 -0
  433. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-pd-alt.svg +1 -0
  434. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-pd.svg +1 -0
  435. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-remix.svg +1 -0
  436. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-sa.svg +1 -0
  437. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-sampling-plus.svg +1 -0
  438. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-sampling.svg +1 -0
  439. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-share.svg +1 -0
  440. package/static/vendor/fontawesome-free/svgs/brands/creative-commons-zero.svg +1 -0
  441. package/static/vendor/fontawesome-free/svgs/brands/creative-commons.svg +1 -0
  442. package/static/vendor/fontawesome-free/svgs/brands/critical-role.svg +1 -0
  443. package/static/vendor/fontawesome-free/svgs/brands/css3-alt.svg +1 -0
  444. package/static/vendor/fontawesome-free/svgs/brands/css3.svg +1 -0
  445. package/static/vendor/fontawesome-free/svgs/brands/cuttlefish.svg +1 -0
  446. package/static/vendor/fontawesome-free/svgs/brands/d-and-d-beyond.svg +1 -0
  447. package/static/vendor/fontawesome-free/svgs/brands/d-and-d.svg +1 -0
  448. package/static/vendor/fontawesome-free/svgs/brands/dailymotion.svg +1 -0
  449. package/static/vendor/fontawesome-free/svgs/brands/dashcube.svg +1 -0
  450. package/static/vendor/fontawesome-free/svgs/brands/deezer.svg +1 -0
  451. package/static/vendor/fontawesome-free/svgs/brands/delicious.svg +1 -0
  452. package/static/vendor/fontawesome-free/svgs/brands/deploydog.svg +1 -0
  453. package/static/vendor/fontawesome-free/svgs/brands/deskpro.svg +1 -0
  454. package/static/vendor/fontawesome-free/svgs/brands/dev.svg +1 -0
  455. package/static/vendor/fontawesome-free/svgs/brands/deviantart.svg +1 -0
  456. package/static/vendor/fontawesome-free/svgs/brands/dhl.svg +1 -0
  457. package/static/vendor/fontawesome-free/svgs/brands/diaspora.svg +1 -0
  458. package/static/vendor/fontawesome-free/svgs/brands/digg.svg +1 -0
  459. package/static/vendor/fontawesome-free/svgs/brands/digital-ocean.svg +1 -0
  460. package/static/vendor/fontawesome-free/svgs/brands/discord.svg +1 -0
  461. package/static/vendor/fontawesome-free/svgs/brands/discourse.svg +1 -0
  462. package/static/vendor/fontawesome-free/svgs/brands/dochub.svg +1 -0
  463. package/static/vendor/fontawesome-free/svgs/brands/docker.svg +1 -0
  464. package/static/vendor/fontawesome-free/svgs/brands/draft2digital.svg +1 -0
  465. package/static/vendor/fontawesome-free/svgs/brands/dribbble-square.svg +1 -0
  466. package/static/vendor/fontawesome-free/svgs/brands/dribbble.svg +1 -0
  467. package/static/vendor/fontawesome-free/svgs/brands/dropbox.svg +1 -0
  468. package/static/vendor/fontawesome-free/svgs/brands/drupal.svg +1 -0
  469. package/static/vendor/fontawesome-free/svgs/brands/dyalog.svg +1 -0
  470. package/static/vendor/fontawesome-free/svgs/brands/earlybirds.svg +1 -0
  471. package/static/vendor/fontawesome-free/svgs/brands/ebay.svg +1 -0
  472. package/static/vendor/fontawesome-free/svgs/brands/edge-legacy.svg +1 -0
  473. package/static/vendor/fontawesome-free/svgs/brands/edge.svg +1 -0
  474. package/static/vendor/fontawesome-free/svgs/brands/elementor.svg +1 -0
  475. package/static/vendor/fontawesome-free/svgs/brands/ello.svg +1 -0
  476. package/static/vendor/fontawesome-free/svgs/brands/ember.svg +1 -0
  477. package/static/vendor/fontawesome-free/svgs/brands/empire.svg +1 -0
  478. package/static/vendor/fontawesome-free/svgs/brands/envira.svg +1 -0
  479. package/static/vendor/fontawesome-free/svgs/brands/erlang.svg +1 -0
  480. package/static/vendor/fontawesome-free/svgs/brands/ethereum.svg +1 -0
  481. package/static/vendor/fontawesome-free/svgs/brands/etsy.svg +1 -0
  482. package/static/vendor/fontawesome-free/svgs/brands/evernote.svg +1 -0
  483. package/static/vendor/fontawesome-free/svgs/brands/expeditedssl.svg +1 -0
  484. package/static/vendor/fontawesome-free/svgs/brands/facebook-f.svg +1 -0
  485. package/static/vendor/fontawesome-free/svgs/brands/facebook-messenger.svg +1 -0
  486. package/static/vendor/fontawesome-free/svgs/brands/facebook-square.svg +1 -0
  487. package/static/vendor/fontawesome-free/svgs/brands/facebook.svg +1 -0
  488. package/static/vendor/fontawesome-free/svgs/brands/fantasy-flight-games.svg +1 -0
  489. package/static/vendor/fontawesome-free/svgs/brands/fedex.svg +1 -0
  490. package/static/vendor/fontawesome-free/svgs/brands/fedora.svg +1 -0
  491. package/static/vendor/fontawesome-free/svgs/brands/figma.svg +1 -0
  492. package/static/vendor/fontawesome-free/svgs/brands/firefox-browser.svg +1 -0
  493. package/static/vendor/fontawesome-free/svgs/brands/firefox.svg +1 -0
  494. package/static/vendor/fontawesome-free/svgs/brands/first-order-alt.svg +1 -0
  495. package/static/vendor/fontawesome-free/svgs/brands/first-order.svg +1 -0
  496. package/static/vendor/fontawesome-free/svgs/brands/firstdraft.svg +1 -0
  497. package/static/vendor/fontawesome-free/svgs/brands/flickr.svg +1 -0
  498. package/static/vendor/fontawesome-free/svgs/brands/flipboard.svg +1 -0
  499. package/static/vendor/fontawesome-free/svgs/brands/fly.svg +1 -0
  500. package/static/vendor/fontawesome-free/svgs/brands/font-awesome-alt.svg +1 -0
  501. package/static/vendor/fontawesome-free/svgs/brands/font-awesome-flag.svg +1 -0
  502. package/static/vendor/fontawesome-free/svgs/brands/font-awesome-logo-full.svg +1 -0
  503. package/static/vendor/fontawesome-free/svgs/brands/font-awesome.svg +1 -0
  504. package/static/vendor/fontawesome-free/svgs/brands/fonticons-fi.svg +1 -0
  505. package/static/vendor/fontawesome-free/svgs/brands/fonticons.svg +1 -0
  506. package/static/vendor/fontawesome-free/svgs/brands/fort-awesome-alt.svg +1 -0
  507. package/static/vendor/fontawesome-free/svgs/brands/fort-awesome.svg +1 -0
  508. package/static/vendor/fontawesome-free/svgs/brands/forumbee.svg +1 -0
  509. package/static/vendor/fontawesome-free/svgs/brands/foursquare.svg +1 -0
  510. package/static/vendor/fontawesome-free/svgs/brands/free-code-camp.svg +1 -0
  511. package/static/vendor/fontawesome-free/svgs/brands/freebsd.svg +1 -0
  512. package/static/vendor/fontawesome-free/svgs/brands/fulcrum.svg +1 -0
  513. package/static/vendor/fontawesome-free/svgs/brands/galactic-republic.svg +1 -0
  514. package/static/vendor/fontawesome-free/svgs/brands/galactic-senate.svg +1 -0
  515. package/static/vendor/fontawesome-free/svgs/brands/get-pocket.svg +1 -0
  516. package/static/vendor/fontawesome-free/svgs/brands/gg-circle.svg +1 -0
  517. package/static/vendor/fontawesome-free/svgs/brands/gg.svg +1 -0
  518. package/static/vendor/fontawesome-free/svgs/brands/git-alt.svg +1 -0
  519. package/static/vendor/fontawesome-free/svgs/brands/git-square.svg +1 -0
  520. package/static/vendor/fontawesome-free/svgs/brands/git.svg +1 -0
  521. package/static/vendor/fontawesome-free/svgs/brands/github-alt.svg +1 -0
  522. package/static/vendor/fontawesome-free/svgs/brands/github-square.svg +1 -0
  523. package/static/vendor/fontawesome-free/svgs/brands/github.svg +1 -0
  524. package/static/vendor/fontawesome-free/svgs/brands/gitkraken.svg +1 -0
  525. package/static/vendor/fontawesome-free/svgs/brands/gitlab.svg +1 -0
  526. package/static/vendor/fontawesome-free/svgs/brands/gitter.svg +1 -0
  527. package/static/vendor/fontawesome-free/svgs/brands/glide-g.svg +1 -0
  528. package/static/vendor/fontawesome-free/svgs/brands/glide.svg +1 -0
  529. package/static/vendor/fontawesome-free/svgs/brands/gofore.svg +1 -0
  530. package/static/vendor/fontawesome-free/svgs/brands/goodreads-g.svg +1 -0
  531. package/static/vendor/fontawesome-free/svgs/brands/goodreads.svg +1 -0
  532. package/static/vendor/fontawesome-free/svgs/brands/google-drive.svg +1 -0
  533. package/static/vendor/fontawesome-free/svgs/brands/google-pay.svg +1 -0
  534. package/static/vendor/fontawesome-free/svgs/brands/google-play.svg +1 -0
  535. package/static/vendor/fontawesome-free/svgs/brands/google-plus-g.svg +1 -0
  536. package/static/vendor/fontawesome-free/svgs/brands/google-plus-square.svg +1 -0
  537. package/static/vendor/fontawesome-free/svgs/brands/google-plus.svg +1 -0
  538. package/static/vendor/fontawesome-free/svgs/brands/google-wallet.svg +1 -0
  539. package/static/vendor/fontawesome-free/svgs/brands/google.svg +1 -0
  540. package/static/vendor/fontawesome-free/svgs/brands/gratipay.svg +1 -0
  541. package/static/vendor/fontawesome-free/svgs/brands/grav.svg +1 -0
  542. package/static/vendor/fontawesome-free/svgs/brands/gripfire.svg +1 -0
  543. package/static/vendor/fontawesome-free/svgs/brands/grunt.svg +1 -0
  544. package/static/vendor/fontawesome-free/svgs/brands/guilded.svg +1 -0
  545. package/static/vendor/fontawesome-free/svgs/brands/gulp.svg +1 -0
  546. package/static/vendor/fontawesome-free/svgs/brands/hacker-news-square.svg +1 -0
  547. package/static/vendor/fontawesome-free/svgs/brands/hacker-news.svg +1 -0
  548. package/static/vendor/fontawesome-free/svgs/brands/hackerrank.svg +1 -0
  549. package/static/vendor/fontawesome-free/svgs/brands/hips.svg +1 -0
  550. package/static/vendor/fontawesome-free/svgs/brands/hire-a-helper.svg +1 -0
  551. package/static/vendor/fontawesome-free/svgs/brands/hive.svg +1 -0
  552. package/static/vendor/fontawesome-free/svgs/brands/hooli.svg +1 -0
  553. package/static/vendor/fontawesome-free/svgs/brands/hornbill.svg +1 -0
  554. package/static/vendor/fontawesome-free/svgs/brands/hotjar.svg +1 -0
  555. package/static/vendor/fontawesome-free/svgs/brands/houzz.svg +1 -0
  556. package/static/vendor/fontawesome-free/svgs/brands/html5.svg +1 -0
  557. package/static/vendor/fontawesome-free/svgs/brands/hubspot.svg +1 -0
  558. package/static/vendor/fontawesome-free/svgs/brands/ideal.svg +1 -0
  559. package/static/vendor/fontawesome-free/svgs/brands/imdb.svg +1 -0
  560. package/static/vendor/fontawesome-free/svgs/brands/innosoft.svg +1 -0
  561. package/static/vendor/fontawesome-free/svgs/brands/instagram-square.svg +1 -0
  562. package/static/vendor/fontawesome-free/svgs/brands/instagram.svg +1 -0
  563. package/static/vendor/fontawesome-free/svgs/brands/instalod.svg +1 -0
  564. package/static/vendor/fontawesome-free/svgs/brands/intercom.svg +1 -0
  565. package/static/vendor/fontawesome-free/svgs/brands/internet-explorer.svg +1 -0
  566. package/static/vendor/fontawesome-free/svgs/brands/invision.svg +1 -0
  567. package/static/vendor/fontawesome-free/svgs/brands/ioxhost.svg +1 -0
  568. package/static/vendor/fontawesome-free/svgs/brands/itch-io.svg +1 -0
  569. package/static/vendor/fontawesome-free/svgs/brands/itunes-note.svg +1 -0
  570. package/static/vendor/fontawesome-free/svgs/brands/itunes.svg +1 -0
  571. package/static/vendor/fontawesome-free/svgs/brands/java.svg +1 -0
  572. package/static/vendor/fontawesome-free/svgs/brands/jedi-order.svg +1 -0
  573. package/static/vendor/fontawesome-free/svgs/brands/jenkins.svg +1 -0
  574. package/static/vendor/fontawesome-free/svgs/brands/jira.svg +1 -0
  575. package/static/vendor/fontawesome-free/svgs/brands/joget.svg +1 -0
  576. package/static/vendor/fontawesome-free/svgs/brands/joomla.svg +1 -0
  577. package/static/vendor/fontawesome-free/svgs/brands/js-square.svg +1 -0
  578. package/static/vendor/fontawesome-free/svgs/brands/js.svg +1 -0
  579. package/static/vendor/fontawesome-free/svgs/brands/jsfiddle.svg +1 -0
  580. package/static/vendor/fontawesome-free/svgs/brands/kaggle.svg +1 -0
  581. package/static/vendor/fontawesome-free/svgs/brands/keybase.svg +1 -0
  582. package/static/vendor/fontawesome-free/svgs/brands/keycdn.svg +1 -0
  583. package/static/vendor/fontawesome-free/svgs/brands/kickstarter-k.svg +1 -0
  584. package/static/vendor/fontawesome-free/svgs/brands/kickstarter.svg +1 -0
  585. package/static/vendor/fontawesome-free/svgs/brands/korvue.svg +1 -0
  586. package/static/vendor/fontawesome-free/svgs/brands/laravel.svg +1 -0
  587. package/static/vendor/fontawesome-free/svgs/brands/lastfm-square.svg +1 -0
  588. package/static/vendor/fontawesome-free/svgs/brands/lastfm.svg +1 -0
  589. package/static/vendor/fontawesome-free/svgs/brands/leanpub.svg +1 -0
  590. package/static/vendor/fontawesome-free/svgs/brands/less.svg +1 -0
  591. package/static/vendor/fontawesome-free/svgs/brands/line.svg +1 -0
  592. package/static/vendor/fontawesome-free/svgs/brands/linkedin-in.svg +1 -0
  593. package/static/vendor/fontawesome-free/svgs/brands/linkedin.svg +1 -0
  594. package/static/vendor/fontawesome-free/svgs/brands/linode.svg +1 -0
  595. package/static/vendor/fontawesome-free/svgs/brands/linux.svg +1 -0
  596. package/static/vendor/fontawesome-free/svgs/brands/lyft.svg +1 -0
  597. package/static/vendor/fontawesome-free/svgs/brands/magento.svg +1 -0
  598. package/static/vendor/fontawesome-free/svgs/brands/mailchimp.svg +1 -0
  599. package/static/vendor/fontawesome-free/svgs/brands/mandalorian.svg +1 -0
  600. package/static/vendor/fontawesome-free/svgs/brands/markdown.svg +1 -0
  601. package/static/vendor/fontawesome-free/svgs/brands/mastodon.svg +1 -0
  602. package/static/vendor/fontawesome-free/svgs/brands/maxcdn.svg +1 -0
  603. package/static/vendor/fontawesome-free/svgs/brands/mdb.svg +1 -0
  604. package/static/vendor/fontawesome-free/svgs/brands/medapps.svg +1 -0
  605. package/static/vendor/fontawesome-free/svgs/brands/medium-m.svg +1 -0
  606. package/static/vendor/fontawesome-free/svgs/brands/medium.svg +1 -0
  607. package/static/vendor/fontawesome-free/svgs/brands/medrt.svg +1 -0
  608. package/static/vendor/fontawesome-free/svgs/brands/meetup.svg +1 -0
  609. package/static/vendor/fontawesome-free/svgs/brands/megaport.svg +1 -0
  610. package/static/vendor/fontawesome-free/svgs/brands/mendeley.svg +1 -0
  611. package/static/vendor/fontawesome-free/svgs/brands/microblog.svg +1 -0
  612. package/static/vendor/fontawesome-free/svgs/brands/microsoft.svg +1 -0
  613. package/static/vendor/fontawesome-free/svgs/brands/mix.svg +1 -0
  614. package/static/vendor/fontawesome-free/svgs/brands/mixcloud.svg +1 -0
  615. package/static/vendor/fontawesome-free/svgs/brands/mixer.svg +1 -0
  616. package/static/vendor/fontawesome-free/svgs/brands/mizuni.svg +1 -0
  617. package/static/vendor/fontawesome-free/svgs/brands/modx.svg +1 -0
  618. package/static/vendor/fontawesome-free/svgs/brands/monero.svg +1 -0
  619. package/static/vendor/fontawesome-free/svgs/brands/napster.svg +1 -0
  620. package/static/vendor/fontawesome-free/svgs/brands/neos.svg +1 -0
  621. package/static/vendor/fontawesome-free/svgs/brands/nimblr.svg +1 -0
  622. package/static/vendor/fontawesome-free/svgs/brands/node-js.svg +1 -0
  623. package/static/vendor/fontawesome-free/svgs/brands/node.svg +1 -0
  624. package/static/vendor/fontawesome-free/svgs/brands/npm.svg +1 -0
  625. package/static/vendor/fontawesome-free/svgs/brands/ns8.svg +1 -0
  626. package/static/vendor/fontawesome-free/svgs/brands/nutritionix.svg +1 -0
  627. package/static/vendor/fontawesome-free/svgs/brands/octopus-deploy.svg +1 -0
  628. package/static/vendor/fontawesome-free/svgs/brands/odnoklassniki-square.svg +1 -0
  629. package/static/vendor/fontawesome-free/svgs/brands/odnoklassniki.svg +1 -0
  630. package/static/vendor/fontawesome-free/svgs/brands/old-republic.svg +1 -0
  631. package/static/vendor/fontawesome-free/svgs/brands/opencart.svg +1 -0
  632. package/static/vendor/fontawesome-free/svgs/brands/openid.svg +1 -0
  633. package/static/vendor/fontawesome-free/svgs/brands/opera.svg +1 -0
  634. package/static/vendor/fontawesome-free/svgs/brands/optin-monster.svg +1 -0
  635. package/static/vendor/fontawesome-free/svgs/brands/orcid.svg +1 -0
  636. package/static/vendor/fontawesome-free/svgs/brands/osi.svg +1 -0
  637. package/static/vendor/fontawesome-free/svgs/brands/page4.svg +1 -0
  638. package/static/vendor/fontawesome-free/svgs/brands/pagelines.svg +1 -0
  639. package/static/vendor/fontawesome-free/svgs/brands/palfed.svg +1 -0
  640. package/static/vendor/fontawesome-free/svgs/brands/patreon.svg +1 -0
  641. package/static/vendor/fontawesome-free/svgs/brands/paypal.svg +1 -0
  642. package/static/vendor/fontawesome-free/svgs/brands/penny-arcade.svg +1 -0
  643. package/static/vendor/fontawesome-free/svgs/brands/perbyte.svg +1 -0
  644. package/static/vendor/fontawesome-free/svgs/brands/periscope.svg +1 -0
  645. package/static/vendor/fontawesome-free/svgs/brands/phabricator.svg +1 -0
  646. package/static/vendor/fontawesome-free/svgs/brands/phoenix-framework.svg +1 -0
  647. package/static/vendor/fontawesome-free/svgs/brands/phoenix-squadron.svg +1 -0
  648. package/static/vendor/fontawesome-free/svgs/brands/php.svg +1 -0
  649. package/static/vendor/fontawesome-free/svgs/brands/pied-piper-alt.svg +1 -0
  650. package/static/vendor/fontawesome-free/svgs/brands/pied-piper-hat.svg +1 -0
  651. package/static/vendor/fontawesome-free/svgs/brands/pied-piper-pp.svg +1 -0
  652. package/static/vendor/fontawesome-free/svgs/brands/pied-piper-square.svg +1 -0
  653. package/static/vendor/fontawesome-free/svgs/brands/pied-piper.svg +1 -0
  654. package/static/vendor/fontawesome-free/svgs/brands/pinterest-p.svg +1 -0
  655. package/static/vendor/fontawesome-free/svgs/brands/pinterest-square.svg +1 -0
  656. package/static/vendor/fontawesome-free/svgs/brands/pinterest.svg +1 -0
  657. package/static/vendor/fontawesome-free/svgs/brands/playstation.svg +1 -0
  658. package/static/vendor/fontawesome-free/svgs/brands/product-hunt.svg +1 -0
  659. package/static/vendor/fontawesome-free/svgs/brands/pushed.svg +1 -0
  660. package/static/vendor/fontawesome-free/svgs/brands/python.svg +1 -0
  661. package/static/vendor/fontawesome-free/svgs/brands/qq.svg +1 -0
  662. package/static/vendor/fontawesome-free/svgs/brands/quinscape.svg +1 -0
  663. package/static/vendor/fontawesome-free/svgs/brands/quora.svg +1 -0
  664. package/static/vendor/fontawesome-free/svgs/brands/r-project.svg +1 -0
  665. package/static/vendor/fontawesome-free/svgs/brands/raspberry-pi.svg +1 -0
  666. package/static/vendor/fontawesome-free/svgs/brands/ravelry.svg +1 -0
  667. package/static/vendor/fontawesome-free/svgs/brands/react.svg +1 -0
  668. package/static/vendor/fontawesome-free/svgs/brands/reacteurope.svg +1 -0
  669. package/static/vendor/fontawesome-free/svgs/brands/readme.svg +1 -0
  670. package/static/vendor/fontawesome-free/svgs/brands/rebel.svg +1 -0
  671. package/static/vendor/fontawesome-free/svgs/brands/red-river.svg +1 -0
  672. package/static/vendor/fontawesome-free/svgs/brands/reddit-alien.svg +1 -0
  673. package/static/vendor/fontawesome-free/svgs/brands/reddit-square.svg +1 -0
  674. package/static/vendor/fontawesome-free/svgs/brands/reddit.svg +1 -0
  675. package/static/vendor/fontawesome-free/svgs/brands/redhat.svg +1 -0
  676. package/static/vendor/fontawesome-free/svgs/brands/renren.svg +1 -0
  677. package/static/vendor/fontawesome-free/svgs/brands/replyd.svg +1 -0
  678. package/static/vendor/fontawesome-free/svgs/brands/researchgate.svg +1 -0
  679. package/static/vendor/fontawesome-free/svgs/brands/resolving.svg +1 -0
  680. package/static/vendor/fontawesome-free/svgs/brands/rev.svg +1 -0
  681. package/static/vendor/fontawesome-free/svgs/brands/rocketchat.svg +1 -0
  682. package/static/vendor/fontawesome-free/svgs/brands/rockrms.svg +1 -0
  683. package/static/vendor/fontawesome-free/svgs/brands/rust.svg +1 -0
  684. package/static/vendor/fontawesome-free/svgs/brands/safari.svg +1 -0
  685. package/static/vendor/fontawesome-free/svgs/brands/salesforce.svg +1 -0
  686. package/static/vendor/fontawesome-free/svgs/brands/sass.svg +1 -0
  687. package/static/vendor/fontawesome-free/svgs/brands/schlix.svg +1 -0
  688. package/static/vendor/fontawesome-free/svgs/brands/scribd.svg +1 -0
  689. package/static/vendor/fontawesome-free/svgs/brands/searchengin.svg +1 -0
  690. package/static/vendor/fontawesome-free/svgs/brands/sellcast.svg +1 -0
  691. package/static/vendor/fontawesome-free/svgs/brands/sellsy.svg +1 -0
  692. package/static/vendor/fontawesome-free/svgs/brands/servicestack.svg +1 -0
  693. package/static/vendor/fontawesome-free/svgs/brands/shirtsinbulk.svg +1 -0
  694. package/static/vendor/fontawesome-free/svgs/brands/shopify.svg +1 -0
  695. package/static/vendor/fontawesome-free/svgs/brands/shopware.svg +1 -0
  696. package/static/vendor/fontawesome-free/svgs/brands/simplybuilt.svg +1 -0
  697. package/static/vendor/fontawesome-free/svgs/brands/sistrix.svg +1 -0
  698. package/static/vendor/fontawesome-free/svgs/brands/sith.svg +1 -0
  699. package/static/vendor/fontawesome-free/svgs/brands/sketch.svg +1 -0
  700. package/static/vendor/fontawesome-free/svgs/brands/skyatlas.svg +1 -0
  701. package/static/vendor/fontawesome-free/svgs/brands/skype.svg +1 -0
  702. package/static/vendor/fontawesome-free/svgs/brands/slack-hash.svg +1 -0
  703. package/static/vendor/fontawesome-free/svgs/brands/slack.svg +1 -0
  704. package/static/vendor/fontawesome-free/svgs/brands/slideshare.svg +1 -0
  705. package/static/vendor/fontawesome-free/svgs/brands/snapchat-ghost.svg +1 -0
  706. package/static/vendor/fontawesome-free/svgs/brands/snapchat-square.svg +1 -0
  707. package/static/vendor/fontawesome-free/svgs/brands/snapchat.svg +1 -0
  708. package/static/vendor/fontawesome-free/svgs/brands/soundcloud.svg +1 -0
  709. package/static/vendor/fontawesome-free/svgs/brands/sourcetree.svg +1 -0
  710. package/static/vendor/fontawesome-free/svgs/brands/speakap.svg +1 -0
  711. package/static/vendor/fontawesome-free/svgs/brands/speaker-deck.svg +1 -0
  712. package/static/vendor/fontawesome-free/svgs/brands/spotify.svg +1 -0
  713. package/static/vendor/fontawesome-free/svgs/brands/squarespace.svg +1 -0
  714. package/static/vendor/fontawesome-free/svgs/brands/stack-exchange.svg +1 -0
  715. package/static/vendor/fontawesome-free/svgs/brands/stack-overflow.svg +1 -0
  716. package/static/vendor/fontawesome-free/svgs/brands/stackpath.svg +1 -0
  717. package/static/vendor/fontawesome-free/svgs/brands/staylinked.svg +1 -0
  718. package/static/vendor/fontawesome-free/svgs/brands/steam-square.svg +1 -0
  719. package/static/vendor/fontawesome-free/svgs/brands/steam-symbol.svg +1 -0
  720. package/static/vendor/fontawesome-free/svgs/brands/steam.svg +1 -0
  721. package/static/vendor/fontawesome-free/svgs/brands/sticker-mule.svg +1 -0
  722. package/static/vendor/fontawesome-free/svgs/brands/strava.svg +1 -0
  723. package/static/vendor/fontawesome-free/svgs/brands/stripe-s.svg +1 -0
  724. package/static/vendor/fontawesome-free/svgs/brands/stripe.svg +1 -0
  725. package/static/vendor/fontawesome-free/svgs/brands/studiovinari.svg +1 -0
  726. package/static/vendor/fontawesome-free/svgs/brands/stumbleupon-circle.svg +1 -0
  727. package/static/vendor/fontawesome-free/svgs/brands/stumbleupon.svg +1 -0
  728. package/static/vendor/fontawesome-free/svgs/brands/superpowers.svg +1 -0
  729. package/static/vendor/fontawesome-free/svgs/brands/supple.svg +1 -0
  730. package/static/vendor/fontawesome-free/svgs/brands/suse.svg +1 -0
  731. package/static/vendor/fontawesome-free/svgs/brands/swift.svg +1 -0
  732. package/static/vendor/fontawesome-free/svgs/brands/symfony.svg +1 -0
  733. package/static/vendor/fontawesome-free/svgs/brands/teamspeak.svg +1 -0
  734. package/static/vendor/fontawesome-free/svgs/brands/telegram-plane.svg +1 -0
  735. package/static/vendor/fontawesome-free/svgs/brands/telegram.svg +1 -0
  736. package/static/vendor/fontawesome-free/svgs/brands/tencent-weibo.svg +1 -0
  737. package/static/vendor/fontawesome-free/svgs/brands/the-red-yeti.svg +1 -0
  738. package/static/vendor/fontawesome-free/svgs/brands/themeco.svg +1 -0
  739. package/static/vendor/fontawesome-free/svgs/brands/themeisle.svg +1 -0
  740. package/static/vendor/fontawesome-free/svgs/brands/think-peaks.svg +1 -0
  741. package/static/vendor/fontawesome-free/svgs/brands/tiktok.svg +1 -0
  742. package/static/vendor/fontawesome-free/svgs/brands/trade-federation.svg +1 -0
  743. package/static/vendor/fontawesome-free/svgs/brands/trello.svg +1 -0
  744. package/static/vendor/fontawesome-free/svgs/brands/tripadvisor.svg +1 -0
  745. package/static/vendor/fontawesome-free/svgs/brands/tumblr-square.svg +1 -0
  746. package/static/vendor/fontawesome-free/svgs/brands/tumblr.svg +1 -0
  747. package/static/vendor/fontawesome-free/svgs/brands/twitch.svg +1 -0
  748. package/static/vendor/fontawesome-free/svgs/brands/twitter-square.svg +1 -0
  749. package/static/vendor/fontawesome-free/svgs/brands/twitter.svg +1 -0
  750. package/static/vendor/fontawesome-free/svgs/brands/typo3.svg +1 -0
  751. package/static/vendor/fontawesome-free/svgs/brands/uber.svg +1 -0
  752. package/static/vendor/fontawesome-free/svgs/brands/ubuntu.svg +1 -0
  753. package/static/vendor/fontawesome-free/svgs/brands/uikit.svg +1 -0
  754. package/static/vendor/fontawesome-free/svgs/brands/umbraco.svg +1 -0
  755. package/static/vendor/fontawesome-free/svgs/brands/uncharted.svg +1 -0
  756. package/static/vendor/fontawesome-free/svgs/brands/uniregistry.svg +1 -0
  757. package/static/vendor/fontawesome-free/svgs/brands/unity.svg +1 -0
  758. package/static/vendor/fontawesome-free/svgs/brands/unsplash.svg +1 -0
  759. package/static/vendor/fontawesome-free/svgs/brands/untappd.svg +1 -0
  760. package/static/vendor/fontawesome-free/svgs/brands/ups.svg +1 -0
  761. package/static/vendor/fontawesome-free/svgs/brands/usb.svg +1 -0
  762. package/static/vendor/fontawesome-free/svgs/brands/usps.svg +1 -0
  763. package/static/vendor/fontawesome-free/svgs/brands/ussunnah.svg +1 -0
  764. package/static/vendor/fontawesome-free/svgs/brands/vaadin.svg +1 -0
  765. package/static/vendor/fontawesome-free/svgs/brands/viacoin.svg +1 -0
  766. package/static/vendor/fontawesome-free/svgs/brands/viadeo-square.svg +1 -0
  767. package/static/vendor/fontawesome-free/svgs/brands/viadeo.svg +1 -0
  768. package/static/vendor/fontawesome-free/svgs/brands/viber.svg +1 -0
  769. package/static/vendor/fontawesome-free/svgs/brands/vimeo-square.svg +1 -0
  770. package/static/vendor/fontawesome-free/svgs/brands/vimeo-v.svg +1 -0
  771. package/static/vendor/fontawesome-free/svgs/brands/vimeo.svg +1 -0
  772. package/static/vendor/fontawesome-free/svgs/brands/vine.svg +1 -0
  773. package/static/vendor/fontawesome-free/svgs/brands/vk.svg +1 -0
  774. package/static/vendor/fontawesome-free/svgs/brands/vnv.svg +1 -0
  775. package/static/vendor/fontawesome-free/svgs/brands/vuejs.svg +1 -0
  776. package/static/vendor/fontawesome-free/svgs/brands/watchman-monitoring.svg +1 -0
  777. package/static/vendor/fontawesome-free/svgs/brands/waze.svg +1 -0
  778. package/static/vendor/fontawesome-free/svgs/brands/weebly.svg +1 -0
  779. package/static/vendor/fontawesome-free/svgs/brands/weibo.svg +1 -0
  780. package/static/vendor/fontawesome-free/svgs/brands/weixin.svg +1 -0
  781. package/static/vendor/fontawesome-free/svgs/brands/whatsapp-square.svg +1 -0
  782. package/static/vendor/fontawesome-free/svgs/brands/whatsapp.svg +1 -0
  783. package/static/vendor/fontawesome-free/svgs/brands/whmcs.svg +1 -0
  784. package/static/vendor/fontawesome-free/svgs/brands/wikipedia-w.svg +1 -0
  785. package/static/vendor/fontawesome-free/svgs/brands/windows.svg +1 -0
  786. package/static/vendor/fontawesome-free/svgs/brands/wix.svg +1 -0
  787. package/static/vendor/fontawesome-free/svgs/brands/wizards-of-the-coast.svg +1 -0
  788. package/static/vendor/fontawesome-free/svgs/brands/wodu.svg +1 -0
  789. package/static/vendor/fontawesome-free/svgs/brands/wolf-pack-battalion.svg +1 -0
  790. package/static/vendor/fontawesome-free/svgs/brands/wordpress-simple.svg +1 -0
  791. package/static/vendor/fontawesome-free/svgs/brands/wordpress.svg +1 -0
  792. package/static/vendor/fontawesome-free/svgs/brands/wpbeginner.svg +1 -0
  793. package/static/vendor/fontawesome-free/svgs/brands/wpexplorer.svg +1 -0
  794. package/static/vendor/fontawesome-free/svgs/brands/wpforms.svg +1 -0
  795. package/static/vendor/fontawesome-free/svgs/brands/wpressr.svg +1 -0
  796. package/static/vendor/fontawesome-free/svgs/brands/xbox.svg +1 -0
  797. package/static/vendor/fontawesome-free/svgs/brands/xing-square.svg +1 -0
  798. package/static/vendor/fontawesome-free/svgs/brands/xing.svg +1 -0
  799. package/static/vendor/fontawesome-free/svgs/brands/y-combinator.svg +1 -0
  800. package/static/vendor/fontawesome-free/svgs/brands/yahoo.svg +1 -0
  801. package/static/vendor/fontawesome-free/svgs/brands/yammer.svg +1 -0
  802. package/static/vendor/fontawesome-free/svgs/brands/yandex-international.svg +1 -0
  803. package/static/vendor/fontawesome-free/svgs/brands/yandex.svg +1 -0
  804. package/static/vendor/fontawesome-free/svgs/brands/yarn.svg +1 -0
  805. package/static/vendor/fontawesome-free/svgs/brands/yelp.svg +1 -0
  806. package/static/vendor/fontawesome-free/svgs/brands/yoast.svg +1 -0
  807. package/static/vendor/fontawesome-free/svgs/brands/youtube-square.svg +1 -0
  808. package/static/vendor/fontawesome-free/svgs/brands/youtube.svg +1 -0
  809. package/static/vendor/fontawesome-free/svgs/brands/zhihu.svg +1 -0
  810. package/static/vendor/fontawesome-free/svgs/regular/address-book.svg +1 -0
  811. package/static/vendor/fontawesome-free/svgs/regular/address-card.svg +1 -0
  812. package/static/vendor/fontawesome-free/svgs/regular/angry.svg +1 -0
  813. package/static/vendor/fontawesome-free/svgs/regular/arrow-alt-circle-down.svg +1 -0
  814. package/static/vendor/fontawesome-free/svgs/regular/arrow-alt-circle-left.svg +1 -0
  815. package/static/vendor/fontawesome-free/svgs/regular/arrow-alt-circle-right.svg +1 -0
  816. package/static/vendor/fontawesome-free/svgs/regular/arrow-alt-circle-up.svg +1 -0
  817. package/static/vendor/fontawesome-free/svgs/regular/bell-slash.svg +1 -0
  818. package/static/vendor/fontawesome-free/svgs/regular/bell.svg +1 -0
  819. package/static/vendor/fontawesome-free/svgs/regular/bookmark.svg +1 -0
  820. package/static/vendor/fontawesome-free/svgs/regular/building.svg +1 -0
  821. package/static/vendor/fontawesome-free/svgs/regular/calendar-alt.svg +1 -0
  822. package/static/vendor/fontawesome-free/svgs/regular/calendar-check.svg +1 -0
  823. package/static/vendor/fontawesome-free/svgs/regular/calendar-minus.svg +1 -0
  824. package/static/vendor/fontawesome-free/svgs/regular/calendar-plus.svg +1 -0
  825. package/static/vendor/fontawesome-free/svgs/regular/calendar-times.svg +1 -0
  826. package/static/vendor/fontawesome-free/svgs/regular/calendar.svg +1 -0
  827. package/static/vendor/fontawesome-free/svgs/regular/caret-square-down.svg +1 -0
  828. package/static/vendor/fontawesome-free/svgs/regular/caret-square-left.svg +1 -0
  829. package/static/vendor/fontawesome-free/svgs/regular/caret-square-right.svg +1 -0
  830. package/static/vendor/fontawesome-free/svgs/regular/caret-square-up.svg +1 -0
  831. package/static/vendor/fontawesome-free/svgs/regular/chart-bar.svg +1 -0
  832. package/static/vendor/fontawesome-free/svgs/regular/check-circle.svg +1 -0
  833. package/static/vendor/fontawesome-free/svgs/regular/check-square.svg +1 -0
  834. package/static/vendor/fontawesome-free/svgs/regular/circle.svg +1 -0
  835. package/static/vendor/fontawesome-free/svgs/regular/clipboard.svg +1 -0
  836. package/static/vendor/fontawesome-free/svgs/regular/clock.svg +1 -0
  837. package/static/vendor/fontawesome-free/svgs/regular/clone.svg +1 -0
  838. package/static/vendor/fontawesome-free/svgs/regular/closed-captioning.svg +1 -0
  839. package/static/vendor/fontawesome-free/svgs/regular/comment-alt.svg +1 -0
  840. package/static/vendor/fontawesome-free/svgs/regular/comment-dots.svg +1 -0
  841. package/static/vendor/fontawesome-free/svgs/regular/comment.svg +1 -0
  842. package/static/vendor/fontawesome-free/svgs/regular/comments.svg +1 -0
  843. package/static/vendor/fontawesome-free/svgs/regular/compass.svg +1 -0
  844. package/static/vendor/fontawesome-free/svgs/regular/copy.svg +1 -0
  845. package/static/vendor/fontawesome-free/svgs/regular/copyright.svg +1 -0
  846. package/static/vendor/fontawesome-free/svgs/regular/credit-card.svg +1 -0
  847. package/static/vendor/fontawesome-free/svgs/regular/dizzy.svg +1 -0
  848. package/static/vendor/fontawesome-free/svgs/regular/dot-circle.svg +1 -0
  849. package/static/vendor/fontawesome-free/svgs/regular/edit.svg +1 -0
  850. package/static/vendor/fontawesome-free/svgs/regular/envelope-open.svg +1 -0
  851. package/static/vendor/fontawesome-free/svgs/regular/envelope.svg +1 -0
  852. package/static/vendor/fontawesome-free/svgs/regular/eye-slash.svg +1 -0
  853. package/static/vendor/fontawesome-free/svgs/regular/eye.svg +1 -0
  854. package/static/vendor/fontawesome-free/svgs/regular/file-alt.svg +1 -0
  855. package/static/vendor/fontawesome-free/svgs/regular/file-archive.svg +1 -0
  856. package/static/vendor/fontawesome-free/svgs/regular/file-audio.svg +1 -0
  857. package/static/vendor/fontawesome-free/svgs/regular/file-code.svg +1 -0
  858. package/static/vendor/fontawesome-free/svgs/regular/file-excel.svg +1 -0
  859. package/static/vendor/fontawesome-free/svgs/regular/file-image.svg +1 -0
  860. package/static/vendor/fontawesome-free/svgs/regular/file-pdf.svg +1 -0
  861. package/static/vendor/fontawesome-free/svgs/regular/file-powerpoint.svg +1 -0
  862. package/static/vendor/fontawesome-free/svgs/regular/file-video.svg +1 -0
  863. package/static/vendor/fontawesome-free/svgs/regular/file-word.svg +1 -0
  864. package/static/vendor/fontawesome-free/svgs/regular/file.svg +1 -0
  865. package/static/vendor/fontawesome-free/svgs/regular/flag.svg +1 -0
  866. package/static/vendor/fontawesome-free/svgs/regular/flushed.svg +1 -0
  867. package/static/vendor/fontawesome-free/svgs/regular/folder-open.svg +1 -0
  868. package/static/vendor/fontawesome-free/svgs/regular/folder.svg +1 -0
  869. package/static/vendor/fontawesome-free/svgs/regular/font-awesome-logo-full.svg +1 -0
  870. package/static/vendor/fontawesome-free/svgs/regular/frown-open.svg +1 -0
  871. package/static/vendor/fontawesome-free/svgs/regular/frown.svg +1 -0
  872. package/static/vendor/fontawesome-free/svgs/regular/futbol.svg +1 -0
  873. package/static/vendor/fontawesome-free/svgs/regular/gem.svg +1 -0
  874. package/static/vendor/fontawesome-free/svgs/regular/grimace.svg +1 -0
  875. package/static/vendor/fontawesome-free/svgs/regular/grin-alt.svg +1 -0
  876. package/static/vendor/fontawesome-free/svgs/regular/grin-beam-sweat.svg +1 -0
  877. package/static/vendor/fontawesome-free/svgs/regular/grin-beam.svg +1 -0
  878. package/static/vendor/fontawesome-free/svgs/regular/grin-hearts.svg +1 -0
  879. package/static/vendor/fontawesome-free/svgs/regular/grin-squint-tears.svg +1 -0
  880. package/static/vendor/fontawesome-free/svgs/regular/grin-squint.svg +1 -0
  881. package/static/vendor/fontawesome-free/svgs/regular/grin-stars.svg +1 -0
  882. package/static/vendor/fontawesome-free/svgs/regular/grin-tears.svg +1 -0
  883. package/static/vendor/fontawesome-free/svgs/regular/grin-tongue-squint.svg +1 -0
  884. package/static/vendor/fontawesome-free/svgs/regular/grin-tongue-wink.svg +1 -0
  885. package/static/vendor/fontawesome-free/svgs/regular/grin-tongue.svg +1 -0
  886. package/static/vendor/fontawesome-free/svgs/regular/grin-wink.svg +1 -0
  887. package/static/vendor/fontawesome-free/svgs/regular/grin.svg +1 -0
  888. package/static/vendor/fontawesome-free/svgs/regular/hand-lizard.svg +1 -0
  889. package/static/vendor/fontawesome-free/svgs/regular/hand-paper.svg +1 -0
  890. package/static/vendor/fontawesome-free/svgs/regular/hand-peace.svg +1 -0
  891. package/static/vendor/fontawesome-free/svgs/regular/hand-point-down.svg +1 -0
  892. package/static/vendor/fontawesome-free/svgs/regular/hand-point-left.svg +1 -0
  893. package/static/vendor/fontawesome-free/svgs/regular/hand-point-right.svg +1 -0
  894. package/static/vendor/fontawesome-free/svgs/regular/hand-point-up.svg +1 -0
  895. package/static/vendor/fontawesome-free/svgs/regular/hand-pointer.svg +1 -0
  896. package/static/vendor/fontawesome-free/svgs/regular/hand-rock.svg +1 -0
  897. package/static/vendor/fontawesome-free/svgs/regular/hand-scissors.svg +1 -0
  898. package/static/vendor/fontawesome-free/svgs/regular/hand-spock.svg +1 -0
  899. package/static/vendor/fontawesome-free/svgs/regular/handshake.svg +1 -0
  900. package/static/vendor/fontawesome-free/svgs/regular/hdd.svg +1 -0
  901. package/static/vendor/fontawesome-free/svgs/regular/heart.svg +1 -0
  902. package/static/vendor/fontawesome-free/svgs/regular/hospital.svg +1 -0
  903. package/static/vendor/fontawesome-free/svgs/regular/hourglass.svg +1 -0
  904. package/static/vendor/fontawesome-free/svgs/regular/id-badge.svg +1 -0
  905. package/static/vendor/fontawesome-free/svgs/regular/id-card.svg +1 -0
  906. package/static/vendor/fontawesome-free/svgs/regular/image.svg +1 -0
  907. package/static/vendor/fontawesome-free/svgs/regular/images.svg +1 -0
  908. package/static/vendor/fontawesome-free/svgs/regular/keyboard.svg +1 -0
  909. package/static/vendor/fontawesome-free/svgs/regular/kiss-beam.svg +1 -0
  910. package/static/vendor/fontawesome-free/svgs/regular/kiss-wink-heart.svg +1 -0
  911. package/static/vendor/fontawesome-free/svgs/regular/kiss.svg +1 -0
  912. package/static/vendor/fontawesome-free/svgs/regular/laugh-beam.svg +1 -0
  913. package/static/vendor/fontawesome-free/svgs/regular/laugh-squint.svg +1 -0
  914. package/static/vendor/fontawesome-free/svgs/regular/laugh-wink.svg +1 -0
  915. package/static/vendor/fontawesome-free/svgs/regular/laugh.svg +1 -0
  916. package/static/vendor/fontawesome-free/svgs/regular/lemon.svg +1 -0
  917. package/static/vendor/fontawesome-free/svgs/regular/life-ring.svg +1 -0
  918. package/static/vendor/fontawesome-free/svgs/regular/lightbulb.svg +1 -0
  919. package/static/vendor/fontawesome-free/svgs/regular/list-alt.svg +1 -0
  920. package/static/vendor/fontawesome-free/svgs/regular/map.svg +1 -0
  921. package/static/vendor/fontawesome-free/svgs/regular/meh-blank.svg +1 -0
  922. package/static/vendor/fontawesome-free/svgs/regular/meh-rolling-eyes.svg +1 -0
  923. package/static/vendor/fontawesome-free/svgs/regular/meh.svg +1 -0
  924. package/static/vendor/fontawesome-free/svgs/regular/minus-square.svg +1 -0
  925. package/static/vendor/fontawesome-free/svgs/regular/money-bill-alt.svg +1 -0
  926. package/static/vendor/fontawesome-free/svgs/regular/moon.svg +1 -0
  927. package/static/vendor/fontawesome-free/svgs/regular/newspaper.svg +1 -0
  928. package/static/vendor/fontawesome-free/svgs/regular/object-group.svg +1 -0
  929. package/static/vendor/fontawesome-free/svgs/regular/object-ungroup.svg +1 -0
  930. package/static/vendor/fontawesome-free/svgs/regular/paper-plane.svg +1 -0
  931. package/static/vendor/fontawesome-free/svgs/regular/pause-circle.svg +1 -0
  932. package/static/vendor/fontawesome-free/svgs/regular/play-circle.svg +1 -0
  933. package/static/vendor/fontawesome-free/svgs/regular/plus-square.svg +1 -0
  934. package/static/vendor/fontawesome-free/svgs/regular/question-circle.svg +1 -0
  935. package/static/vendor/fontawesome-free/svgs/regular/registered.svg +1 -0
  936. package/static/vendor/fontawesome-free/svgs/regular/sad-cry.svg +1 -0
  937. package/static/vendor/fontawesome-free/svgs/regular/sad-tear.svg +1 -0
  938. package/static/vendor/fontawesome-free/svgs/regular/save.svg +1 -0
  939. package/static/vendor/fontawesome-free/svgs/regular/share-square.svg +1 -0
  940. package/static/vendor/fontawesome-free/svgs/regular/smile-beam.svg +1 -0
  941. package/static/vendor/fontawesome-free/svgs/regular/smile-wink.svg +1 -0
  942. package/static/vendor/fontawesome-free/svgs/regular/smile.svg +1 -0
  943. package/static/vendor/fontawesome-free/svgs/regular/snowflake.svg +1 -0
  944. package/static/vendor/fontawesome-free/svgs/regular/square.svg +1 -0
  945. package/static/vendor/fontawesome-free/svgs/regular/star-half.svg +1 -0
  946. package/static/vendor/fontawesome-free/svgs/regular/star.svg +1 -0
  947. package/static/vendor/fontawesome-free/svgs/regular/sticky-note.svg +1 -0
  948. package/static/vendor/fontawesome-free/svgs/regular/stop-circle.svg +1 -0
  949. package/static/vendor/fontawesome-free/svgs/regular/sun.svg +1 -0
  950. package/static/vendor/fontawesome-free/svgs/regular/surprise.svg +1 -0
  951. package/static/vendor/fontawesome-free/svgs/regular/thumbs-down.svg +1 -0
  952. package/static/vendor/fontawesome-free/svgs/regular/thumbs-up.svg +1 -0
  953. package/static/vendor/fontawesome-free/svgs/regular/times-circle.svg +1 -0
  954. package/static/vendor/fontawesome-free/svgs/regular/tired.svg +1 -0
  955. package/static/vendor/fontawesome-free/svgs/regular/trash-alt.svg +1 -0
  956. package/static/vendor/fontawesome-free/svgs/regular/user-circle.svg +1 -0
  957. package/static/vendor/fontawesome-free/svgs/regular/user.svg +1 -0
  958. package/static/vendor/fontawesome-free/svgs/regular/window-close.svg +1 -0
  959. package/static/vendor/fontawesome-free/svgs/regular/window-maximize.svg +1 -0
  960. package/static/vendor/fontawesome-free/svgs/regular/window-minimize.svg +1 -0
  961. package/static/vendor/fontawesome-free/svgs/regular/window-restore.svg +1 -0
  962. package/static/vendor/fontawesome-free/svgs/solid/ad.svg +1 -0
  963. package/static/vendor/fontawesome-free/svgs/solid/address-book.svg +1 -0
  964. package/static/vendor/fontawesome-free/svgs/solid/address-card.svg +1 -0
  965. package/static/vendor/fontawesome-free/svgs/solid/adjust.svg +1 -0
  966. package/static/vendor/fontawesome-free/svgs/solid/air-freshener.svg +1 -0
  967. package/static/vendor/fontawesome-free/svgs/solid/align-center.svg +1 -0
  968. package/static/vendor/fontawesome-free/svgs/solid/align-justify.svg +1 -0
  969. package/static/vendor/fontawesome-free/svgs/solid/align-left.svg +1 -0
  970. package/static/vendor/fontawesome-free/svgs/solid/align-right.svg +1 -0
  971. package/static/vendor/fontawesome-free/svgs/solid/allergies.svg +1 -0
  972. package/static/vendor/fontawesome-free/svgs/solid/ambulance.svg +1 -0
  973. package/static/vendor/fontawesome-free/svgs/solid/american-sign-language-interpreting.svg +1 -0
  974. package/static/vendor/fontawesome-free/svgs/solid/anchor.svg +1 -0
  975. package/static/vendor/fontawesome-free/svgs/solid/angle-double-down.svg +1 -0
  976. package/static/vendor/fontawesome-free/svgs/solid/angle-double-left.svg +1 -0
  977. package/static/vendor/fontawesome-free/svgs/solid/angle-double-right.svg +1 -0
  978. package/static/vendor/fontawesome-free/svgs/solid/angle-double-up.svg +1 -0
  979. package/static/vendor/fontawesome-free/svgs/solid/angle-down.svg +1 -0
  980. package/static/vendor/fontawesome-free/svgs/solid/angle-left.svg +1 -0
  981. package/static/vendor/fontawesome-free/svgs/solid/angle-right.svg +1 -0
  982. package/static/vendor/fontawesome-free/svgs/solid/angle-up.svg +1 -0
  983. package/static/vendor/fontawesome-free/svgs/solid/angry.svg +1 -0
  984. package/static/vendor/fontawesome-free/svgs/solid/ankh.svg +1 -0
  985. package/static/vendor/fontawesome-free/svgs/solid/apple-alt.svg +1 -0
  986. package/static/vendor/fontawesome-free/svgs/solid/archive.svg +1 -0
  987. package/static/vendor/fontawesome-free/svgs/solid/archway.svg +1 -0
  988. package/static/vendor/fontawesome-free/svgs/solid/arrow-alt-circle-down.svg +1 -0
  989. package/static/vendor/fontawesome-free/svgs/solid/arrow-alt-circle-left.svg +1 -0
  990. package/static/vendor/fontawesome-free/svgs/solid/arrow-alt-circle-right.svg +1 -0
  991. package/static/vendor/fontawesome-free/svgs/solid/arrow-alt-circle-up.svg +1 -0
  992. package/static/vendor/fontawesome-free/svgs/solid/arrow-circle-down.svg +1 -0
  993. package/static/vendor/fontawesome-free/svgs/solid/arrow-circle-left.svg +1 -0
  994. package/static/vendor/fontawesome-free/svgs/solid/arrow-circle-right.svg +1 -0
  995. package/static/vendor/fontawesome-free/svgs/solid/arrow-circle-up.svg +1 -0
  996. package/static/vendor/fontawesome-free/svgs/solid/arrow-down.svg +1 -0
  997. package/static/vendor/fontawesome-free/svgs/solid/arrow-left.svg +1 -0
  998. package/static/vendor/fontawesome-free/svgs/solid/arrow-right.svg +1 -0
  999. package/static/vendor/fontawesome-free/svgs/solid/arrow-up.svg +1 -0
  1000. package/static/vendor/fontawesome-free/svgs/solid/arrows-alt-h.svg +1 -0
  1001. package/static/vendor/fontawesome-free/svgs/solid/arrows-alt-v.svg +1 -0
  1002. package/static/vendor/fontawesome-free/svgs/solid/arrows-alt.svg +1 -0
  1003. package/static/vendor/fontawesome-free/svgs/solid/assistive-listening-systems.svg +1 -0
  1004. package/static/vendor/fontawesome-free/svgs/solid/asterisk.svg +1 -0
  1005. package/static/vendor/fontawesome-free/svgs/solid/at.svg +1 -0
  1006. package/static/vendor/fontawesome-free/svgs/solid/atlas.svg +1 -0
  1007. package/static/vendor/fontawesome-free/svgs/solid/atom.svg +1 -0
  1008. package/static/vendor/fontawesome-free/svgs/solid/audio-description.svg +1 -0
  1009. package/static/vendor/fontawesome-free/svgs/solid/award.svg +1 -0
  1010. package/static/vendor/fontawesome-free/svgs/solid/baby-carriage.svg +1 -0
  1011. package/static/vendor/fontawesome-free/svgs/solid/baby.svg +1 -0
  1012. package/static/vendor/fontawesome-free/svgs/solid/backspace.svg +1 -0
  1013. package/static/vendor/fontawesome-free/svgs/solid/backward.svg +1 -0
  1014. package/static/vendor/fontawesome-free/svgs/solid/bacon.svg +1 -0
  1015. package/static/vendor/fontawesome-free/svgs/solid/bacteria.svg +1 -0
  1016. package/static/vendor/fontawesome-free/svgs/solid/bacterium.svg +1 -0
  1017. package/static/vendor/fontawesome-free/svgs/solid/bahai.svg +1 -0
  1018. package/static/vendor/fontawesome-free/svgs/solid/balance-scale-left.svg +1 -0
  1019. package/static/vendor/fontawesome-free/svgs/solid/balance-scale-right.svg +1 -0
  1020. package/static/vendor/fontawesome-free/svgs/solid/balance-scale.svg +1 -0
  1021. package/static/vendor/fontawesome-free/svgs/solid/ban.svg +1 -0
  1022. package/static/vendor/fontawesome-free/svgs/solid/band-aid.svg +1 -0
  1023. package/static/vendor/fontawesome-free/svgs/solid/barcode.svg +1 -0
  1024. package/static/vendor/fontawesome-free/svgs/solid/bars.svg +1 -0
  1025. package/static/vendor/fontawesome-free/svgs/solid/baseball-ball.svg +1 -0
  1026. package/static/vendor/fontawesome-free/svgs/solid/basketball-ball.svg +1 -0
  1027. package/static/vendor/fontawesome-free/svgs/solid/bath.svg +1 -0
  1028. package/static/vendor/fontawesome-free/svgs/solid/battery-empty.svg +1 -0
  1029. package/static/vendor/fontawesome-free/svgs/solid/battery-full.svg +1 -0
  1030. package/static/vendor/fontawesome-free/svgs/solid/battery-half.svg +1 -0
  1031. package/static/vendor/fontawesome-free/svgs/solid/battery-quarter.svg +1 -0
  1032. package/static/vendor/fontawesome-free/svgs/solid/battery-three-quarters.svg +1 -0
  1033. package/static/vendor/fontawesome-free/svgs/solid/bed.svg +1 -0
  1034. package/static/vendor/fontawesome-free/svgs/solid/beer.svg +1 -0
  1035. package/static/vendor/fontawesome-free/svgs/solid/bell-slash.svg +1 -0
  1036. package/static/vendor/fontawesome-free/svgs/solid/bell.svg +1 -0
  1037. package/static/vendor/fontawesome-free/svgs/solid/bezier-curve.svg +1 -0
  1038. package/static/vendor/fontawesome-free/svgs/solid/bible.svg +1 -0
  1039. package/static/vendor/fontawesome-free/svgs/solid/bicycle.svg +1 -0
  1040. package/static/vendor/fontawesome-free/svgs/solid/biking.svg +1 -0
  1041. package/static/vendor/fontawesome-free/svgs/solid/binoculars.svg +1 -0
  1042. package/static/vendor/fontawesome-free/svgs/solid/biohazard.svg +1 -0
  1043. package/static/vendor/fontawesome-free/svgs/solid/birthday-cake.svg +1 -0
  1044. package/static/vendor/fontawesome-free/svgs/solid/blender-phone.svg +1 -0
  1045. package/static/vendor/fontawesome-free/svgs/solid/blender.svg +1 -0
  1046. package/static/vendor/fontawesome-free/svgs/solid/blind.svg +1 -0
  1047. package/static/vendor/fontawesome-free/svgs/solid/blog.svg +1 -0
  1048. package/static/vendor/fontawesome-free/svgs/solid/bold.svg +1 -0
  1049. package/static/vendor/fontawesome-free/svgs/solid/bolt.svg +1 -0
  1050. package/static/vendor/fontawesome-free/svgs/solid/bomb.svg +1 -0
  1051. package/static/vendor/fontawesome-free/svgs/solid/bone.svg +1 -0
  1052. package/static/vendor/fontawesome-free/svgs/solid/bong.svg +1 -0
  1053. package/static/vendor/fontawesome-free/svgs/solid/book-dead.svg +1 -0
  1054. package/static/vendor/fontawesome-free/svgs/solid/book-medical.svg +1 -0
  1055. package/static/vendor/fontawesome-free/svgs/solid/book-open.svg +1 -0
  1056. package/static/vendor/fontawesome-free/svgs/solid/book-reader.svg +1 -0
  1057. package/static/vendor/fontawesome-free/svgs/solid/book.svg +1 -0
  1058. package/static/vendor/fontawesome-free/svgs/solid/bookmark.svg +1 -0
  1059. package/static/vendor/fontawesome-free/svgs/solid/border-all.svg +1 -0
  1060. package/static/vendor/fontawesome-free/svgs/solid/border-none.svg +1 -0
  1061. package/static/vendor/fontawesome-free/svgs/solid/border-style.svg +1 -0
  1062. package/static/vendor/fontawesome-free/svgs/solid/bowling-ball.svg +1 -0
  1063. package/static/vendor/fontawesome-free/svgs/solid/box-open.svg +1 -0
  1064. package/static/vendor/fontawesome-free/svgs/solid/box-tissue.svg +1 -0
  1065. package/static/vendor/fontawesome-free/svgs/solid/box.svg +1 -0
  1066. package/static/vendor/fontawesome-free/svgs/solid/boxes.svg +1 -0
  1067. package/static/vendor/fontawesome-free/svgs/solid/braille.svg +1 -0
  1068. package/static/vendor/fontawesome-free/svgs/solid/brain.svg +1 -0
  1069. package/static/vendor/fontawesome-free/svgs/solid/bread-slice.svg +1 -0
  1070. package/static/vendor/fontawesome-free/svgs/solid/briefcase-medical.svg +1 -0
  1071. package/static/vendor/fontawesome-free/svgs/solid/briefcase.svg +1 -0
  1072. package/static/vendor/fontawesome-free/svgs/solid/broadcast-tower.svg +1 -0
  1073. package/static/vendor/fontawesome-free/svgs/solid/broom.svg +1 -0
  1074. package/static/vendor/fontawesome-free/svgs/solid/brush.svg +1 -0
  1075. package/static/vendor/fontawesome-free/svgs/solid/bug.svg +1 -0
  1076. package/static/vendor/fontawesome-free/svgs/solid/building.svg +1 -0
  1077. package/static/vendor/fontawesome-free/svgs/solid/bullhorn.svg +1 -0
  1078. package/static/vendor/fontawesome-free/svgs/solid/bullseye.svg +1 -0
  1079. package/static/vendor/fontawesome-free/svgs/solid/burn.svg +1 -0
  1080. package/static/vendor/fontawesome-free/svgs/solid/bus-alt.svg +1 -0
  1081. package/static/vendor/fontawesome-free/svgs/solid/bus.svg +1 -0
  1082. package/static/vendor/fontawesome-free/svgs/solid/business-time.svg +1 -0
  1083. package/static/vendor/fontawesome-free/svgs/solid/calculator.svg +1 -0
  1084. package/static/vendor/fontawesome-free/svgs/solid/calendar-alt.svg +1 -0
  1085. package/static/vendor/fontawesome-free/svgs/solid/calendar-check.svg +1 -0
  1086. package/static/vendor/fontawesome-free/svgs/solid/calendar-day.svg +1 -0
  1087. package/static/vendor/fontawesome-free/svgs/solid/calendar-minus.svg +1 -0
  1088. package/static/vendor/fontawesome-free/svgs/solid/calendar-plus.svg +1 -0
  1089. package/static/vendor/fontawesome-free/svgs/solid/calendar-times.svg +1 -0
  1090. package/static/vendor/fontawesome-free/svgs/solid/calendar-week.svg +1 -0
  1091. package/static/vendor/fontawesome-free/svgs/solid/calendar.svg +1 -0
  1092. package/static/vendor/fontawesome-free/svgs/solid/camera-retro.svg +1 -0
  1093. package/static/vendor/fontawesome-free/svgs/solid/camera.svg +1 -0
  1094. package/static/vendor/fontawesome-free/svgs/solid/campground.svg +1 -0
  1095. package/static/vendor/fontawesome-free/svgs/solid/candy-cane.svg +1 -0
  1096. package/static/vendor/fontawesome-free/svgs/solid/cannabis.svg +1 -0
  1097. package/static/vendor/fontawesome-free/svgs/solid/capsules.svg +1 -0
  1098. package/static/vendor/fontawesome-free/svgs/solid/car-alt.svg +1 -0
  1099. package/static/vendor/fontawesome-free/svgs/solid/car-battery.svg +1 -0
  1100. package/static/vendor/fontawesome-free/svgs/solid/car-crash.svg +1 -0
  1101. package/static/vendor/fontawesome-free/svgs/solid/car-side.svg +1 -0
  1102. package/static/vendor/fontawesome-free/svgs/solid/car.svg +1 -0
  1103. package/static/vendor/fontawesome-free/svgs/solid/caravan.svg +1 -0
  1104. package/static/vendor/fontawesome-free/svgs/solid/caret-down.svg +1 -0
  1105. package/static/vendor/fontawesome-free/svgs/solid/caret-left.svg +1 -0
  1106. package/static/vendor/fontawesome-free/svgs/solid/caret-right.svg +1 -0
  1107. package/static/vendor/fontawesome-free/svgs/solid/caret-square-down.svg +1 -0
  1108. package/static/vendor/fontawesome-free/svgs/solid/caret-square-left.svg +1 -0
  1109. package/static/vendor/fontawesome-free/svgs/solid/caret-square-right.svg +1 -0
  1110. package/static/vendor/fontawesome-free/svgs/solid/caret-square-up.svg +1 -0
  1111. package/static/vendor/fontawesome-free/svgs/solid/caret-up.svg +1 -0
  1112. package/static/vendor/fontawesome-free/svgs/solid/carrot.svg +1 -0
  1113. package/static/vendor/fontawesome-free/svgs/solid/cart-arrow-down.svg +1 -0
  1114. package/static/vendor/fontawesome-free/svgs/solid/cart-plus.svg +1 -0
  1115. package/static/vendor/fontawesome-free/svgs/solid/cash-register.svg +1 -0
  1116. package/static/vendor/fontawesome-free/svgs/solid/cat.svg +1 -0
  1117. package/static/vendor/fontawesome-free/svgs/solid/certificate.svg +1 -0
  1118. package/static/vendor/fontawesome-free/svgs/solid/chair.svg +1 -0
  1119. package/static/vendor/fontawesome-free/svgs/solid/chalkboard-teacher.svg +1 -0
  1120. package/static/vendor/fontawesome-free/svgs/solid/chalkboard.svg +1 -0
  1121. package/static/vendor/fontawesome-free/svgs/solid/charging-station.svg +1 -0
  1122. package/static/vendor/fontawesome-free/svgs/solid/chart-area.svg +1 -0
  1123. package/static/vendor/fontawesome-free/svgs/solid/chart-bar.svg +1 -0
  1124. package/static/vendor/fontawesome-free/svgs/solid/chart-line.svg +1 -0
  1125. package/static/vendor/fontawesome-free/svgs/solid/chart-pie.svg +1 -0
  1126. package/static/vendor/fontawesome-free/svgs/solid/check-circle.svg +1 -0
  1127. package/static/vendor/fontawesome-free/svgs/solid/check-double.svg +1 -0
  1128. package/static/vendor/fontawesome-free/svgs/solid/check-square.svg +1 -0
  1129. package/static/vendor/fontawesome-free/svgs/solid/check.svg +1 -0
  1130. package/static/vendor/fontawesome-free/svgs/solid/cheese.svg +1 -0
  1131. package/static/vendor/fontawesome-free/svgs/solid/chess-bishop.svg +1 -0
  1132. package/static/vendor/fontawesome-free/svgs/solid/chess-board.svg +1 -0
  1133. package/static/vendor/fontawesome-free/svgs/solid/chess-king.svg +1 -0
  1134. package/static/vendor/fontawesome-free/svgs/solid/chess-knight.svg +1 -0
  1135. package/static/vendor/fontawesome-free/svgs/solid/chess-pawn.svg +1 -0
  1136. package/static/vendor/fontawesome-free/svgs/solid/chess-queen.svg +1 -0
  1137. package/static/vendor/fontawesome-free/svgs/solid/chess-rook.svg +1 -0
  1138. package/static/vendor/fontawesome-free/svgs/solid/chess.svg +1 -0
  1139. package/static/vendor/fontawesome-free/svgs/solid/chevron-circle-down.svg +1 -0
  1140. package/static/vendor/fontawesome-free/svgs/solid/chevron-circle-left.svg +1 -0
  1141. package/static/vendor/fontawesome-free/svgs/solid/chevron-circle-right.svg +1 -0
  1142. package/static/vendor/fontawesome-free/svgs/solid/chevron-circle-up.svg +1 -0
  1143. package/static/vendor/fontawesome-free/svgs/solid/chevron-down.svg +1 -0
  1144. package/static/vendor/fontawesome-free/svgs/solid/chevron-left.svg +1 -0
  1145. package/static/vendor/fontawesome-free/svgs/solid/chevron-right.svg +1 -0
  1146. package/static/vendor/fontawesome-free/svgs/solid/chevron-up.svg +1 -0
  1147. package/static/vendor/fontawesome-free/svgs/solid/child.svg +1 -0
  1148. package/static/vendor/fontawesome-free/svgs/solid/church.svg +1 -0
  1149. package/static/vendor/fontawesome-free/svgs/solid/circle-notch.svg +1 -0
  1150. package/static/vendor/fontawesome-free/svgs/solid/circle.svg +1 -0
  1151. package/static/vendor/fontawesome-free/svgs/solid/city.svg +1 -0
  1152. package/static/vendor/fontawesome-free/svgs/solid/clinic-medical.svg +1 -0
  1153. package/static/vendor/fontawesome-free/svgs/solid/clipboard-check.svg +1 -0
  1154. package/static/vendor/fontawesome-free/svgs/solid/clipboard-list.svg +1 -0
  1155. package/static/vendor/fontawesome-free/svgs/solid/clipboard.svg +1 -0
  1156. package/static/vendor/fontawesome-free/svgs/solid/clock.svg +1 -0
  1157. package/static/vendor/fontawesome-free/svgs/solid/clone.svg +1 -0
  1158. package/static/vendor/fontawesome-free/svgs/solid/closed-captioning.svg +1 -0
  1159. package/static/vendor/fontawesome-free/svgs/solid/cloud-download-alt.svg +1 -0
  1160. package/static/vendor/fontawesome-free/svgs/solid/cloud-meatball.svg +1 -0
  1161. package/static/vendor/fontawesome-free/svgs/solid/cloud-moon-rain.svg +1 -0
  1162. package/static/vendor/fontawesome-free/svgs/solid/cloud-moon.svg +1 -0
  1163. package/static/vendor/fontawesome-free/svgs/solid/cloud-rain.svg +1 -0
  1164. package/static/vendor/fontawesome-free/svgs/solid/cloud-showers-heavy.svg +1 -0
  1165. package/static/vendor/fontawesome-free/svgs/solid/cloud-sun-rain.svg +1 -0
  1166. package/static/vendor/fontawesome-free/svgs/solid/cloud-sun.svg +1 -0
  1167. package/static/vendor/fontawesome-free/svgs/solid/cloud-upload-alt.svg +1 -0
  1168. package/static/vendor/fontawesome-free/svgs/solid/cloud.svg +1 -0
  1169. package/static/vendor/fontawesome-free/svgs/solid/cocktail.svg +1 -0
  1170. package/static/vendor/fontawesome-free/svgs/solid/code-branch.svg +1 -0
  1171. package/static/vendor/fontawesome-free/svgs/solid/code.svg +1 -0
  1172. package/static/vendor/fontawesome-free/svgs/solid/coffee.svg +1 -0
  1173. package/static/vendor/fontawesome-free/svgs/solid/cog.svg +1 -0
  1174. package/static/vendor/fontawesome-free/svgs/solid/cogs.svg +1 -0
  1175. package/static/vendor/fontawesome-free/svgs/solid/coins.svg +1 -0
  1176. package/static/vendor/fontawesome-free/svgs/solid/columns.svg +1 -0
  1177. package/static/vendor/fontawesome-free/svgs/solid/comment-alt.svg +1 -0
  1178. package/static/vendor/fontawesome-free/svgs/solid/comment-dollar.svg +1 -0
  1179. package/static/vendor/fontawesome-free/svgs/solid/comment-dots.svg +1 -0
  1180. package/static/vendor/fontawesome-free/svgs/solid/comment-medical.svg +1 -0
  1181. package/static/vendor/fontawesome-free/svgs/solid/comment-slash.svg +1 -0
  1182. package/static/vendor/fontawesome-free/svgs/solid/comment.svg +1 -0
  1183. package/static/vendor/fontawesome-free/svgs/solid/comments-dollar.svg +1 -0
  1184. package/static/vendor/fontawesome-free/svgs/solid/comments.svg +1 -0
  1185. package/static/vendor/fontawesome-free/svgs/solid/compact-disc.svg +1 -0
  1186. package/static/vendor/fontawesome-free/svgs/solid/compass.svg +1 -0
  1187. package/static/vendor/fontawesome-free/svgs/solid/compress-alt.svg +1 -0
  1188. package/static/vendor/fontawesome-free/svgs/solid/compress-arrows-alt.svg +1 -0
  1189. package/static/vendor/fontawesome-free/svgs/solid/compress.svg +1 -0
  1190. package/static/vendor/fontawesome-free/svgs/solid/concierge-bell.svg +1 -0
  1191. package/static/vendor/fontawesome-free/svgs/solid/cookie-bite.svg +1 -0
  1192. package/static/vendor/fontawesome-free/svgs/solid/cookie.svg +1 -0
  1193. package/static/vendor/fontawesome-free/svgs/solid/copy.svg +1 -0
  1194. package/static/vendor/fontawesome-free/svgs/solid/copyright.svg +1 -0
  1195. package/static/vendor/fontawesome-free/svgs/solid/couch.svg +1 -0
  1196. package/static/vendor/fontawesome-free/svgs/solid/credit-card.svg +1 -0
  1197. package/static/vendor/fontawesome-free/svgs/solid/crop-alt.svg +1 -0
  1198. package/static/vendor/fontawesome-free/svgs/solid/crop.svg +1 -0
  1199. package/static/vendor/fontawesome-free/svgs/solid/cross.svg +1 -0
  1200. package/static/vendor/fontawesome-free/svgs/solid/crosshairs.svg +1 -0
  1201. package/static/vendor/fontawesome-free/svgs/solid/crow.svg +1 -0
  1202. package/static/vendor/fontawesome-free/svgs/solid/crown.svg +1 -0
  1203. package/static/vendor/fontawesome-free/svgs/solid/crutch.svg +1 -0
  1204. package/static/vendor/fontawesome-free/svgs/solid/cube.svg +1 -0
  1205. package/static/vendor/fontawesome-free/svgs/solid/cubes.svg +1 -0
  1206. package/static/vendor/fontawesome-free/svgs/solid/cut.svg +1 -0
  1207. package/static/vendor/fontawesome-free/svgs/solid/database.svg +1 -0
  1208. package/static/vendor/fontawesome-free/svgs/solid/deaf.svg +1 -0
  1209. package/static/vendor/fontawesome-free/svgs/solid/democrat.svg +1 -0
  1210. package/static/vendor/fontawesome-free/svgs/solid/desktop.svg +1 -0
  1211. package/static/vendor/fontawesome-free/svgs/solid/dharmachakra.svg +1 -0
  1212. package/static/vendor/fontawesome-free/svgs/solid/diagnoses.svg +1 -0
  1213. package/static/vendor/fontawesome-free/svgs/solid/dice-d20.svg +1 -0
  1214. package/static/vendor/fontawesome-free/svgs/solid/dice-d6.svg +1 -0
  1215. package/static/vendor/fontawesome-free/svgs/solid/dice-five.svg +1 -0
  1216. package/static/vendor/fontawesome-free/svgs/solid/dice-four.svg +1 -0
  1217. package/static/vendor/fontawesome-free/svgs/solid/dice-one.svg +1 -0
  1218. package/static/vendor/fontawesome-free/svgs/solid/dice-six.svg +1 -0
  1219. package/static/vendor/fontawesome-free/svgs/solid/dice-three.svg +1 -0
  1220. package/static/vendor/fontawesome-free/svgs/solid/dice-two.svg +1 -0
  1221. package/static/vendor/fontawesome-free/svgs/solid/dice.svg +1 -0
  1222. package/static/vendor/fontawesome-free/svgs/solid/digital-tachograph.svg +1 -0
  1223. package/static/vendor/fontawesome-free/svgs/solid/directions.svg +1 -0
  1224. package/static/vendor/fontawesome-free/svgs/solid/disease.svg +1 -0
  1225. package/static/vendor/fontawesome-free/svgs/solid/divide.svg +1 -0
  1226. package/static/vendor/fontawesome-free/svgs/solid/dizzy.svg +1 -0
  1227. package/static/vendor/fontawesome-free/svgs/solid/dna.svg +1 -0
  1228. package/static/vendor/fontawesome-free/svgs/solid/dog.svg +1 -0
  1229. package/static/vendor/fontawesome-free/svgs/solid/dollar-sign.svg +1 -0
  1230. package/static/vendor/fontawesome-free/svgs/solid/dolly-flatbed.svg +1 -0
  1231. package/static/vendor/fontawesome-free/svgs/solid/dolly.svg +1 -0
  1232. package/static/vendor/fontawesome-free/svgs/solid/donate.svg +1 -0
  1233. package/static/vendor/fontawesome-free/svgs/solid/door-closed.svg +1 -0
  1234. package/static/vendor/fontawesome-free/svgs/solid/door-open.svg +1 -0
  1235. package/static/vendor/fontawesome-free/svgs/solid/dot-circle.svg +1 -0
  1236. package/static/vendor/fontawesome-free/svgs/solid/dove.svg +1 -0
  1237. package/static/vendor/fontawesome-free/svgs/solid/download.svg +1 -0
  1238. package/static/vendor/fontawesome-free/svgs/solid/drafting-compass.svg +1 -0
  1239. package/static/vendor/fontawesome-free/svgs/solid/dragon.svg +1 -0
  1240. package/static/vendor/fontawesome-free/svgs/solid/draw-polygon.svg +1 -0
  1241. package/static/vendor/fontawesome-free/svgs/solid/drum-steelpan.svg +1 -0
  1242. package/static/vendor/fontawesome-free/svgs/solid/drum.svg +1 -0
  1243. package/static/vendor/fontawesome-free/svgs/solid/drumstick-bite.svg +1 -0
  1244. package/static/vendor/fontawesome-free/svgs/solid/dumbbell.svg +1 -0
  1245. package/static/vendor/fontawesome-free/svgs/solid/dumpster-fire.svg +1 -0
  1246. package/static/vendor/fontawesome-free/svgs/solid/dumpster.svg +1 -0
  1247. package/static/vendor/fontawesome-free/svgs/solid/dungeon.svg +1 -0
  1248. package/static/vendor/fontawesome-free/svgs/solid/edit.svg +1 -0
  1249. package/static/vendor/fontawesome-free/svgs/solid/egg.svg +1 -0
  1250. package/static/vendor/fontawesome-free/svgs/solid/eject.svg +1 -0
  1251. package/static/vendor/fontawesome-free/svgs/solid/ellipsis-h.svg +1 -0
  1252. package/static/vendor/fontawesome-free/svgs/solid/ellipsis-v.svg +1 -0
  1253. package/static/vendor/fontawesome-free/svgs/solid/envelope-open-text.svg +1 -0
  1254. package/static/vendor/fontawesome-free/svgs/solid/envelope-open.svg +1 -0
  1255. package/static/vendor/fontawesome-free/svgs/solid/envelope-square.svg +1 -0
  1256. package/static/vendor/fontawesome-free/svgs/solid/envelope.svg +1 -0
  1257. package/static/vendor/fontawesome-free/svgs/solid/equals.svg +1 -0
  1258. package/static/vendor/fontawesome-free/svgs/solid/eraser.svg +1 -0
  1259. package/static/vendor/fontawesome-free/svgs/solid/ethernet.svg +1 -0
  1260. package/static/vendor/fontawesome-free/svgs/solid/euro-sign.svg +1 -0
  1261. package/static/vendor/fontawesome-free/svgs/solid/exchange-alt.svg +1 -0
  1262. package/static/vendor/fontawesome-free/svgs/solid/exclamation-circle.svg +1 -0
  1263. package/static/vendor/fontawesome-free/svgs/solid/exclamation-triangle.svg +1 -0
  1264. package/static/vendor/fontawesome-free/svgs/solid/exclamation.svg +1 -0
  1265. package/static/vendor/fontawesome-free/svgs/solid/expand-alt.svg +1 -0
  1266. package/static/vendor/fontawesome-free/svgs/solid/expand-arrows-alt.svg +1 -0
  1267. package/static/vendor/fontawesome-free/svgs/solid/expand.svg +1 -0
  1268. package/static/vendor/fontawesome-free/svgs/solid/external-link-alt.svg +1 -0
  1269. package/static/vendor/fontawesome-free/svgs/solid/external-link-square-alt.svg +1 -0
  1270. package/static/vendor/fontawesome-free/svgs/solid/eye-dropper.svg +1 -0
  1271. package/static/vendor/fontawesome-free/svgs/solid/eye-slash.svg +1 -0
  1272. package/static/vendor/fontawesome-free/svgs/solid/eye.svg +1 -0
  1273. package/static/vendor/fontawesome-free/svgs/solid/fan.svg +1 -0
  1274. package/static/vendor/fontawesome-free/svgs/solid/fast-backward.svg +1 -0
  1275. package/static/vendor/fontawesome-free/svgs/solid/fast-forward.svg +1 -0
  1276. package/static/vendor/fontawesome-free/svgs/solid/faucet.svg +1 -0
  1277. package/static/vendor/fontawesome-free/svgs/solid/fax.svg +1 -0
  1278. package/static/vendor/fontawesome-free/svgs/solid/feather-alt.svg +1 -0
  1279. package/static/vendor/fontawesome-free/svgs/solid/feather.svg +1 -0
  1280. package/static/vendor/fontawesome-free/svgs/solid/female.svg +1 -0
  1281. package/static/vendor/fontawesome-free/svgs/solid/fighter-jet.svg +1 -0
  1282. package/static/vendor/fontawesome-free/svgs/solid/file-alt.svg +1 -0
  1283. package/static/vendor/fontawesome-free/svgs/solid/file-archive.svg +1 -0
  1284. package/static/vendor/fontawesome-free/svgs/solid/file-audio.svg +1 -0
  1285. package/static/vendor/fontawesome-free/svgs/solid/file-code.svg +1 -0
  1286. package/static/vendor/fontawesome-free/svgs/solid/file-contract.svg +1 -0
  1287. package/static/vendor/fontawesome-free/svgs/solid/file-csv.svg +1 -0
  1288. package/static/vendor/fontawesome-free/svgs/solid/file-download.svg +1 -0
  1289. package/static/vendor/fontawesome-free/svgs/solid/file-excel.svg +1 -0
  1290. package/static/vendor/fontawesome-free/svgs/solid/file-export.svg +1 -0
  1291. package/static/vendor/fontawesome-free/svgs/solid/file-image.svg +1 -0
  1292. package/static/vendor/fontawesome-free/svgs/solid/file-import.svg +1 -0
  1293. package/static/vendor/fontawesome-free/svgs/solid/file-invoice-dollar.svg +1 -0
  1294. package/static/vendor/fontawesome-free/svgs/solid/file-invoice.svg +1 -0
  1295. package/static/vendor/fontawesome-free/svgs/solid/file-medical-alt.svg +1 -0
  1296. package/static/vendor/fontawesome-free/svgs/solid/file-medical.svg +1 -0
  1297. package/static/vendor/fontawesome-free/svgs/solid/file-pdf.svg +1 -0
  1298. package/static/vendor/fontawesome-free/svgs/solid/file-powerpoint.svg +1 -0
  1299. package/static/vendor/fontawesome-free/svgs/solid/file-prescription.svg +1 -0
  1300. package/static/vendor/fontawesome-free/svgs/solid/file-signature.svg +1 -0
  1301. package/static/vendor/fontawesome-free/svgs/solid/file-upload.svg +1 -0
  1302. package/static/vendor/fontawesome-free/svgs/solid/file-video.svg +1 -0
  1303. package/static/vendor/fontawesome-free/svgs/solid/file-word.svg +1 -0
  1304. package/static/vendor/fontawesome-free/svgs/solid/file.svg +1 -0
  1305. package/static/vendor/fontawesome-free/svgs/solid/fill-drip.svg +1 -0
  1306. package/static/vendor/fontawesome-free/svgs/solid/fill.svg +1 -0
  1307. package/static/vendor/fontawesome-free/svgs/solid/film.svg +1 -0
  1308. package/static/vendor/fontawesome-free/svgs/solid/filter.svg +1 -0
  1309. package/static/vendor/fontawesome-free/svgs/solid/fingerprint.svg +1 -0
  1310. package/static/vendor/fontawesome-free/svgs/solid/fire-alt.svg +1 -0
  1311. package/static/vendor/fontawesome-free/svgs/solid/fire-extinguisher.svg +1 -0
  1312. package/static/vendor/fontawesome-free/svgs/solid/fire.svg +1 -0
  1313. package/static/vendor/fontawesome-free/svgs/solid/first-aid.svg +1 -0
  1314. package/static/vendor/fontawesome-free/svgs/solid/fish.svg +1 -0
  1315. package/static/vendor/fontawesome-free/svgs/solid/fist-raised.svg +1 -0
  1316. package/static/vendor/fontawesome-free/svgs/solid/flag-checkered.svg +1 -0
  1317. package/static/vendor/fontawesome-free/svgs/solid/flag-usa.svg +1 -0
  1318. package/static/vendor/fontawesome-free/svgs/solid/flag.svg +1 -0
  1319. package/static/vendor/fontawesome-free/svgs/solid/flask.svg +1 -0
  1320. package/static/vendor/fontawesome-free/svgs/solid/flushed.svg +1 -0
  1321. package/static/vendor/fontawesome-free/svgs/solid/folder-minus.svg +1 -0
  1322. package/static/vendor/fontawesome-free/svgs/solid/folder-open.svg +1 -0
  1323. package/static/vendor/fontawesome-free/svgs/solid/folder-plus.svg +1 -0
  1324. package/static/vendor/fontawesome-free/svgs/solid/folder.svg +1 -0
  1325. package/static/vendor/fontawesome-free/svgs/solid/font-awesome-logo-full.svg +1 -0
  1326. package/static/vendor/fontawesome-free/svgs/solid/font.svg +1 -0
  1327. package/static/vendor/fontawesome-free/svgs/solid/football-ball.svg +1 -0
  1328. package/static/vendor/fontawesome-free/svgs/solid/forward.svg +1 -0
  1329. package/static/vendor/fontawesome-free/svgs/solid/frog.svg +1 -0
  1330. package/static/vendor/fontawesome-free/svgs/solid/frown-open.svg +1 -0
  1331. package/static/vendor/fontawesome-free/svgs/solid/frown.svg +1 -0
  1332. package/static/vendor/fontawesome-free/svgs/solid/funnel-dollar.svg +1 -0
  1333. package/static/vendor/fontawesome-free/svgs/solid/futbol.svg +1 -0
  1334. package/static/vendor/fontawesome-free/svgs/solid/gamepad.svg +1 -0
  1335. package/static/vendor/fontawesome-free/svgs/solid/gas-pump.svg +1 -0
  1336. package/static/vendor/fontawesome-free/svgs/solid/gavel.svg +1 -0
  1337. package/static/vendor/fontawesome-free/svgs/solid/gem.svg +1 -0
  1338. package/static/vendor/fontawesome-free/svgs/solid/genderless.svg +1 -0
  1339. package/static/vendor/fontawesome-free/svgs/solid/ghost.svg +1 -0
  1340. package/static/vendor/fontawesome-free/svgs/solid/gift.svg +1 -0
  1341. package/static/vendor/fontawesome-free/svgs/solid/gifts.svg +1 -0
  1342. package/static/vendor/fontawesome-free/svgs/solid/glass-cheers.svg +1 -0
  1343. package/static/vendor/fontawesome-free/svgs/solid/glass-martini-alt.svg +1 -0
  1344. package/static/vendor/fontawesome-free/svgs/solid/glass-martini.svg +1 -0
  1345. package/static/vendor/fontawesome-free/svgs/solid/glass-whiskey.svg +1 -0
  1346. package/static/vendor/fontawesome-free/svgs/solid/glasses.svg +1 -0
  1347. package/static/vendor/fontawesome-free/svgs/solid/globe-africa.svg +1 -0
  1348. package/static/vendor/fontawesome-free/svgs/solid/globe-americas.svg +1 -0
  1349. package/static/vendor/fontawesome-free/svgs/solid/globe-asia.svg +1 -0
  1350. package/static/vendor/fontawesome-free/svgs/solid/globe-europe.svg +1 -0
  1351. package/static/vendor/fontawesome-free/svgs/solid/globe.svg +1 -0
  1352. package/static/vendor/fontawesome-free/svgs/solid/golf-ball.svg +1 -0
  1353. package/static/vendor/fontawesome-free/svgs/solid/gopuram.svg +1 -0
  1354. package/static/vendor/fontawesome-free/svgs/solid/graduation-cap.svg +1 -0
  1355. package/static/vendor/fontawesome-free/svgs/solid/greater-than-equal.svg +1 -0
  1356. package/static/vendor/fontawesome-free/svgs/solid/greater-than.svg +1 -0
  1357. package/static/vendor/fontawesome-free/svgs/solid/grimace.svg +1 -0
  1358. package/static/vendor/fontawesome-free/svgs/solid/grin-alt.svg +1 -0
  1359. package/static/vendor/fontawesome-free/svgs/solid/grin-beam-sweat.svg +1 -0
  1360. package/static/vendor/fontawesome-free/svgs/solid/grin-beam.svg +1 -0
  1361. package/static/vendor/fontawesome-free/svgs/solid/grin-hearts.svg +1 -0
  1362. package/static/vendor/fontawesome-free/svgs/solid/grin-squint-tears.svg +1 -0
  1363. package/static/vendor/fontawesome-free/svgs/solid/grin-squint.svg +1 -0
  1364. package/static/vendor/fontawesome-free/svgs/solid/grin-stars.svg +1 -0
  1365. package/static/vendor/fontawesome-free/svgs/solid/grin-tears.svg +1 -0
  1366. package/static/vendor/fontawesome-free/svgs/solid/grin-tongue-squint.svg +1 -0
  1367. package/static/vendor/fontawesome-free/svgs/solid/grin-tongue-wink.svg +1 -0
  1368. package/static/vendor/fontawesome-free/svgs/solid/grin-tongue.svg +1 -0
  1369. package/static/vendor/fontawesome-free/svgs/solid/grin-wink.svg +1 -0
  1370. package/static/vendor/fontawesome-free/svgs/solid/grin.svg +1 -0
  1371. package/static/vendor/fontawesome-free/svgs/solid/grip-horizontal.svg +1 -0
  1372. package/static/vendor/fontawesome-free/svgs/solid/grip-lines-vertical.svg +1 -0
  1373. package/static/vendor/fontawesome-free/svgs/solid/grip-lines.svg +1 -0
  1374. package/static/vendor/fontawesome-free/svgs/solid/grip-vertical.svg +1 -0
  1375. package/static/vendor/fontawesome-free/svgs/solid/guitar.svg +1 -0
  1376. package/static/vendor/fontawesome-free/svgs/solid/h-square.svg +1 -0
  1377. package/static/vendor/fontawesome-free/svgs/solid/hamburger.svg +1 -0
  1378. package/static/vendor/fontawesome-free/svgs/solid/hammer.svg +1 -0
  1379. package/static/vendor/fontawesome-free/svgs/solid/hamsa.svg +1 -0
  1380. package/static/vendor/fontawesome-free/svgs/solid/hand-holding-heart.svg +1 -0
  1381. package/static/vendor/fontawesome-free/svgs/solid/hand-holding-medical.svg +1 -0
  1382. package/static/vendor/fontawesome-free/svgs/solid/hand-holding-usd.svg +1 -0
  1383. package/static/vendor/fontawesome-free/svgs/solid/hand-holding-water.svg +1 -0
  1384. package/static/vendor/fontawesome-free/svgs/solid/hand-holding.svg +1 -0
  1385. package/static/vendor/fontawesome-free/svgs/solid/hand-lizard.svg +1 -0
  1386. package/static/vendor/fontawesome-free/svgs/solid/hand-middle-finger.svg +1 -0
  1387. package/static/vendor/fontawesome-free/svgs/solid/hand-paper.svg +1 -0
  1388. package/static/vendor/fontawesome-free/svgs/solid/hand-peace.svg +1 -0
  1389. package/static/vendor/fontawesome-free/svgs/solid/hand-point-down.svg +1 -0
  1390. package/static/vendor/fontawesome-free/svgs/solid/hand-point-left.svg +1 -0
  1391. package/static/vendor/fontawesome-free/svgs/solid/hand-point-right.svg +1 -0
  1392. package/static/vendor/fontawesome-free/svgs/solid/hand-point-up.svg +1 -0
  1393. package/static/vendor/fontawesome-free/svgs/solid/hand-pointer.svg +1 -0
  1394. package/static/vendor/fontawesome-free/svgs/solid/hand-rock.svg +1 -0
  1395. package/static/vendor/fontawesome-free/svgs/solid/hand-scissors.svg +1 -0
  1396. package/static/vendor/fontawesome-free/svgs/solid/hand-sparkles.svg +1 -0
  1397. package/static/vendor/fontawesome-free/svgs/solid/hand-spock.svg +1 -0
  1398. package/static/vendor/fontawesome-free/svgs/solid/hands-helping.svg +1 -0
  1399. package/static/vendor/fontawesome-free/svgs/solid/hands-wash.svg +1 -0
  1400. package/static/vendor/fontawesome-free/svgs/solid/hands.svg +1 -0
  1401. package/static/vendor/fontawesome-free/svgs/solid/handshake-alt-slash.svg +1 -0
  1402. package/static/vendor/fontawesome-free/svgs/solid/handshake-slash.svg +1 -0
  1403. package/static/vendor/fontawesome-free/svgs/solid/handshake.svg +1 -0
  1404. package/static/vendor/fontawesome-free/svgs/solid/hanukiah.svg +1 -0
  1405. package/static/vendor/fontawesome-free/svgs/solid/hard-hat.svg +1 -0
  1406. package/static/vendor/fontawesome-free/svgs/solid/hashtag.svg +1 -0
  1407. package/static/vendor/fontawesome-free/svgs/solid/hat-cowboy-side.svg +1 -0
  1408. package/static/vendor/fontawesome-free/svgs/solid/hat-cowboy.svg +1 -0
  1409. package/static/vendor/fontawesome-free/svgs/solid/hat-wizard.svg +1 -0
  1410. package/static/vendor/fontawesome-free/svgs/solid/hdd.svg +1 -0
  1411. package/static/vendor/fontawesome-free/svgs/solid/head-side-cough-slash.svg +1 -0
  1412. package/static/vendor/fontawesome-free/svgs/solid/head-side-cough.svg +1 -0
  1413. package/static/vendor/fontawesome-free/svgs/solid/head-side-mask.svg +1 -0
  1414. package/static/vendor/fontawesome-free/svgs/solid/head-side-virus.svg +1 -0
  1415. package/static/vendor/fontawesome-free/svgs/solid/heading.svg +1 -0
  1416. package/static/vendor/fontawesome-free/svgs/solid/headphones-alt.svg +1 -0
  1417. package/static/vendor/fontawesome-free/svgs/solid/headphones.svg +1 -0
  1418. package/static/vendor/fontawesome-free/svgs/solid/headset.svg +1 -0
  1419. package/static/vendor/fontawesome-free/svgs/solid/heart-broken.svg +1 -0
  1420. package/static/vendor/fontawesome-free/svgs/solid/heart.svg +1 -0
  1421. package/static/vendor/fontawesome-free/svgs/solid/heartbeat.svg +1 -0
  1422. package/static/vendor/fontawesome-free/svgs/solid/helicopter.svg +1 -0
  1423. package/static/vendor/fontawesome-free/svgs/solid/highlighter.svg +1 -0
  1424. package/static/vendor/fontawesome-free/svgs/solid/hiking.svg +1 -0
  1425. package/static/vendor/fontawesome-free/svgs/solid/hippo.svg +1 -0
  1426. package/static/vendor/fontawesome-free/svgs/solid/history.svg +1 -0
  1427. package/static/vendor/fontawesome-free/svgs/solid/hockey-puck.svg +1 -0
  1428. package/static/vendor/fontawesome-free/svgs/solid/holly-berry.svg +1 -0
  1429. package/static/vendor/fontawesome-free/svgs/solid/home.svg +1 -0
  1430. package/static/vendor/fontawesome-free/svgs/solid/horse-head.svg +1 -0
  1431. package/static/vendor/fontawesome-free/svgs/solid/horse.svg +1 -0
  1432. package/static/vendor/fontawesome-free/svgs/solid/hospital-alt.svg +1 -0
  1433. package/static/vendor/fontawesome-free/svgs/solid/hospital-symbol.svg +1 -0
  1434. package/static/vendor/fontawesome-free/svgs/solid/hospital-user.svg +1 -0
  1435. package/static/vendor/fontawesome-free/svgs/solid/hospital.svg +1 -0
  1436. package/static/vendor/fontawesome-free/svgs/solid/hot-tub.svg +1 -0
  1437. package/static/vendor/fontawesome-free/svgs/solid/hotdog.svg +1 -0
  1438. package/static/vendor/fontawesome-free/svgs/solid/hotel.svg +1 -0
  1439. package/static/vendor/fontawesome-free/svgs/solid/hourglass-end.svg +1 -0
  1440. package/static/vendor/fontawesome-free/svgs/solid/hourglass-half.svg +1 -0
  1441. package/static/vendor/fontawesome-free/svgs/solid/hourglass-start.svg +1 -0
  1442. package/static/vendor/fontawesome-free/svgs/solid/hourglass.svg +1 -0
  1443. package/static/vendor/fontawesome-free/svgs/solid/house-damage.svg +1 -0
  1444. package/static/vendor/fontawesome-free/svgs/solid/house-user.svg +1 -0
  1445. package/static/vendor/fontawesome-free/svgs/solid/hryvnia.svg +1 -0
  1446. package/static/vendor/fontawesome-free/svgs/solid/i-cursor.svg +1 -0
  1447. package/static/vendor/fontawesome-free/svgs/solid/ice-cream.svg +1 -0
  1448. package/static/vendor/fontawesome-free/svgs/solid/icicles.svg +1 -0
  1449. package/static/vendor/fontawesome-free/svgs/solid/icons.svg +1 -0
  1450. package/static/vendor/fontawesome-free/svgs/solid/id-badge.svg +1 -0
  1451. package/static/vendor/fontawesome-free/svgs/solid/id-card-alt.svg +1 -0
  1452. package/static/vendor/fontawesome-free/svgs/solid/id-card.svg +1 -0
  1453. package/static/vendor/fontawesome-free/svgs/solid/igloo.svg +1 -0
  1454. package/static/vendor/fontawesome-free/svgs/solid/image.svg +1 -0
  1455. package/static/vendor/fontawesome-free/svgs/solid/images.svg +1 -0
  1456. package/static/vendor/fontawesome-free/svgs/solid/inbox.svg +1 -0
  1457. package/static/vendor/fontawesome-free/svgs/solid/indent.svg +1 -0
  1458. package/static/vendor/fontawesome-free/svgs/solid/industry.svg +1 -0
  1459. package/static/vendor/fontawesome-free/svgs/solid/infinity.svg +1 -0
  1460. package/static/vendor/fontawesome-free/svgs/solid/info-circle.svg +1 -0
  1461. package/static/vendor/fontawesome-free/svgs/solid/info.svg +1 -0
  1462. package/static/vendor/fontawesome-free/svgs/solid/italic.svg +1 -0
  1463. package/static/vendor/fontawesome-free/svgs/solid/jedi.svg +1 -0
  1464. package/static/vendor/fontawesome-free/svgs/solid/joint.svg +1 -0
  1465. package/static/vendor/fontawesome-free/svgs/solid/journal-whills.svg +1 -0
  1466. package/static/vendor/fontawesome-free/svgs/solid/kaaba.svg +1 -0
  1467. package/static/vendor/fontawesome-free/svgs/solid/key.svg +1 -0
  1468. package/static/vendor/fontawesome-free/svgs/solid/keyboard.svg +1 -0
  1469. package/static/vendor/fontawesome-free/svgs/solid/khanda.svg +1 -0
  1470. package/static/vendor/fontawesome-free/svgs/solid/kiss-beam.svg +1 -0
  1471. package/static/vendor/fontawesome-free/svgs/solid/kiss-wink-heart.svg +1 -0
  1472. package/static/vendor/fontawesome-free/svgs/solid/kiss.svg +1 -0
  1473. package/static/vendor/fontawesome-free/svgs/solid/kiwi-bird.svg +1 -0
  1474. package/static/vendor/fontawesome-free/svgs/solid/landmark.svg +1 -0
  1475. package/static/vendor/fontawesome-free/svgs/solid/language.svg +1 -0
  1476. package/static/vendor/fontawesome-free/svgs/solid/laptop-code.svg +1 -0
  1477. package/static/vendor/fontawesome-free/svgs/solid/laptop-house.svg +1 -0
  1478. package/static/vendor/fontawesome-free/svgs/solid/laptop-medical.svg +1 -0
  1479. package/static/vendor/fontawesome-free/svgs/solid/laptop.svg +1 -0
  1480. package/static/vendor/fontawesome-free/svgs/solid/laugh-beam.svg +1 -0
  1481. package/static/vendor/fontawesome-free/svgs/solid/laugh-squint.svg +1 -0
  1482. package/static/vendor/fontawesome-free/svgs/solid/laugh-wink.svg +1 -0
  1483. package/static/vendor/fontawesome-free/svgs/solid/laugh.svg +1 -0
  1484. package/static/vendor/fontawesome-free/svgs/solid/layer-group.svg +1 -0
  1485. package/static/vendor/fontawesome-free/svgs/solid/leaf.svg +1 -0
  1486. package/static/vendor/fontawesome-free/svgs/solid/lemon.svg +1 -0
  1487. package/static/vendor/fontawesome-free/svgs/solid/less-than-equal.svg +1 -0
  1488. package/static/vendor/fontawesome-free/svgs/solid/less-than.svg +1 -0
  1489. package/static/vendor/fontawesome-free/svgs/solid/level-down-alt.svg +1 -0
  1490. package/static/vendor/fontawesome-free/svgs/solid/level-up-alt.svg +1 -0
  1491. package/static/vendor/fontawesome-free/svgs/solid/life-ring.svg +1 -0
  1492. package/static/vendor/fontawesome-free/svgs/solid/lightbulb.svg +1 -0
  1493. package/static/vendor/fontawesome-free/svgs/solid/link.svg +1 -0
  1494. package/static/vendor/fontawesome-free/svgs/solid/lira-sign.svg +1 -0
  1495. package/static/vendor/fontawesome-free/svgs/solid/list-alt.svg +1 -0
  1496. package/static/vendor/fontawesome-free/svgs/solid/list-ol.svg +1 -0
  1497. package/static/vendor/fontawesome-free/svgs/solid/list-ul.svg +1 -0
  1498. package/static/vendor/fontawesome-free/svgs/solid/list.svg +1 -0
  1499. package/static/vendor/fontawesome-free/svgs/solid/location-arrow.svg +1 -0
  1500. package/static/vendor/fontawesome-free/svgs/solid/lock-open.svg +1 -0
  1501. package/static/vendor/fontawesome-free/svgs/solid/lock.svg +1 -0
  1502. package/static/vendor/fontawesome-free/svgs/solid/long-arrow-alt-down.svg +1 -0
  1503. package/static/vendor/fontawesome-free/svgs/solid/long-arrow-alt-left.svg +1 -0
  1504. package/static/vendor/fontawesome-free/svgs/solid/long-arrow-alt-right.svg +1 -0
  1505. package/static/vendor/fontawesome-free/svgs/solid/long-arrow-alt-up.svg +1 -0
  1506. package/static/vendor/fontawesome-free/svgs/solid/low-vision.svg +1 -0
  1507. package/static/vendor/fontawesome-free/svgs/solid/luggage-cart.svg +1 -0
  1508. package/static/vendor/fontawesome-free/svgs/solid/lungs-virus.svg +1 -0
  1509. package/static/vendor/fontawesome-free/svgs/solid/lungs.svg +1 -0
  1510. package/static/vendor/fontawesome-free/svgs/solid/magic.svg +1 -0
  1511. package/static/vendor/fontawesome-free/svgs/solid/magnet.svg +1 -0
  1512. package/static/vendor/fontawesome-free/svgs/solid/mail-bulk.svg +1 -0
  1513. package/static/vendor/fontawesome-free/svgs/solid/male.svg +1 -0
  1514. package/static/vendor/fontawesome-free/svgs/solid/map-marked-alt.svg +1 -0
  1515. package/static/vendor/fontawesome-free/svgs/solid/map-marked.svg +1 -0
  1516. package/static/vendor/fontawesome-free/svgs/solid/map-marker-alt.svg +1 -0
  1517. package/static/vendor/fontawesome-free/svgs/solid/map-marker.svg +1 -0
  1518. package/static/vendor/fontawesome-free/svgs/solid/map-pin.svg +1 -0
  1519. package/static/vendor/fontawesome-free/svgs/solid/map-signs.svg +1 -0
  1520. package/static/vendor/fontawesome-free/svgs/solid/map.svg +1 -0
  1521. package/static/vendor/fontawesome-free/svgs/solid/marker.svg +1 -0
  1522. package/static/vendor/fontawesome-free/svgs/solid/mars-double.svg +1 -0
  1523. package/static/vendor/fontawesome-free/svgs/solid/mars-stroke-h.svg +1 -0
  1524. package/static/vendor/fontawesome-free/svgs/solid/mars-stroke-v.svg +1 -0
  1525. package/static/vendor/fontawesome-free/svgs/solid/mars-stroke.svg +1 -0
  1526. package/static/vendor/fontawesome-free/svgs/solid/mars.svg +1 -0
  1527. package/static/vendor/fontawesome-free/svgs/solid/mask.svg +1 -0
  1528. package/static/vendor/fontawesome-free/svgs/solid/medal.svg +1 -0
  1529. package/static/vendor/fontawesome-free/svgs/solid/medkit.svg +1 -0
  1530. package/static/vendor/fontawesome-free/svgs/solid/meh-blank.svg +1 -0
  1531. package/static/vendor/fontawesome-free/svgs/solid/meh-rolling-eyes.svg +1 -0
  1532. package/static/vendor/fontawesome-free/svgs/solid/meh.svg +1 -0
  1533. package/static/vendor/fontawesome-free/svgs/solid/memory.svg +1 -0
  1534. package/static/vendor/fontawesome-free/svgs/solid/menorah.svg +1 -0
  1535. package/static/vendor/fontawesome-free/svgs/solid/mercury.svg +1 -0
  1536. package/static/vendor/fontawesome-free/svgs/solid/meteor.svg +1 -0
  1537. package/static/vendor/fontawesome-free/svgs/solid/microchip.svg +1 -0
  1538. package/static/vendor/fontawesome-free/svgs/solid/microphone-alt-slash.svg +1 -0
  1539. package/static/vendor/fontawesome-free/svgs/solid/microphone-alt.svg +1 -0
  1540. package/static/vendor/fontawesome-free/svgs/solid/microphone-slash.svg +1 -0
  1541. package/static/vendor/fontawesome-free/svgs/solid/microphone.svg +1 -0
  1542. package/static/vendor/fontawesome-free/svgs/solid/microscope.svg +1 -0
  1543. package/static/vendor/fontawesome-free/svgs/solid/minus-circle.svg +1 -0
  1544. package/static/vendor/fontawesome-free/svgs/solid/minus-square.svg +1 -0
  1545. package/static/vendor/fontawesome-free/svgs/solid/minus.svg +1 -0
  1546. package/static/vendor/fontawesome-free/svgs/solid/mitten.svg +1 -0
  1547. package/static/vendor/fontawesome-free/svgs/solid/mobile-alt.svg +1 -0
  1548. package/static/vendor/fontawesome-free/svgs/solid/mobile.svg +1 -0
  1549. package/static/vendor/fontawesome-free/svgs/solid/money-bill-alt.svg +1 -0
  1550. package/static/vendor/fontawesome-free/svgs/solid/money-bill-wave-alt.svg +1 -0
  1551. package/static/vendor/fontawesome-free/svgs/solid/money-bill-wave.svg +1 -0
  1552. package/static/vendor/fontawesome-free/svgs/solid/money-bill.svg +1 -0
  1553. package/static/vendor/fontawesome-free/svgs/solid/money-check-alt.svg +1 -0
  1554. package/static/vendor/fontawesome-free/svgs/solid/money-check.svg +1 -0
  1555. package/static/vendor/fontawesome-free/svgs/solid/monument.svg +1 -0
  1556. package/static/vendor/fontawesome-free/svgs/solid/moon.svg +1 -0
  1557. package/static/vendor/fontawesome-free/svgs/solid/mortar-pestle.svg +1 -0
  1558. package/static/vendor/fontawesome-free/svgs/solid/mosque.svg +1 -0
  1559. package/static/vendor/fontawesome-free/svgs/solid/motorcycle.svg +1 -0
  1560. package/static/vendor/fontawesome-free/svgs/solid/mountain.svg +1 -0
  1561. package/static/vendor/fontawesome-free/svgs/solid/mouse-pointer.svg +1 -0
  1562. package/static/vendor/fontawesome-free/svgs/solid/mouse.svg +1 -0
  1563. package/static/vendor/fontawesome-free/svgs/solid/mug-hot.svg +1 -0
  1564. package/static/vendor/fontawesome-free/svgs/solid/music.svg +1 -0
  1565. package/static/vendor/fontawesome-free/svgs/solid/network-wired.svg +1 -0
  1566. package/static/vendor/fontawesome-free/svgs/solid/neuter.svg +1 -0
  1567. package/static/vendor/fontawesome-free/svgs/solid/newspaper.svg +1 -0
  1568. package/static/vendor/fontawesome-free/svgs/solid/not-equal.svg +1 -0
  1569. package/static/vendor/fontawesome-free/svgs/solid/notes-medical.svg +1 -0
  1570. package/static/vendor/fontawesome-free/svgs/solid/object-group.svg +1 -0
  1571. package/static/vendor/fontawesome-free/svgs/solid/object-ungroup.svg +1 -0
  1572. package/static/vendor/fontawesome-free/svgs/solid/oil-can.svg +1 -0
  1573. package/static/vendor/fontawesome-free/svgs/solid/om.svg +1 -0
  1574. package/static/vendor/fontawesome-free/svgs/solid/otter.svg +1 -0
  1575. package/static/vendor/fontawesome-free/svgs/solid/outdent.svg +1 -0
  1576. package/static/vendor/fontawesome-free/svgs/solid/pager.svg +1 -0
  1577. package/static/vendor/fontawesome-free/svgs/solid/paint-brush.svg +1 -0
  1578. package/static/vendor/fontawesome-free/svgs/solid/paint-roller.svg +1 -0
  1579. package/static/vendor/fontawesome-free/svgs/solid/palette.svg +1 -0
  1580. package/static/vendor/fontawesome-free/svgs/solid/pallet.svg +1 -0
  1581. package/static/vendor/fontawesome-free/svgs/solid/paper-plane.svg +1 -0
  1582. package/static/vendor/fontawesome-free/svgs/solid/paperclip.svg +1 -0
  1583. package/static/vendor/fontawesome-free/svgs/solid/parachute-box.svg +1 -0
  1584. package/static/vendor/fontawesome-free/svgs/solid/paragraph.svg +1 -0
  1585. package/static/vendor/fontawesome-free/svgs/solid/parking.svg +1 -0
  1586. package/static/vendor/fontawesome-free/svgs/solid/passport.svg +1 -0
  1587. package/static/vendor/fontawesome-free/svgs/solid/pastafarianism.svg +1 -0
  1588. package/static/vendor/fontawesome-free/svgs/solid/paste.svg +1 -0
  1589. package/static/vendor/fontawesome-free/svgs/solid/pause-circle.svg +1 -0
  1590. package/static/vendor/fontawesome-free/svgs/solid/pause.svg +1 -0
  1591. package/static/vendor/fontawesome-free/svgs/solid/paw.svg +1 -0
  1592. package/static/vendor/fontawesome-free/svgs/solid/peace.svg +1 -0
  1593. package/static/vendor/fontawesome-free/svgs/solid/pen-alt.svg +1 -0
  1594. package/static/vendor/fontawesome-free/svgs/solid/pen-fancy.svg +1 -0
  1595. package/static/vendor/fontawesome-free/svgs/solid/pen-nib.svg +1 -0
  1596. package/static/vendor/fontawesome-free/svgs/solid/pen-square.svg +1 -0
  1597. package/static/vendor/fontawesome-free/svgs/solid/pen.svg +1 -0
  1598. package/static/vendor/fontawesome-free/svgs/solid/pencil-alt.svg +1 -0
  1599. package/static/vendor/fontawesome-free/svgs/solid/pencil-ruler.svg +1 -0
  1600. package/static/vendor/fontawesome-free/svgs/solid/people-arrows.svg +1 -0
  1601. package/static/vendor/fontawesome-free/svgs/solid/people-carry.svg +1 -0
  1602. package/static/vendor/fontawesome-free/svgs/solid/pepper-hot.svg +1 -0
  1603. package/static/vendor/fontawesome-free/svgs/solid/percent.svg +1 -0
  1604. package/static/vendor/fontawesome-free/svgs/solid/percentage.svg +1 -0
  1605. package/static/vendor/fontawesome-free/svgs/solid/person-booth.svg +1 -0
  1606. package/static/vendor/fontawesome-free/svgs/solid/phone-alt.svg +1 -0
  1607. package/static/vendor/fontawesome-free/svgs/solid/phone-slash.svg +1 -0
  1608. package/static/vendor/fontawesome-free/svgs/solid/phone-square-alt.svg +1 -0
  1609. package/static/vendor/fontawesome-free/svgs/solid/phone-square.svg +1 -0
  1610. package/static/vendor/fontawesome-free/svgs/solid/phone-volume.svg +1 -0
  1611. package/static/vendor/fontawesome-free/svgs/solid/phone.svg +1 -0
  1612. package/static/vendor/fontawesome-free/svgs/solid/photo-video.svg +1 -0
  1613. package/static/vendor/fontawesome-free/svgs/solid/piggy-bank.svg +1 -0
  1614. package/static/vendor/fontawesome-free/svgs/solid/pills.svg +1 -0
  1615. package/static/vendor/fontawesome-free/svgs/solid/pizza-slice.svg +1 -0
  1616. package/static/vendor/fontawesome-free/svgs/solid/place-of-worship.svg +1 -0
  1617. package/static/vendor/fontawesome-free/svgs/solid/plane-arrival.svg +1 -0
  1618. package/static/vendor/fontawesome-free/svgs/solid/plane-departure.svg +1 -0
  1619. package/static/vendor/fontawesome-free/svgs/solid/plane-slash.svg +1 -0
  1620. package/static/vendor/fontawesome-free/svgs/solid/plane.svg +1 -0
  1621. package/static/vendor/fontawesome-free/svgs/solid/play-circle.svg +1 -0
  1622. package/static/vendor/fontawesome-free/svgs/solid/play.svg +1 -0
  1623. package/static/vendor/fontawesome-free/svgs/solid/plug.svg +1 -0
  1624. package/static/vendor/fontawesome-free/svgs/solid/plus-circle.svg +1 -0
  1625. package/static/vendor/fontawesome-free/svgs/solid/plus-square.svg +1 -0
  1626. package/static/vendor/fontawesome-free/svgs/solid/plus.svg +1 -0
  1627. package/static/vendor/fontawesome-free/svgs/solid/podcast.svg +1 -0
  1628. package/static/vendor/fontawesome-free/svgs/solid/poll-h.svg +1 -0
  1629. package/static/vendor/fontawesome-free/svgs/solid/poll.svg +1 -0
  1630. package/static/vendor/fontawesome-free/svgs/solid/poo-storm.svg +1 -0
  1631. package/static/vendor/fontawesome-free/svgs/solid/poo.svg +1 -0
  1632. package/static/vendor/fontawesome-free/svgs/solid/poop.svg +1 -0
  1633. package/static/vendor/fontawesome-free/svgs/solid/portrait.svg +1 -0
  1634. package/static/vendor/fontawesome-free/svgs/solid/pound-sign.svg +1 -0
  1635. package/static/vendor/fontawesome-free/svgs/solid/power-off.svg +1 -0
  1636. package/static/vendor/fontawesome-free/svgs/solid/pray.svg +1 -0
  1637. package/static/vendor/fontawesome-free/svgs/solid/praying-hands.svg +1 -0
  1638. package/static/vendor/fontawesome-free/svgs/solid/prescription-bottle-alt.svg +1 -0
  1639. package/static/vendor/fontawesome-free/svgs/solid/prescription-bottle.svg +1 -0
  1640. package/static/vendor/fontawesome-free/svgs/solid/prescription.svg +1 -0
  1641. package/static/vendor/fontawesome-free/svgs/solid/print.svg +1 -0
  1642. package/static/vendor/fontawesome-free/svgs/solid/procedures.svg +1 -0
  1643. package/static/vendor/fontawesome-free/svgs/solid/project-diagram.svg +1 -0
  1644. package/static/vendor/fontawesome-free/svgs/solid/pump-medical.svg +1 -0
  1645. package/static/vendor/fontawesome-free/svgs/solid/pump-soap.svg +1 -0
  1646. package/static/vendor/fontawesome-free/svgs/solid/puzzle-piece.svg +1 -0
  1647. package/static/vendor/fontawesome-free/svgs/solid/qrcode.svg +1 -0
  1648. package/static/vendor/fontawesome-free/svgs/solid/question-circle.svg +1 -0
  1649. package/static/vendor/fontawesome-free/svgs/solid/question.svg +1 -0
  1650. package/static/vendor/fontawesome-free/svgs/solid/quidditch.svg +1 -0
  1651. package/static/vendor/fontawesome-free/svgs/solid/quote-left.svg +1 -0
  1652. package/static/vendor/fontawesome-free/svgs/solid/quote-right.svg +1 -0
  1653. package/static/vendor/fontawesome-free/svgs/solid/quran.svg +1 -0
  1654. package/static/vendor/fontawesome-free/svgs/solid/radiation-alt.svg +1 -0
  1655. package/static/vendor/fontawesome-free/svgs/solid/radiation.svg +1 -0
  1656. package/static/vendor/fontawesome-free/svgs/solid/rainbow.svg +1 -0
  1657. package/static/vendor/fontawesome-free/svgs/solid/random.svg +1 -0
  1658. package/static/vendor/fontawesome-free/svgs/solid/receipt.svg +1 -0
  1659. package/static/vendor/fontawesome-free/svgs/solid/record-vinyl.svg +1 -0
  1660. package/static/vendor/fontawesome-free/svgs/solid/recycle.svg +1 -0
  1661. package/static/vendor/fontawesome-free/svgs/solid/redo-alt.svg +1 -0
  1662. package/static/vendor/fontawesome-free/svgs/solid/redo.svg +1 -0
  1663. package/static/vendor/fontawesome-free/svgs/solid/registered.svg +1 -0
  1664. package/static/vendor/fontawesome-free/svgs/solid/remove-format.svg +1 -0
  1665. package/static/vendor/fontawesome-free/svgs/solid/reply-all.svg +1 -0
  1666. package/static/vendor/fontawesome-free/svgs/solid/reply.svg +1 -0
  1667. package/static/vendor/fontawesome-free/svgs/solid/republican.svg +1 -0
  1668. package/static/vendor/fontawesome-free/svgs/solid/restroom.svg +1 -0
  1669. package/static/vendor/fontawesome-free/svgs/solid/retweet.svg +1 -0
  1670. package/static/vendor/fontawesome-free/svgs/solid/ribbon.svg +1 -0
  1671. package/static/vendor/fontawesome-free/svgs/solid/ring.svg +1 -0
  1672. package/static/vendor/fontawesome-free/svgs/solid/road.svg +1 -0
  1673. package/static/vendor/fontawesome-free/svgs/solid/robot.svg +1 -0
  1674. package/static/vendor/fontawesome-free/svgs/solid/rocket.svg +1 -0
  1675. package/static/vendor/fontawesome-free/svgs/solid/route.svg +1 -0
  1676. package/static/vendor/fontawesome-free/svgs/solid/rss-square.svg +1 -0
  1677. package/static/vendor/fontawesome-free/svgs/solid/rss.svg +1 -0
  1678. package/static/vendor/fontawesome-free/svgs/solid/ruble-sign.svg +1 -0
  1679. package/static/vendor/fontawesome-free/svgs/solid/ruler-combined.svg +1 -0
  1680. package/static/vendor/fontawesome-free/svgs/solid/ruler-horizontal.svg +1 -0
  1681. package/static/vendor/fontawesome-free/svgs/solid/ruler-vertical.svg +1 -0
  1682. package/static/vendor/fontawesome-free/svgs/solid/ruler.svg +1 -0
  1683. package/static/vendor/fontawesome-free/svgs/solid/running.svg +1 -0
  1684. package/static/vendor/fontawesome-free/svgs/solid/rupee-sign.svg +1 -0
  1685. package/static/vendor/fontawesome-free/svgs/solid/sad-cry.svg +1 -0
  1686. package/static/vendor/fontawesome-free/svgs/solid/sad-tear.svg +1 -0
  1687. package/static/vendor/fontawesome-free/svgs/solid/satellite-dish.svg +1 -0
  1688. package/static/vendor/fontawesome-free/svgs/solid/satellite.svg +1 -0
  1689. package/static/vendor/fontawesome-free/svgs/solid/save.svg +1 -0
  1690. package/static/vendor/fontawesome-free/svgs/solid/school.svg +1 -0
  1691. package/static/vendor/fontawesome-free/svgs/solid/screwdriver.svg +1 -0
  1692. package/static/vendor/fontawesome-free/svgs/solid/scroll.svg +1 -0
  1693. package/static/vendor/fontawesome-free/svgs/solid/sd-card.svg +1 -0
  1694. package/static/vendor/fontawesome-free/svgs/solid/search-dollar.svg +1 -0
  1695. package/static/vendor/fontawesome-free/svgs/solid/search-location.svg +1 -0
  1696. package/static/vendor/fontawesome-free/svgs/solid/search-minus.svg +1 -0
  1697. package/static/vendor/fontawesome-free/svgs/solid/search-plus.svg +1 -0
  1698. package/static/vendor/fontawesome-free/svgs/solid/search.svg +1 -0
  1699. package/static/vendor/fontawesome-free/svgs/solid/seedling.svg +1 -0
  1700. package/static/vendor/fontawesome-free/svgs/solid/server.svg +1 -0
  1701. package/static/vendor/fontawesome-free/svgs/solid/shapes.svg +1 -0
  1702. package/static/vendor/fontawesome-free/svgs/solid/share-alt-square.svg +1 -0
  1703. package/static/vendor/fontawesome-free/svgs/solid/share-alt.svg +1 -0
  1704. package/static/vendor/fontawesome-free/svgs/solid/share-square.svg +1 -0
  1705. package/static/vendor/fontawesome-free/svgs/solid/share.svg +1 -0
  1706. package/static/vendor/fontawesome-free/svgs/solid/shekel-sign.svg +1 -0
  1707. package/static/vendor/fontawesome-free/svgs/solid/shield-alt.svg +1 -0
  1708. package/static/vendor/fontawesome-free/svgs/solid/shield-virus.svg +1 -0
  1709. package/static/vendor/fontawesome-free/svgs/solid/ship.svg +1 -0
  1710. package/static/vendor/fontawesome-free/svgs/solid/shipping-fast.svg +1 -0
  1711. package/static/vendor/fontawesome-free/svgs/solid/shoe-prints.svg +1 -0
  1712. package/static/vendor/fontawesome-free/svgs/solid/shopping-bag.svg +1 -0
  1713. package/static/vendor/fontawesome-free/svgs/solid/shopping-basket.svg +1 -0
  1714. package/static/vendor/fontawesome-free/svgs/solid/shopping-cart.svg +1 -0
  1715. package/static/vendor/fontawesome-free/svgs/solid/shower.svg +1 -0
  1716. package/static/vendor/fontawesome-free/svgs/solid/shuttle-van.svg +1 -0
  1717. package/static/vendor/fontawesome-free/svgs/solid/sign-in-alt.svg +1 -0
  1718. package/static/vendor/fontawesome-free/svgs/solid/sign-language.svg +1 -0
  1719. package/static/vendor/fontawesome-free/svgs/solid/sign-out-alt.svg +1 -0
  1720. package/static/vendor/fontawesome-free/svgs/solid/sign.svg +1 -0
  1721. package/static/vendor/fontawesome-free/svgs/solid/signal.svg +1 -0
  1722. package/static/vendor/fontawesome-free/svgs/solid/signature.svg +1 -0
  1723. package/static/vendor/fontawesome-free/svgs/solid/sim-card.svg +1 -0
  1724. package/static/vendor/fontawesome-free/svgs/solid/sink.svg +1 -0
  1725. package/static/vendor/fontawesome-free/svgs/solid/sitemap.svg +1 -0
  1726. package/static/vendor/fontawesome-free/svgs/solid/skating.svg +1 -0
  1727. package/static/vendor/fontawesome-free/svgs/solid/skiing-nordic.svg +1 -0
  1728. package/static/vendor/fontawesome-free/svgs/solid/skiing.svg +1 -0
  1729. package/static/vendor/fontawesome-free/svgs/solid/skull-crossbones.svg +1 -0
  1730. package/static/vendor/fontawesome-free/svgs/solid/skull.svg +1 -0
  1731. package/static/vendor/fontawesome-free/svgs/solid/slash.svg +1 -0
  1732. package/static/vendor/fontawesome-free/svgs/solid/sleigh.svg +1 -0
  1733. package/static/vendor/fontawesome-free/svgs/solid/sliders-h.svg +1 -0
  1734. package/static/vendor/fontawesome-free/svgs/solid/smile-beam.svg +1 -0
  1735. package/static/vendor/fontawesome-free/svgs/solid/smile-wink.svg +1 -0
  1736. package/static/vendor/fontawesome-free/svgs/solid/smile.svg +1 -0
  1737. package/static/vendor/fontawesome-free/svgs/solid/smog.svg +1 -0
  1738. package/static/vendor/fontawesome-free/svgs/solid/smoking-ban.svg +1 -0
  1739. package/static/vendor/fontawesome-free/svgs/solid/smoking.svg +1 -0
  1740. package/static/vendor/fontawesome-free/svgs/solid/sms.svg +1 -0
  1741. package/static/vendor/fontawesome-free/svgs/solid/snowboarding.svg +1 -0
  1742. package/static/vendor/fontawesome-free/svgs/solid/snowflake.svg +1 -0
  1743. package/static/vendor/fontawesome-free/svgs/solid/snowman.svg +1 -0
  1744. package/static/vendor/fontawesome-free/svgs/solid/snowplow.svg +1 -0
  1745. package/static/vendor/fontawesome-free/svgs/solid/soap.svg +1 -0
  1746. package/static/vendor/fontawesome-free/svgs/solid/socks.svg +1 -0
  1747. package/static/vendor/fontawesome-free/svgs/solid/solar-panel.svg +1 -0
  1748. package/static/vendor/fontawesome-free/svgs/solid/sort-alpha-down-alt.svg +1 -0
  1749. package/static/vendor/fontawesome-free/svgs/solid/sort-alpha-down.svg +1 -0
  1750. package/static/vendor/fontawesome-free/svgs/solid/sort-alpha-up-alt.svg +1 -0
  1751. package/static/vendor/fontawesome-free/svgs/solid/sort-alpha-up.svg +1 -0
  1752. package/static/vendor/fontawesome-free/svgs/solid/sort-amount-down-alt.svg +1 -0
  1753. package/static/vendor/fontawesome-free/svgs/solid/sort-amount-down.svg +1 -0
  1754. package/static/vendor/fontawesome-free/svgs/solid/sort-amount-up-alt.svg +1 -0
  1755. package/static/vendor/fontawesome-free/svgs/solid/sort-amount-up.svg +1 -0
  1756. package/static/vendor/fontawesome-free/svgs/solid/sort-down.svg +1 -0
  1757. package/static/vendor/fontawesome-free/svgs/solid/sort-numeric-down-alt.svg +1 -0
  1758. package/static/vendor/fontawesome-free/svgs/solid/sort-numeric-down.svg +1 -0
  1759. package/static/vendor/fontawesome-free/svgs/solid/sort-numeric-up-alt.svg +1 -0
  1760. package/static/vendor/fontawesome-free/svgs/solid/sort-numeric-up.svg +1 -0
  1761. package/static/vendor/fontawesome-free/svgs/solid/sort-up.svg +1 -0
  1762. package/static/vendor/fontawesome-free/svgs/solid/sort.svg +1 -0
  1763. package/static/vendor/fontawesome-free/svgs/solid/spa.svg +1 -0
  1764. package/static/vendor/fontawesome-free/svgs/solid/space-shuttle.svg +1 -0
  1765. package/static/vendor/fontawesome-free/svgs/solid/spell-check.svg +1 -0
  1766. package/static/vendor/fontawesome-free/svgs/solid/spider.svg +1 -0
  1767. package/static/vendor/fontawesome-free/svgs/solid/spinner.svg +1 -0
  1768. package/static/vendor/fontawesome-free/svgs/solid/splotch.svg +1 -0
  1769. package/static/vendor/fontawesome-free/svgs/solid/spray-can.svg +1 -0
  1770. package/static/vendor/fontawesome-free/svgs/solid/square-full.svg +1 -0
  1771. package/static/vendor/fontawesome-free/svgs/solid/square-root-alt.svg +1 -0
  1772. package/static/vendor/fontawesome-free/svgs/solid/square.svg +1 -0
  1773. package/static/vendor/fontawesome-free/svgs/solid/stamp.svg +1 -0
  1774. package/static/vendor/fontawesome-free/svgs/solid/star-and-crescent.svg +1 -0
  1775. package/static/vendor/fontawesome-free/svgs/solid/star-half-alt.svg +1 -0
  1776. package/static/vendor/fontawesome-free/svgs/solid/star-half.svg +1 -0
  1777. package/static/vendor/fontawesome-free/svgs/solid/star-of-david.svg +1 -0
  1778. package/static/vendor/fontawesome-free/svgs/solid/star-of-life.svg +1 -0
  1779. package/static/vendor/fontawesome-free/svgs/solid/star.svg +1 -0
  1780. package/static/vendor/fontawesome-free/svgs/solid/step-backward.svg +1 -0
  1781. package/static/vendor/fontawesome-free/svgs/solid/step-forward.svg +1 -0
  1782. package/static/vendor/fontawesome-free/svgs/solid/stethoscope.svg +1 -0
  1783. package/static/vendor/fontawesome-free/svgs/solid/sticky-note.svg +1 -0
  1784. package/static/vendor/fontawesome-free/svgs/solid/stop-circle.svg +1 -0
  1785. package/static/vendor/fontawesome-free/svgs/solid/stop.svg +1 -0
  1786. package/static/vendor/fontawesome-free/svgs/solid/stopwatch-20.svg +1 -0
  1787. package/static/vendor/fontawesome-free/svgs/solid/stopwatch.svg +1 -0
  1788. package/static/vendor/fontawesome-free/svgs/solid/store-alt-slash.svg +1 -0
  1789. package/static/vendor/fontawesome-free/svgs/solid/store-alt.svg +1 -0
  1790. package/static/vendor/fontawesome-free/svgs/solid/store-slash.svg +1 -0
  1791. package/static/vendor/fontawesome-free/svgs/solid/store.svg +1 -0
  1792. package/static/vendor/fontawesome-free/svgs/solid/stream.svg +1 -0
  1793. package/static/vendor/fontawesome-free/svgs/solid/street-view.svg +1 -0
  1794. package/static/vendor/fontawesome-free/svgs/solid/strikethrough.svg +1 -0
  1795. package/static/vendor/fontawesome-free/svgs/solid/stroopwafel.svg +1 -0
  1796. package/static/vendor/fontawesome-free/svgs/solid/subscript.svg +1 -0
  1797. package/static/vendor/fontawesome-free/svgs/solid/subway.svg +1 -0
  1798. package/static/vendor/fontawesome-free/svgs/solid/suitcase-rolling.svg +1 -0
  1799. package/static/vendor/fontawesome-free/svgs/solid/suitcase.svg +1 -0
  1800. package/static/vendor/fontawesome-free/svgs/solid/sun.svg +1 -0
  1801. package/static/vendor/fontawesome-free/svgs/solid/superscript.svg +1 -0
  1802. package/static/vendor/fontawesome-free/svgs/solid/surprise.svg +1 -0
  1803. package/static/vendor/fontawesome-free/svgs/solid/swatchbook.svg +1 -0
  1804. package/static/vendor/fontawesome-free/svgs/solid/swimmer.svg +1 -0
  1805. package/static/vendor/fontawesome-free/svgs/solid/swimming-pool.svg +1 -0
  1806. package/static/vendor/fontawesome-free/svgs/solid/synagogue.svg +1 -0
  1807. package/static/vendor/fontawesome-free/svgs/solid/sync-alt.svg +1 -0
  1808. package/static/vendor/fontawesome-free/svgs/solid/sync.svg +1 -0
  1809. package/static/vendor/fontawesome-free/svgs/solid/syringe.svg +1 -0
  1810. package/static/vendor/fontawesome-free/svgs/solid/table-tennis.svg +1 -0
  1811. package/static/vendor/fontawesome-free/svgs/solid/table.svg +1 -0
  1812. package/static/vendor/fontawesome-free/svgs/solid/tablet-alt.svg +1 -0
  1813. package/static/vendor/fontawesome-free/svgs/solid/tablet.svg +1 -0
  1814. package/static/vendor/fontawesome-free/svgs/solid/tablets.svg +1 -0
  1815. package/static/vendor/fontawesome-free/svgs/solid/tachometer-alt.svg +1 -0
  1816. package/static/vendor/fontawesome-free/svgs/solid/tag.svg +1 -0
  1817. package/static/vendor/fontawesome-free/svgs/solid/tags.svg +1 -0
  1818. package/static/vendor/fontawesome-free/svgs/solid/tape.svg +1 -0
  1819. package/static/vendor/fontawesome-free/svgs/solid/tasks.svg +1 -0
  1820. package/static/vendor/fontawesome-free/svgs/solid/taxi.svg +1 -0
  1821. package/static/vendor/fontawesome-free/svgs/solid/teeth-open.svg +1 -0
  1822. package/static/vendor/fontawesome-free/svgs/solid/teeth.svg +1 -0
  1823. package/static/vendor/fontawesome-free/svgs/solid/temperature-high.svg +1 -0
  1824. package/static/vendor/fontawesome-free/svgs/solid/temperature-low.svg +1 -0
  1825. package/static/vendor/fontawesome-free/svgs/solid/tenge.svg +1 -0
  1826. package/static/vendor/fontawesome-free/svgs/solid/terminal.svg +1 -0
  1827. package/static/vendor/fontawesome-free/svgs/solid/text-height.svg +1 -0
  1828. package/static/vendor/fontawesome-free/svgs/solid/text-width.svg +1 -0
  1829. package/static/vendor/fontawesome-free/svgs/solid/th-large.svg +1 -0
  1830. package/static/vendor/fontawesome-free/svgs/solid/th-list.svg +1 -0
  1831. package/static/vendor/fontawesome-free/svgs/solid/th.svg +1 -0
  1832. package/static/vendor/fontawesome-free/svgs/solid/theater-masks.svg +1 -0
  1833. package/static/vendor/fontawesome-free/svgs/solid/thermometer-empty.svg +1 -0
  1834. package/static/vendor/fontawesome-free/svgs/solid/thermometer-full.svg +1 -0
  1835. package/static/vendor/fontawesome-free/svgs/solid/thermometer-half.svg +1 -0
  1836. package/static/vendor/fontawesome-free/svgs/solid/thermometer-quarter.svg +1 -0
  1837. package/static/vendor/fontawesome-free/svgs/solid/thermometer-three-quarters.svg +1 -0
  1838. package/static/vendor/fontawesome-free/svgs/solid/thermometer.svg +1 -0
  1839. package/static/vendor/fontawesome-free/svgs/solid/thumbs-down.svg +1 -0
  1840. package/static/vendor/fontawesome-free/svgs/solid/thumbs-up.svg +1 -0
  1841. package/static/vendor/fontawesome-free/svgs/solid/thumbtack.svg +1 -0
  1842. package/static/vendor/fontawesome-free/svgs/solid/ticket-alt.svg +1 -0
  1843. package/static/vendor/fontawesome-free/svgs/solid/times-circle.svg +1 -0
  1844. package/static/vendor/fontawesome-free/svgs/solid/times.svg +1 -0
  1845. package/static/vendor/fontawesome-free/svgs/solid/tint-slash.svg +1 -0
  1846. package/static/vendor/fontawesome-free/svgs/solid/tint.svg +1 -0
  1847. package/static/vendor/fontawesome-free/svgs/solid/tired.svg +1 -0
  1848. package/static/vendor/fontawesome-free/svgs/solid/toggle-off.svg +1 -0
  1849. package/static/vendor/fontawesome-free/svgs/solid/toggle-on.svg +1 -0
  1850. package/static/vendor/fontawesome-free/svgs/solid/toilet-paper-slash.svg +1 -0
  1851. package/static/vendor/fontawesome-free/svgs/solid/toilet-paper.svg +1 -0
  1852. package/static/vendor/fontawesome-free/svgs/solid/toilet.svg +1 -0
  1853. package/static/vendor/fontawesome-free/svgs/solid/toolbox.svg +1 -0
  1854. package/static/vendor/fontawesome-free/svgs/solid/tools.svg +1 -0
  1855. package/static/vendor/fontawesome-free/svgs/solid/tooth.svg +1 -0
  1856. package/static/vendor/fontawesome-free/svgs/solid/torah.svg +1 -0
  1857. package/static/vendor/fontawesome-free/svgs/solid/torii-gate.svg +1 -0
  1858. package/static/vendor/fontawesome-free/svgs/solid/tractor.svg +1 -0
  1859. package/static/vendor/fontawesome-free/svgs/solid/trademark.svg +1 -0
  1860. package/static/vendor/fontawesome-free/svgs/solid/traffic-light.svg +1 -0
  1861. package/static/vendor/fontawesome-free/svgs/solid/trailer.svg +1 -0
  1862. package/static/vendor/fontawesome-free/svgs/solid/train.svg +1 -0
  1863. package/static/vendor/fontawesome-free/svgs/solid/tram.svg +1 -0
  1864. package/static/vendor/fontawesome-free/svgs/solid/transgender-alt.svg +1 -0
  1865. package/static/vendor/fontawesome-free/svgs/solid/transgender.svg +1 -0
  1866. package/static/vendor/fontawesome-free/svgs/solid/trash-alt.svg +1 -0
  1867. package/static/vendor/fontawesome-free/svgs/solid/trash-restore-alt.svg +1 -0
  1868. package/static/vendor/fontawesome-free/svgs/solid/trash-restore.svg +1 -0
  1869. package/static/vendor/fontawesome-free/svgs/solid/trash.svg +1 -0
  1870. package/static/vendor/fontawesome-free/svgs/solid/tree.svg +1 -0
  1871. package/static/vendor/fontawesome-free/svgs/solid/trophy.svg +1 -0
  1872. package/static/vendor/fontawesome-free/svgs/solid/truck-loading.svg +1 -0
  1873. package/static/vendor/fontawesome-free/svgs/solid/truck-monster.svg +1 -0
  1874. package/static/vendor/fontawesome-free/svgs/solid/truck-moving.svg +1 -0
  1875. package/static/vendor/fontawesome-free/svgs/solid/truck-pickup.svg +1 -0
  1876. package/static/vendor/fontawesome-free/svgs/solid/truck.svg +1 -0
  1877. package/static/vendor/fontawesome-free/svgs/solid/tshirt.svg +1 -0
  1878. package/static/vendor/fontawesome-free/svgs/solid/tty.svg +1 -0
  1879. package/static/vendor/fontawesome-free/svgs/solid/tv.svg +1 -0
  1880. package/static/vendor/fontawesome-free/svgs/solid/umbrella-beach.svg +1 -0
  1881. package/static/vendor/fontawesome-free/svgs/solid/umbrella.svg +1 -0
  1882. package/static/vendor/fontawesome-free/svgs/solid/underline.svg +1 -0
  1883. package/static/vendor/fontawesome-free/svgs/solid/undo-alt.svg +1 -0
  1884. package/static/vendor/fontawesome-free/svgs/solid/undo.svg +1 -0
  1885. package/static/vendor/fontawesome-free/svgs/solid/universal-access.svg +1 -0
  1886. package/static/vendor/fontawesome-free/svgs/solid/university.svg +1 -0
  1887. package/static/vendor/fontawesome-free/svgs/solid/unlink.svg +1 -0
  1888. package/static/vendor/fontawesome-free/svgs/solid/unlock-alt.svg +1 -0
  1889. package/static/vendor/fontawesome-free/svgs/solid/unlock.svg +1 -0
  1890. package/static/vendor/fontawesome-free/svgs/solid/upload.svg +1 -0
  1891. package/static/vendor/fontawesome-free/svgs/solid/user-alt-slash.svg +1 -0
  1892. package/static/vendor/fontawesome-free/svgs/solid/user-alt.svg +1 -0
  1893. package/static/vendor/fontawesome-free/svgs/solid/user-astronaut.svg +1 -0
  1894. package/static/vendor/fontawesome-free/svgs/solid/user-check.svg +1 -0
  1895. package/static/vendor/fontawesome-free/svgs/solid/user-circle.svg +1 -0
  1896. package/static/vendor/fontawesome-free/svgs/solid/user-clock.svg +1 -0
  1897. package/static/vendor/fontawesome-free/svgs/solid/user-cog.svg +1 -0
  1898. package/static/vendor/fontawesome-free/svgs/solid/user-edit.svg +1 -0
  1899. package/static/vendor/fontawesome-free/svgs/solid/user-friends.svg +1 -0
  1900. package/static/vendor/fontawesome-free/svgs/solid/user-graduate.svg +1 -0
  1901. package/static/vendor/fontawesome-free/svgs/solid/user-injured.svg +1 -0
  1902. package/static/vendor/fontawesome-free/svgs/solid/user-lock.svg +1 -0
  1903. package/static/vendor/fontawesome-free/svgs/solid/user-md.svg +1 -0
  1904. package/static/vendor/fontawesome-free/svgs/solid/user-minus.svg +1 -0
  1905. package/static/vendor/fontawesome-free/svgs/solid/user-ninja.svg +1 -0
  1906. package/static/vendor/fontawesome-free/svgs/solid/user-nurse.svg +1 -0
  1907. package/static/vendor/fontawesome-free/svgs/solid/user-plus.svg +1 -0
  1908. package/static/vendor/fontawesome-free/svgs/solid/user-secret.svg +1 -0
  1909. package/static/vendor/fontawesome-free/svgs/solid/user-shield.svg +1 -0
  1910. package/static/vendor/fontawesome-free/svgs/solid/user-slash.svg +1 -0
  1911. package/static/vendor/fontawesome-free/svgs/solid/user-tag.svg +1 -0
  1912. package/static/vendor/fontawesome-free/svgs/solid/user-tie.svg +1 -0
  1913. package/static/vendor/fontawesome-free/svgs/solid/user-times.svg +1 -0
  1914. package/static/vendor/fontawesome-free/svgs/solid/user.svg +1 -0
  1915. package/static/vendor/fontawesome-free/svgs/solid/users-cog.svg +1 -0
  1916. package/static/vendor/fontawesome-free/svgs/solid/users-slash.svg +1 -0
  1917. package/static/vendor/fontawesome-free/svgs/solid/users.svg +1 -0
  1918. package/static/vendor/fontawesome-free/svgs/solid/utensil-spoon.svg +1 -0
  1919. package/static/vendor/fontawesome-free/svgs/solid/utensils.svg +1 -0
  1920. package/static/vendor/fontawesome-free/svgs/solid/vector-square.svg +1 -0
  1921. package/static/vendor/fontawesome-free/svgs/solid/venus-double.svg +1 -0
  1922. package/static/vendor/fontawesome-free/svgs/solid/venus-mars.svg +1 -0
  1923. package/static/vendor/fontawesome-free/svgs/solid/venus.svg +1 -0
  1924. package/static/vendor/fontawesome-free/svgs/solid/vest-patches.svg +1 -0
  1925. package/static/vendor/fontawesome-free/svgs/solid/vest.svg +1 -0
  1926. package/static/vendor/fontawesome-free/svgs/solid/vial.svg +1 -0
  1927. package/static/vendor/fontawesome-free/svgs/solid/vials.svg +1 -0
  1928. package/static/vendor/fontawesome-free/svgs/solid/video-slash.svg +1 -0
  1929. package/static/vendor/fontawesome-free/svgs/solid/video.svg +1 -0
  1930. package/static/vendor/fontawesome-free/svgs/solid/vihara.svg +1 -0
  1931. package/static/vendor/fontawesome-free/svgs/solid/virus-slash.svg +1 -0
  1932. package/static/vendor/fontawesome-free/svgs/solid/virus.svg +1 -0
  1933. package/static/vendor/fontawesome-free/svgs/solid/viruses.svg +1 -0
  1934. package/static/vendor/fontawesome-free/svgs/solid/voicemail.svg +1 -0
  1935. package/static/vendor/fontawesome-free/svgs/solid/volleyball-ball.svg +1 -0
  1936. package/static/vendor/fontawesome-free/svgs/solid/volume-down.svg +1 -0
  1937. package/static/vendor/fontawesome-free/svgs/solid/volume-mute.svg +1 -0
  1938. package/static/vendor/fontawesome-free/svgs/solid/volume-off.svg +1 -0
  1939. package/static/vendor/fontawesome-free/svgs/solid/volume-up.svg +1 -0
  1940. package/static/vendor/fontawesome-free/svgs/solid/vote-yea.svg +1 -0
  1941. package/static/vendor/fontawesome-free/svgs/solid/vr-cardboard.svg +1 -0
  1942. package/static/vendor/fontawesome-free/svgs/solid/walking.svg +1 -0
  1943. package/static/vendor/fontawesome-free/svgs/solid/wallet.svg +1 -0
  1944. package/static/vendor/fontawesome-free/svgs/solid/warehouse.svg +1 -0
  1945. package/static/vendor/fontawesome-free/svgs/solid/water.svg +1 -0
  1946. package/static/vendor/fontawesome-free/svgs/solid/wave-square.svg +1 -0
  1947. package/static/vendor/fontawesome-free/svgs/solid/weight-hanging.svg +1 -0
  1948. package/static/vendor/fontawesome-free/svgs/solid/weight.svg +1 -0
  1949. package/static/vendor/fontawesome-free/svgs/solid/wheelchair.svg +1 -0
  1950. package/static/vendor/fontawesome-free/svgs/solid/wifi.svg +1 -0
  1951. package/static/vendor/fontawesome-free/svgs/solid/wind.svg +1 -0
  1952. package/static/vendor/fontawesome-free/svgs/solid/window-close.svg +1 -0
  1953. package/static/vendor/fontawesome-free/svgs/solid/window-maximize.svg +1 -0
  1954. package/static/vendor/fontawesome-free/svgs/solid/window-minimize.svg +1 -0
  1955. package/static/vendor/fontawesome-free/svgs/solid/window-restore.svg +1 -0
  1956. package/static/vendor/fontawesome-free/svgs/solid/wine-bottle.svg +1 -0
  1957. package/static/vendor/fontawesome-free/svgs/solid/wine-glass-alt.svg +1 -0
  1958. package/static/vendor/fontawesome-free/svgs/solid/wine-glass.svg +1 -0
  1959. package/static/vendor/fontawesome-free/svgs/solid/won-sign.svg +1 -0
  1960. package/static/vendor/fontawesome-free/svgs/solid/wrench.svg +1 -0
  1961. package/static/vendor/fontawesome-free/svgs/solid/x-ray.svg +1 -0
  1962. package/static/vendor/fontawesome-free/svgs/solid/yen-sign.svg +1 -0
  1963. package/static/vendor/fontawesome-free/svgs/solid/yin-yang.svg +1 -0
  1964. package/static/vendor/fontawesome-free/webfonts/fa-brands-400.eot +0 -0
  1965. package/static/vendor/fontawesome-free/webfonts/fa-brands-400.svg +3717 -0
  1966. package/static/vendor/fontawesome-free/webfonts/fa-brands-400.ttf +0 -0
  1967. package/static/vendor/fontawesome-free/webfonts/fa-brands-400.woff +0 -0
  1968. package/static/vendor/fontawesome-free/webfonts/fa-brands-400.woff2 +0 -0
  1969. package/static/vendor/fontawesome-free/webfonts/fa-regular-400.eot +0 -0
  1970. package/static/vendor/fontawesome-free/webfonts/fa-regular-400.svg +801 -0
  1971. package/static/vendor/fontawesome-free/webfonts/fa-regular-400.ttf +0 -0
  1972. package/static/vendor/fontawesome-free/webfonts/fa-regular-400.woff +0 -0
  1973. package/static/vendor/fontawesome-free/webfonts/fa-regular-400.woff2 +0 -0
  1974. package/static/vendor/fontawesome-free/webfonts/fa-solid-900.eot +0 -0
  1975. package/static/vendor/fontawesome-free/webfonts/fa-solid-900.svg +5034 -0
  1976. package/static/vendor/fontawesome-free/webfonts/fa-solid-900.ttf +0 -0
  1977. package/static/vendor/fontawesome-free/webfonts/fa-solid-900.woff +0 -0
  1978. package/static/vendor/fontawesome-free/webfonts/fa-solid-900.woff2 +0 -0
  1979. package/static/vendor/handlebars/handlebars.min-v4.7.7.js +29 -0
  1980. package/static/vendor/jquery/jquery.js +10881 -0
  1981. package/static/vendor/jquery/jquery.min.js +2 -0
  1982. package/static/vendor/jquery/jquery.min.map +1 -0
  1983. package/static/vendor/jquery/jquery.slim.js +8782 -0
  1984. package/static/vendor/jquery/jquery.slim.min.js +2 -0
  1985. package/static/vendor/jquery/jquery.slim.min.map +1 -0
  1986. package/static/vendor/jquery-easing/jquery.easing.min.js +1 -0
  1987. package/systemd/emailengine.service +11 -3
  1988. package/systemd/nginx-proxy.conf +1 -1
  1989. package/test/api-test.js +899 -0
  1990. package/test/bounce-test.js +151 -0
  1991. package/test/fixtures/bounces/163.eml +2521 -0
  1992. package/test/fixtures/bounces/fastmail.eml +242 -0
  1993. package/test/fixtures/bounces/gmail.eml +252 -0
  1994. package/test/fixtures/bounces/hotmail.eml +655 -0
  1995. package/test/fixtures/bounces/mailru.eml +121 -0
  1996. package/test/fixtures/bounces/outlook.eml +1107 -0
  1997. package/test/fixtures/bounces/postfix.eml +101 -0
  1998. package/test/fixtures/bounces/rambler.eml +116 -0
  1999. package/test/fixtures/bounces/workmail.eml +142 -0
  2000. package/test/fixtures/bounces/yahoo.eml +139 -0
  2001. package/test/fixtures/bounces/zoho.eml +83 -0
  2002. package/test/fixtures/bounces/zonemta.eml +100 -0
  2003. package/test/oauth2-apps-test.js +301 -0
  2004. package/test/sendonly-test.js +160 -0
  2005. package/test/test-config.js +34 -0
  2006. package/test/webhooks-server.js +39 -0
  2007. package/translations/README.md +16 -0
  2008. package/translations/de.mo +0 -0
  2009. package/translations/de.po +335 -0
  2010. package/translations/en.mo +0 -0
  2011. package/translations/en.po +310 -0
  2012. package/translations/et.mo +0 -0
  2013. package/translations/et.po +331 -0
  2014. package/translations/fr.mo +0 -0
  2015. package/translations/fr.po +333 -0
  2016. package/translations/ja.mo +0 -0
  2017. package/translations/ja.po +322 -0
  2018. package/translations/locales.json +43 -0
  2019. package/translations/messages.pot +323 -0
  2020. package/translations/nl.mo +0 -0
  2021. package/translations/nl.po +325 -0
  2022. package/translations/pl.mo +0 -0
  2023. package/translations/pl.po +328 -0
  2024. package/update-info.sh +10 -0
  2025. package/views/account/login.hbs +54 -0
  2026. package/views/account/password.hbs +88 -0
  2027. package/views/account/security.hbs +269 -0
  2028. package/views/account/totp.hbs +30 -0
  2029. package/views/accounts/account.hbs +1254 -0
  2030. package/views/accounts/browse.hbs +102 -0
  2031. package/views/accounts/edit.hbs +332 -0
  2032. package/views/accounts/index.hbs +143 -0
  2033. package/views/accounts/register/imap-server.hbs +507 -0
  2034. package/views/accounts/register/imap.hbs +56 -0
  2035. package/views/accounts/register/index.hbs +52 -0
  2036. package/views/arena/index.hbs +4 -0
  2037. package/views/config/ai.hbs +820 -0
  2038. package/views/config/document-store/chat.hbs +362 -0
  2039. package/views/config/document-store/index.hbs +231 -0
  2040. package/views/config/document-store/mappings/index.hbs +116 -0
  2041. package/views/config/document-store/mappings/new.hbs +95 -0
  2042. package/views/config/document-store/pre-processing/index.hbs +459 -0
  2043. package/views/config/imap-proxy.hbs +479 -0
  2044. package/views/config/license.hbs +256 -0
  2045. package/views/config/logging.hbs +61 -0
  2046. package/views/config/network.hbs +334 -0
  2047. package/views/config/oauth/app.hbs +309 -0
  2048. package/views/config/oauth/edit.hbs +92 -0
  2049. package/views/config/oauth/index.hbs +150 -0
  2050. package/views/config/oauth/new.hbs +90 -0
  2051. package/views/config/oauth.hbs +354 -0
  2052. package/views/config/service-preview.hbs +14 -0
  2053. package/views/config/service.hbs +718 -0
  2054. package/views/config/smtp.hbs +525 -0
  2055. package/views/config/webhooks.hbs +404 -0
  2056. package/views/dashboard.hbs +315 -0
  2057. package/views/error.hbs +6 -1
  2058. package/views/gateways/edit.hbs +52 -0
  2059. package/views/gateways/gateway.hbs +120 -0
  2060. package/views/gateways/index.hbs +152 -0
  2061. package/views/gateways/new.hbs +61 -0
  2062. package/views/index.hbs +21 -0
  2063. package/views/internals/index.hbs +170 -0
  2064. package/views/internals/thread.hbs +143 -0
  2065. package/views/layout/app.hbs +516 -0
  2066. package/views/layout/login.hbs +78 -0
  2067. package/views/layout/main.hbs +67 -0
  2068. package/views/layout/public.hbs +90 -0
  2069. package/views/legal.hbs +83 -0
  2070. package/views/license.hbs +5 -0
  2071. package/views/partials/accounts_header.hbs +6 -0
  2072. package/views/partials/add_account_modal.hbs +60 -0
  2073. package/views/partials/address_list.hbs +37 -0
  2074. package/views/partials/alerts.hbs +33 -0
  2075. package/views/partials/document_store_header.hbs +52 -0
  2076. package/views/partials/editor_scope_info.hbs +10 -0
  2077. package/views/partials/gateway_form.hbs +65 -0
  2078. package/views/partials/gateway_js.hbs +90 -0
  2079. package/views/partials/gateways_header.hbs +6 -0
  2080. package/views/partials/oauth_config_header.hbs +10 -0
  2081. package/views/partials/oauth_form.hbs +1204 -0
  2082. package/views/partials/scope_info.hbs +134 -0
  2083. package/views/partials/security_header.hbs +11 -0
  2084. package/views/partials/side_menu.hbs +114 -0
  2085. package/views/partials/template_form.hbs +121 -0
  2086. package/views/partials/templates_header.hbs +6 -0
  2087. package/views/partials/test_send.hbs +327 -0
  2088. package/views/partials/tokens_header.hbs +6 -0
  2089. package/views/partials/webhook_form.hbs +151 -0
  2090. package/views/partials/webhooks_editor_functions.hbs +372 -0
  2091. package/views/partials/webhooks_header.hbs +6 -0
  2092. package/views/redirect.hbs +1 -0
  2093. package/views/swagger/index.hbs +76 -0
  2094. package/views/templates/edit.hbs +87 -0
  2095. package/views/templates/index.hbs +208 -0
  2096. package/views/templates/new.hbs +85 -0
  2097. package/views/templates/template.hbs +423 -0
  2098. package/views/tokens/index.hbs +207 -0
  2099. package/views/tokens/new.hbs +230 -0
  2100. package/views/unsubscribe.hbs +93 -0
  2101. package/views/upgrade.hbs +56 -0
  2102. package/views/webhooks/edit.hbs +31 -0
  2103. package/views/webhooks/index.hbs +144 -0
  2104. package/views/webhooks/new.hbs +27 -0
  2105. package/views/webhooks/webhook.hbs +265 -0
  2106. package/winconf.js +93 -0
  2107. package/workers/api.js +8246 -1256
  2108. package/workers/documents.js +1120 -0
  2109. package/workers/imap-proxy.js +91 -0
  2110. package/workers/imap.js +552 -161
  2111. package/workers/smtp.js +355 -82
  2112. package/workers/submit.js +319 -54
  2113. package/workers/webhooks.js +542 -80
  2114. package/.eslintrc +0 -14
  2115. package/.github/FUNDING.yml +0 -4
  2116. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  2117. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  2118. package/LICENSE.txt +0 -661
  2119. package/examples/api.md +0 -137
  2120. package/lib/connection.js +0 -1769
  2121. package/lib/lua/z-push.lua +0 -14
  2122. package/lib/mailbox.js +0 -1546
  2123. package/license-report-config.json +0 -3
  2124. package/licenses.txt +0 -37
  2125. package/static/bootstrap-4.6.0-dist/css/bootstrap-grid.css.map +0 -1
  2126. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.css.map +0 -1
  2127. package/static/bootstrap-4.6.0-dist/css/bootstrap-reboot.min.css.map +0 -1
  2128. package/static/bootstrap-4.6.0-dist/css/bootstrap.css.map +0 -1
  2129. package/static/bootstrap-4.6.0-dist/css/bootstrap.min.css +0 -7
  2130. package/static/bootstrap-4.6.0-dist/css/bootstrap.min.css.map +0 -1
  2131. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.js +0 -7045
  2132. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.js.map +0 -1
  2133. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.min.js +0 -7
  2134. package/static/bootstrap-4.6.0-dist/js/bootstrap.bundle.min.js.map +0 -1
  2135. package/static/bootstrap-4.6.0-dist/js/bootstrap.js.map +0 -1
  2136. package/static/bootstrap-4.6.0-dist/js/bootstrap.min.js +0 -7
  2137. package/static/bootstrap-4.6.0-dist/js/bootstrap.min.js.map +0 -1
  2138. package/static/js/emailengine.js +0 -581
  2139. package/workers/arena.js +0 -89
@@ -0,0 +1,3677 @@
1
+ 'use strict';
2
+
3
+ const { parentPort, threadId: workerThreadId } = require('worker_threads');
4
+ const crypto = require('crypto');
5
+ const logger = require('../logger');
6
+ const { webhooks: Webhooks } = require('../webhooks');
7
+ const { getESClient } = require('../document-store');
8
+ const { getThread } = require('../threads');
9
+ const settings = require('../settings');
10
+ const msgpack = require('msgpack5')();
11
+ const { templates } = require('../templates');
12
+ const { Gateway } = require('../gateway');
13
+ const os = require('os');
14
+ const punycode = require('punycode.js');
15
+ const { inlineHtml, inlineText, htmlToText, textToHtml, mimeHtml } = require('@postalsys/email-text-tools');
16
+ const { randomUUID: uuid } = require('crypto');
17
+ const { addTrackers } = require('../add-trackers');
18
+ const { getRawEmail } = require('../get-raw-email');
19
+ const { getTemplate } = require('@postalsys/templates');
20
+ const { deepEqual } = require('assert');
21
+ const { arfDetect } = require('../arf-detect');
22
+ const simpleParser = require('mailparser').simpleParser;
23
+ const libmime = require('libmime');
24
+ const { bounceDetect } = require('../bounce-detect');
25
+ const ical = require('ical.js');
26
+ const { llmPreProcess } = require('../llm-pre-process');
27
+ const { oauth2Apps } = require('../oauth2-apps');
28
+ const { Account } = require('../account');
29
+ const util = require('util');
30
+ const socks = require('socks');
31
+ const nodemailer = require('nodemailer');
32
+ const { removeBcc } = require('../get-raw-email');
33
+ const { oauth2ProviderData } = require('../oauth2-apps');
34
+
35
+ // Import various utility functions and constants
36
+ const {
37
+ getLocalAddress,
38
+ getSignedFormDataSync,
39
+ getServiceSecret,
40
+ convertDataUrisToAttachments,
41
+ genBaseBoundary,
42
+ getDuration,
43
+ getByteSize,
44
+ readEnvValue,
45
+ emitChangeEvent,
46
+ filterEmptyObjectValues,
47
+ resolveCredentials,
48
+ getDateBuckets
49
+ } = require('../tools');
50
+
51
+ // Import application constants
52
+ const {
53
+ AUTH_ERROR_NOTIFY,
54
+ ACCOUNT_INITIALIZED_NOTIFY,
55
+ REDIS_PREFIX,
56
+ MESSAGE_NEW_NOTIFY,
57
+ MESSAGE_DELETED_NOTIFY,
58
+ MESSAGE_UPDATED_NOTIFY,
59
+ EMAIL_BOUNCE_NOTIFY,
60
+ MAILBOX_DELETED_NOTIFY,
61
+ DEFAULT_DELIVERY_ATTEMPTS,
62
+ MIME_BOUNDARY_PREFIX,
63
+ DEFAULT_DOWNLOAD_CHUNK_SIZE,
64
+ EMAIL_COMPLAINT_NOTIFY,
65
+ MAX_INLINE_ATTACHMENT_SIZE,
66
+ DEFAULT_MAX_IMAP_AUTH_FAILURE_TIME,
67
+ TLS_DEFAULTS,
68
+ EMAIL_DELIVERY_ERROR_NOTIFY,
69
+ EMAIL_SENT_NOTIFY
70
+ } = require('../consts');
71
+
72
+ // Configure download chunk size from environment or use default
73
+ const DOWNLOAD_CHUNK_SIZE = getByteSize(readEnvValue('EENGINE_CHUNK_SIZE')) || DEFAULT_DOWNLOAD_CHUNK_SIZE;
74
+
75
+ // Configure maximum time to wait before disabling IMAP on authentication failures
76
+ const MAX_IMAP_AUTH_FAILURE_TIME = getDuration(readEnvValue('EENGINE_MAX_IMAP_AUTH_FAILURE_TIME')) || DEFAULT_MAX_IMAP_AUTH_FAILURE_TIME;
77
+
78
+ /**
79
+ * Sends metrics data to the parent thread for aggregation
80
+ * @param {Object} meta - Metadata to include with the metric
81
+ * @param {Object} logger - Logger instance
82
+ * @param {string} key - Metric key identifier
83
+ * @param {string} method - Metric method (e.g., 'inc', 'dec')
84
+ * @param {...any} args - Additional arguments for the metric
85
+ */
86
+ async function metricsMeta(meta, logger, key, method, ...args) {
87
+ try {
88
+ parentPort.postMessage({
89
+ cmd: 'metrics',
90
+ key,
91
+ method,
92
+ args,
93
+ meta: meta || {}
94
+ });
95
+ } catch (err) {
96
+ logger.error({ msg: 'Failed to post metrics to parent', err });
97
+ }
98
+ }
99
+
100
+ // Map to track pending idempotency operations to prevent duplicate processing
101
+ const pendingIdempotencyOperations = new Map();
102
+
103
+ // Cache for SMTP connection pools to reuse connections
104
+ const SMTP_POOLS = new Map();
105
+ // Track last usage time for each pool to enable LRU-based cleanup
106
+ const SMTP_POOL_LAST_USED = new Map();
107
+
108
+ // Maximum idle time for SMTP pool connections (10 minutes of inactivity)
109
+ const SMTP_POOL_MAX_IDLE = 10 * 60 * 1000;
110
+ // Cleanup interval for idle SMTP pools (2 minutes)
111
+ const SMTP_POOL_CLEANUP_INTERVAL = 2 * 60 * 1000;
112
+
113
+ // Periodic cleanup of idle SMTP pool connections
114
+ let smtpPoolCleanupTimer = setInterval(() => {
115
+ const now = Date.now();
116
+ const idleKeys = [];
117
+
118
+ for (const [poolKey, lastUsed] of SMTP_POOL_LAST_USED.entries()) {
119
+ if (now - lastUsed > SMTP_POOL_MAX_IDLE) {
120
+ idleKeys.push(poolKey);
121
+ }
122
+ }
123
+
124
+ for (const poolKey of idleKeys) {
125
+ const transporter = SMTP_POOLS.get(poolKey);
126
+ if (transporter) {
127
+ // Check if the transporter has active connections
128
+ if (transporter._connectionPool && transporter._connectionPool.size > 0) {
129
+ // Still has active connections, update last used time
130
+ SMTP_POOL_LAST_USED.set(poolKey, now);
131
+ logger.trace({ msg: 'SMTP pool still has active connections, skipping cleanup', poolKey });
132
+ continue;
133
+ }
134
+
135
+ logger.debug({ msg: 'Cleaning up idle SMTP pool connection', poolKey, idleTime: now - SMTP_POOL_LAST_USED.get(poolKey) });
136
+ try {
137
+ transporter.close();
138
+ } catch (err) {
139
+ logger.error({ msg: 'Failed to close idle SMTP transporter', poolKey, err });
140
+ }
141
+ }
142
+ SMTP_POOLS.delete(poolKey);
143
+ SMTP_POOL_LAST_USED.delete(poolKey);
144
+ }
145
+
146
+ if (idleKeys.length > 0) {
147
+ logger.info({ msg: 'SMTP pool cleanup completed', cleaned: idleKeys.length, remaining: SMTP_POOLS.size });
148
+ }
149
+ }, SMTP_POOL_CLEANUP_INTERVAL);
150
+
151
+ // Prevent the timer from keeping the process alive
152
+ smtpPoolCleanupTimer.unref();
153
+
154
+ /**
155
+ * Gets or creates a reusable SMTP transport for the given configuration
156
+ * Connection pooling improves performance by reusing SMTP connections
157
+ * @param {Object} smtpSettings - SMTP configuration settings
158
+ * @returns {Object} Nodemailer transport instance
159
+ */
160
+ function getMailTransport(smtpSettings) {
161
+ // Extract only the settings that affect connection identity
162
+ let limitedSettings = {};
163
+ for (let key of ['name', 'localAddress', 'auth', 'host', 'port', 'secure', 'transactionLog', 'proxy']) {
164
+ limitedSettings[key] = smtpSettings[key];
165
+ }
166
+
167
+ // Create a unique key for this SMTP configuration
168
+ let serializedSettings = JSON.stringify(limitedSettings);
169
+ let poolKey = crypto.createHash('sha256').update(serializedSettings).digest('hex');
170
+
171
+ // Return existing transport if available
172
+ let transporter;
173
+ if (SMTP_POOLS.has(poolKey)) {
174
+ transporter = SMTP_POOLS.get(poolKey);
175
+ // Update last used time for LRU tracking
176
+ SMTP_POOL_LAST_USED.set(poolKey, Date.now());
177
+ return transporter;
178
+ }
179
+
180
+ // Configure connection pooling settings
181
+ smtpSettings.pool = true;
182
+ smtpSettings.maxConnections = 1;
183
+ smtpSettings.maxMessages = 100;
184
+ smtpSettings.socketTimeout = 2 * 60 * 1000; // 2 minute timeout
185
+
186
+ // Create new transport with pooling enabled
187
+ transporter = nodemailer.createTransport(smtpSettings);
188
+ transporter.set('proxy_socks_module', socks);
189
+
190
+ // Handle connection pool cleanup when idle
191
+ transporter.once('clear', () => {
192
+ // all emails processed and connection timed out
193
+ logger.trace({ msg: 'Clearing disconnected SMTP pool', poolKey });
194
+ SMTP_POOLS.delete(poolKey);
195
+ SMTP_POOL_LAST_USED.delete(poolKey);
196
+ try {
197
+ transporter.close();
198
+ } catch (closeErr) {
199
+ logger.error({ msg: 'Failed to close transporter', err: closeErr });
200
+ }
201
+ transporter = null;
202
+ });
203
+
204
+ // Handle transport errors by removing from pool
205
+ transporter.once('error', err => {
206
+ // not sure what happned, but do not re-use this transporter object anymore
207
+ logger.error({ msg: 'Transporter failed', err });
208
+ SMTP_POOLS.delete(poolKey);
209
+ SMTP_POOL_LAST_USED.delete(poolKey);
210
+ try {
211
+ transporter.close();
212
+ } catch (closeErr) {
213
+ logger.error({ msg: 'Failed to close transporter', err: closeErr });
214
+ }
215
+ transporter = null;
216
+ });
217
+
218
+ // Cache the transport for reuse
219
+ SMTP_POOLS.set(poolKey, transporter);
220
+ SMTP_POOL_LAST_USED.set(poolKey, Date.now());
221
+ logger.trace({ msg: 'Created SMTP pool', poolKey });
222
+
223
+ return transporter;
224
+ }
225
+
226
+ /**
227
+ * Check if an error is a transient error that should be retried
228
+ * @param {Error} err - The error to check
229
+ * @returns {boolean} True if the error is transient
230
+ */
231
+ function isTransientError(err) {
232
+ if (!err) {
233
+ return false;
234
+ }
235
+
236
+ // Check for specific OAuth2 request failures (like Gmail 500 errors)
237
+ if (err.oauthRequest) {
238
+ const status = err.oauthRequest.status;
239
+ // 500, 502, 503, 504 are transient server errors
240
+ if (status >= 500 && status < 600) {
241
+ return true;
242
+ }
243
+ }
244
+
245
+ // Check for network errors
246
+ if (err.code === 'ETIMEDOUT' || err.code === 'ECONNRESET' || err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN') {
247
+ return true;
248
+ }
249
+
250
+ // Check for specific error codes that indicate temporary issues
251
+ if (err.statusCode >= 500 && err.statusCode < 600) {
252
+ return true;
253
+ }
254
+
255
+ return false;
256
+ }
257
+
258
+ /**
259
+ * Retry a function with exponential backoff for transient errors
260
+ * @param {Function} fn - Async function to retry
261
+ * @param {Object} options - Retry options
262
+ * @returns {Promise} Result of the function
263
+ */
264
+ async function retryOnTransientError(fn, options = {}) {
265
+ const maxAttempts = options.maxAttempts || 3;
266
+ const baseDelay = options.baseDelay || 1000; // 1 second
267
+ const logger = options.logger;
268
+
269
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
270
+ try {
271
+ return await fn();
272
+ } catch (err) {
273
+ const isLastAttempt = attempt === maxAttempts;
274
+ const isTransient = isTransientError(err);
275
+
276
+ if (isTransient && !isLastAttempt) {
277
+ const delay = baseDelay * Math.pow(2, attempt - 1); // Exponential backoff
278
+ if (logger) {
279
+ logger.warn({
280
+ msg: 'Transient error occurred, retrying',
281
+ attempt,
282
+ maxAttempts,
283
+ delay,
284
+ err: err.message,
285
+ statusCode: err.statusCode || err.oauthRequest?.status
286
+ });
287
+ }
288
+ await new Promise(resolve => setTimeout(resolve, delay));
289
+ } else {
290
+ // Either non-transient error or last attempt - rethrow
291
+ throw err;
292
+ }
293
+ }
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Base class for email client implementations
299
+ * Provides common functionality for IMAP/SMTP operations
300
+ * Subclasses should implement protocol-specific behavior
301
+ */
302
+ class BaseClient {
303
+ constructor(account, options) {
304
+ this.account = account;
305
+ this.options = options || {};
306
+
307
+ // Generate unique connection ID for tracking
308
+ this.cid = this.getRandomId();
309
+
310
+ this.runIndex = this.options.runIndex;
311
+
312
+ // Core service dependencies
313
+ this.accountObject = this.options.accountObject;
314
+ this.accountLogger = this.options.accountLogger;
315
+ this.redis = this.options.redis;
316
+
317
+ // Message queue connections
318
+ this.notifyQueue = this.options.notifyQueue;
319
+ this.submitQueue = this.options.submitQueue;
320
+ this.documentsQueue = this.options.documentsQueue;
321
+ this.flowProducer = this.options.flowProducer;
322
+
323
+ // Inter-process communication handler
324
+ this.call = this.options.call;
325
+
326
+ this.logger = this.getLogger();
327
+
328
+ this.secret = this.options.secret;
329
+
330
+ // Track sub-connections (e.g., for IMAP IDLE)
331
+ this.subconnections = [];
332
+ }
333
+
334
+ // Stub methods to be implemented by subclasses
335
+
336
+ async init() {
337
+ return null;
338
+ }
339
+
340
+ close() {
341
+ return null;
342
+ }
343
+
344
+ async syncMailboxes() {
345
+ return null;
346
+ }
347
+
348
+ async delete() {
349
+ return null;
350
+ }
351
+
352
+ async resume() {
353
+ return null;
354
+ }
355
+
356
+ async reconnect() {
357
+ return null;
358
+ }
359
+
360
+ async subconnections() {
361
+ return [];
362
+ }
363
+
364
+ async getQuota() {
365
+ return false;
366
+ }
367
+
368
+ /**
369
+ * Generates a random 20-character ID for connection tracking
370
+ * @returns {string} Random ID string
371
+ */
372
+ getRandomId() {
373
+ let rid = BigInt('0x' + crypto.randomBytes(13).toString('hex')).toString(36);
374
+ if (rid.length < 20) {
375
+ rid = '0'.repeat(20 - rid.length) + rid;
376
+ } else if (rid.length > 20) {
377
+ rid = rid.substring(0, 20);
378
+ }
379
+ return rid;
380
+ }
381
+
382
+ // Redis key generators for different data types
383
+
384
+ getAccountKey() {
385
+ return `${REDIS_PREFIX}iad:${this.account}`;
386
+ }
387
+
388
+ getMailboxListKey() {
389
+ return `${REDIS_PREFIX}ial:${this.account}`;
390
+ }
391
+
392
+ getMailboxHashKey() {
393
+ return `${REDIS_PREFIX}iah:${this.account}`;
394
+ }
395
+
396
+ getSeenMessagesKey() {
397
+ return `${REDIS_PREFIX}iar:s:${this.account}`;
398
+ }
399
+
400
+ getLogKey() {
401
+ // this format ensures that the key is deleted when user is removed
402
+ return `${REDIS_PREFIX}iam:${this.account}:g`;
403
+ }
404
+
405
+ getLoggedAccountsKey() {
406
+ return `${REDIS_PREFIX}iaz:logged`;
407
+ }
408
+
409
+ async currentState() {
410
+ return 'connected';
411
+ }
412
+
413
+ /**
414
+ * Creates a logger instance that logs to both main logger and account logger
415
+ * @returns {Object} Synthetic logger that duplicates logs to account storage
416
+ */
417
+ getLogger() {
418
+ this.mainLogger =
419
+ this.options.logger ||
420
+ logger.child({
421
+ component: 'connection-client',
422
+ account: this.account,
423
+ cid: this.cid
424
+ });
425
+
426
+ let syntheticLogger = {};
427
+ let levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
428
+
429
+ // Create wrapper methods for each log level
430
+ for (let level of levels) {
431
+ syntheticLogger[level] = (...args) => {
432
+ // Log to main logger
433
+ this.mainLogger[level](...args);
434
+
435
+ // Also log to account-specific logger if enabled
436
+ if (this.accountLogger.enabled && args && args[0] && typeof args[0] === 'object') {
437
+ let entry = Object.assign({ level, t: Date.now(), cid: this.cid }, args[0]);
438
+
439
+ // Serialize error objects properly
440
+ if (entry.err && typeof entry.err === 'object') {
441
+ let err = entry.err;
442
+ entry.err = {
443
+ stack: err.stack
444
+ };
445
+ // enumerable error fields
446
+ Object.keys(err).forEach(key => {
447
+ entry.err[key] = err[key];
448
+ });
449
+ }
450
+
451
+ this.accountLogger.log(entry);
452
+ }
453
+ };
454
+ }
455
+
456
+ syntheticLogger.child = opts => this.mainLogger.child(opts);
457
+
458
+ return syntheticLogger;
459
+ }
460
+
461
+ /**
462
+ * Updates the connection state in Redis and tracks state changes
463
+ * Sends notification on first successful connection
464
+ */
465
+ async setStateVal() {
466
+ let [[e1], [e2], [e3, prevVal], [e4, incrVal], [e5, stateVal]] = await this.redis
467
+ .multi()
468
+ .hSetExists(this.getAccountKey(), 'state', this.state)
469
+ .hSetBigger(this.getAccountKey(), 'runIndex', this.runIndex.toString())
470
+ .hget(this.getAccountKey(), `state:count:${this.state}`)
471
+ .hIncrbyExists(this.getAccountKey(), `state:count:${this.state}`, 1)
472
+ .hget(this.getAccountKey(), 'state')
473
+ .exec();
474
+
475
+ if (e1 || e2 || e3 || e4 || e5) {
476
+ throw e1 || e2 || e3 || e4 || e5;
477
+ }
478
+
479
+ // Detect first successful connection
480
+ if (stateVal === 'connected' && incrVal === 1 && prevVal === '0') {
481
+ // first connected event!
482
+ await this.notify(false, ACCOUNT_INITIALIZED_NOTIFY, {
483
+ initialized: true
484
+ });
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Sets error state and manages error tracking/notifications
490
+ * Implements logic to disable IMAP after repeated authentication failures
491
+ * @param {string} event - Error event type
492
+ * @param {Object} data - Error details
493
+ * @returns {boolean} Whether this is the first occurrence of this error
494
+ */
495
+ async setErrorState(event, data) {
496
+ // Retrieve previous error state for comparison
497
+ let prevLastErrorState = await this.redis.hget(this.getAccountKey(), 'lastErrorState');
498
+ if (prevLastErrorState) {
499
+ try {
500
+ prevLastErrorState = JSON.parse(prevLastErrorState);
501
+ } catch (err) {
502
+ // ignore
503
+ }
504
+ }
505
+
506
+ this.state = event;
507
+ await this.setStateVal();
508
+
509
+ let isFirstOccurrence = true;
510
+
511
+ // Store current error state
512
+ await this.redis.hSetExists(this.getAccountKey(), 'lastErrorState', JSON.stringify(data));
513
+ await emitChangeEvent(this.logger, this.account, 'state', event, { error: data });
514
+
515
+ // Check if this is a repeat of the same error
516
+ if (data && Object.keys(data).length && prevLastErrorState) {
517
+ // we have an error object, let's see if the error hasn't changed
518
+
519
+ if (data.serverResponseCode && data.serverResponseCode === prevLastErrorState.serverResponseCode) {
520
+ // error code did not change, assume it is the same error
521
+ isFirstOccurrence = false;
522
+ } else {
523
+ try {
524
+ deepEqual(data, prevLastErrorState);
525
+ // nothing changed
526
+ isFirstOccurrence = false;
527
+ } catch (err) {
528
+ // seems different
529
+ }
530
+ }
531
+ }
532
+
533
+ // Track error occurrences
534
+ if (isFirstOccurrence) {
535
+ await this.redis
536
+ .multi()
537
+ .hSetExists(this.getAccountKey(), 'lastError:errorCount', 1)
538
+ .hSetExists(this.getAccountKey(), 'lastError:first', new Date().toISOString())
539
+ .exec();
540
+ } else {
541
+ let errorCount;
542
+ let firstError;
543
+
544
+ let [[err1, ec], [err2, fe]] = await this.redis
545
+ .multi()
546
+ .hIncrbyExists(this.getAccountKey(), `lastError:errorCount`, 1)
547
+ .hget(this.getAccountKey(), 'lastError:first')
548
+ .exec();
549
+
550
+ if (!err1 && !err2) {
551
+ errorCount = ec || 0;
552
+ if (fe) {
553
+ fe = new Date(fe);
554
+ if (fe.toString() !== 'Invalid Date') {
555
+ firstError = fe;
556
+ }
557
+ }
558
+ } else {
559
+ this.logger.error({ msg: 'Redis error while checking error state counters', err1, err2 });
560
+ }
561
+
562
+ // Handle repeated authentication errors by disabling IMAP
563
+ switch (event) {
564
+ case 'authenticationError':
565
+ if (errorCount > 0 && firstError && Date.now() - firstError.getTime() > MAX_IMAP_AUTH_FAILURE_TIME) {
566
+ // disable IMAP
567
+ let imapData;
568
+ let imapInfo = await this.redis.hget(this.getAccountKey(), 'imap');
569
+ if (imapInfo) {
570
+ try {
571
+ imapData = JSON.parse(imapInfo);
572
+ } catch (err) {
573
+ this.logger.error({ msg: 'Failed parsing IMAP data', err });
574
+ }
575
+ }
576
+ if (imapData && !imapData.disabled) {
577
+ imapData.disabled = true;
578
+
579
+ // Disable IMAP and reset error counters
580
+ await this.redis
581
+ .multi()
582
+ .hSetExists(this.getAccountKey(), 'imap', JSON.stringify(imapData))
583
+ .hdel(this.getAccountKey(), 'lastError:errorCount', 'lastError:first')
584
+ .hSetExists(
585
+ this.getAccountKey(),
586
+ 'lastErrorState',
587
+ JSON.stringify({
588
+ description: 'IMAP was disabled for the account due to exceeding the authentication error threshold',
589
+ response: data.response
590
+ })
591
+ )
592
+ .exec();
593
+
594
+ this.logger.info({
595
+ msg: 'IMAP was disabled for the account due to exceeding the authentication error threshold',
596
+ errorEvent: event,
597
+ firstError: firstError.toISOString(),
598
+ timeThreshold: MAX_IMAP_AUTH_FAILURE_TIME,
599
+ errorCount
600
+ });
601
+
602
+ // Close the connection after disabling
603
+ setImmediate(() => {
604
+ this.close();
605
+ });
606
+ }
607
+ }
608
+ break;
609
+ }
610
+ }
611
+
612
+ return isFirstOccurrence;
613
+ }
614
+
615
+ /**
616
+ * Sends notifications for various email events through webhooks and queues
617
+ * Handles special processing for certain event types and document store integration
618
+ * @param {Object} mailbox - Mailbox information
619
+ * @param {string} event - Event type constant
620
+ * @param {Object} data - Event data payload
621
+ * @param {Object} extraOpts - Additional options
622
+ */
623
+ async notify(mailbox, event, data, extraOpts) {
624
+ extraOpts = extraOpts || {};
625
+ const { skipWebhook, canSync = true } = extraOpts;
626
+
627
+ // Track event metrics
628
+ metricsMeta({ account: this.account }, this.logger, 'events', 'inc', {
629
+ event
630
+ });
631
+
632
+ // Handle error state tracking for connection/auth errors
633
+ switch (event) {
634
+ case 'connectError':
635
+ case 'authenticationError': {
636
+ let shouldNotify = await this.setErrorState(event, data);
637
+
638
+ if (!shouldNotify) {
639
+ // do not send a webhook as nothing really changed
640
+ return;
641
+ }
642
+ break;
643
+ }
644
+ }
645
+
646
+ let serviceUrl = (await settings.get('serviceUrl')) || null;
647
+
648
+ // Build notification payload
649
+ let payload = {
650
+ serviceUrl,
651
+ account: this.account,
652
+ date: new Date().toISOString()
653
+ };
654
+
655
+ let path = (mailbox && mailbox.path) || (data && data.path);
656
+ if (path) {
657
+ payload.path = path;
658
+ }
659
+
660
+ if (mailbox && mailbox.listingEntry && mailbox.listingEntry.specialUse) {
661
+ payload.specialUse = mailbox.listingEntry.specialUse;
662
+ }
663
+
664
+ if (event) {
665
+ payload.event = event;
666
+ }
667
+
668
+ if (data) {
669
+ payload.data = data;
670
+ }
671
+
672
+ let queueKeep = (await settings.get('queueKeep')) || true;
673
+
674
+ // Determine if we need to sync with document store (ElasticSearch)
675
+ let addDocumentQueueJob =
676
+ canSync &&
677
+ this.documentsQueue &&
678
+ [MESSAGE_NEW_NOTIFY, MESSAGE_DELETED_NOTIFY, MESSAGE_UPDATED_NOTIFY, EMAIL_BOUNCE_NOTIFY, MAILBOX_DELETED_NOTIFY].includes(event) &&
679
+ (await settings.get('documentStoreEnabled'));
680
+
681
+ // Generate thread ID for new messages if needed
682
+ if (addDocumentQueueJob && payload.data && event === MESSAGE_NEW_NOTIFY && !payload.data.threadId) {
683
+ // Generate a thread ID for the email. This is also stored in ElasticSearch.
684
+ const { index, client } = await getESClient(logger);
685
+ try {
686
+ if (client) {
687
+ let thread = await getThread(client, index, this.account, payload.data, logger);
688
+ if (thread) {
689
+ payload.data.threadId = thread;
690
+ logger.info({
691
+ msg: 'Provisioned thread ID for a message',
692
+ account: this.account,
693
+ message: payload.data.id,
694
+ threadId: payload.data.threadId
695
+ });
696
+ }
697
+ }
698
+ } catch (err) {
699
+ if (logger.notifyError) {
700
+ logger.notifyError(err, event => {
701
+ event.setUser(this.account);
702
+ event.addMetadata('ee', {
703
+ index
704
+ });
705
+ });
706
+ }
707
+ logger.error({ msg: 'Failed to resolve thread', account: this.account, message: payload.data.id, err });
708
+ }
709
+ }
710
+
711
+ // Configure job options for queuing
712
+ const defaultJobOptions = {
713
+ removeOnComplete: queueKeep,
714
+ removeOnFail: queueKeep,
715
+ attempts: 10,
716
+ backoff: {
717
+ type: 'exponential',
718
+ delay: 5000
719
+ }
720
+ };
721
+
722
+ // use more attempts for ElasticSearch updates
723
+ const documentJobOptions = Object.assign(structuredClone(defaultJobOptions), { attempts: 16 });
724
+
725
+ // Process notifications with flow (webhook + document store) or separately
726
+ if (!skipWebhook && addDocumentQueueJob) {
727
+ // add both jobs as a Flow
728
+
729
+ let notifyPayload = await Webhooks.formatPayload(event, payload);
730
+
731
+ const queueFlow = [
732
+ {
733
+ name: event,
734
+ data: payload,
735
+ queueName: 'documents'
736
+ }
737
+ ];
738
+
739
+ await Webhooks.pushToQueue(event, notifyPayload, {
740
+ routesOnly: true,
741
+ queueFlow
742
+ });
743
+
744
+ await this.flowProducer.add(
745
+ {
746
+ name: event,
747
+ data: notifyPayload,
748
+ queueName: 'notify',
749
+ children: queueFlow
750
+ },
751
+ {
752
+ queuesOptions: {
753
+ notify: {
754
+ defaultJobOptions
755
+ },
756
+ documents: {
757
+ defaultJobOptions: documentJobOptions
758
+ }
759
+ }
760
+ }
761
+ );
762
+ } else {
763
+ // add to queues as normal jobs
764
+
765
+ if (!skipWebhook) {
766
+ await Webhooks.pushToQueue(event, await Webhooks.formatPayload(event, payload));
767
+ }
768
+
769
+ if (addDocumentQueueJob) {
770
+ await this.documentsQueue.add(event, payload, documentJobOptions);
771
+ }
772
+ }
773
+ }
774
+
775
+ /**
776
+ * Loads OAuth2 credentials for login, handling token refresh if needed
777
+ * @param {Object} accountObject - Account instance
778
+ * @param {Object} accountData - Account configuration data
779
+ * @param {Object} ctx - Context object (usually 'this')
780
+ * @param {string} target - Target service ('imap' or 'smtp')
781
+ * @returns {Object} OAuth2 credentials including access token
782
+ */
783
+ async loadOAuth2LoginCredentials(accountObject, accountData, ctx, target) {
784
+ const now = Date.now();
785
+ const oauth2User = accountData.oauth2.auth.delegatedUser || accountData.oauth2.auth.user;
786
+ let accessToken;
787
+
788
+ // Load OAuth2 app configuration
789
+ const oauth2App = await oauth2Apps.get(accountData.oauth2.provider);
790
+ if (!oauth2App) {
791
+ let error = new Error('Missing or disabled OAuth2 app');
792
+ error.code = 'AppNotFound';
793
+ throw error;
794
+ }
795
+
796
+ // Verify app is configured for IMAP
797
+ if (oauth2App.baseScopes && oauth2App.baseScopes !== 'imap') {
798
+ let error = new Error('Invalid base scopes for references OAuth2 application');
799
+ error.code = 'InvalidBaseScopes';
800
+ throw error;
801
+ }
802
+
803
+ // Check if token needs refresh (with 30 second buffer)
804
+ if (!accountData.oauth2.accessToken || !accountData.oauth2.expires || accountData.oauth2.expires < new Date(now - 30 * 1000)) {
805
+ // renew access token
806
+ try {
807
+ accountData = await accountObject.renewAccessToken({
808
+ logger: this.logger,
809
+ logRaw: this.options.logRaw
810
+ });
811
+ accessToken = accountData.oauth2.accessToken;
812
+ } catch (err) {
813
+ err.authenticationFailed = true;
814
+ let notifyData = {
815
+ response: err.message,
816
+ serverResponseCode: 'OauthRenewError'
817
+ };
818
+ if (err.tokenRequest) {
819
+ notifyData.tokenRequest = err.tokenRequest;
820
+ }
821
+ await ctx.notify(false, AUTH_ERROR_NOTIFY, notifyData);
822
+ ctx.logger.error({
823
+ account: accountObject.account,
824
+ err
825
+ });
826
+ ctx.state = 'authenticationError';
827
+ throw err;
828
+ }
829
+ } else {
830
+ // Use cached token
831
+ this.logger.info({
832
+ msg: 'Using cached OAuth2 access token',
833
+ action: 'ensureAccessToken',
834
+ target,
835
+ error: null,
836
+ user: accountData.oauth2.auth.user,
837
+ expires: accountData.oauth2.expires,
838
+ scopes: accountData.oauth2.scope,
839
+ oauth2App: accountData.oauth2.provider
840
+ });
841
+ accessToken = accountData.oauth2.accessToken;
842
+ }
843
+ return { oauth2User, accessToken, oauth2App };
844
+ }
845
+
846
+ /**
847
+ * Resolves delegated account chain for OAuth2 authentication
848
+ * Follows delegation chain up to 20 hops, detecting loops
849
+ * @param {Object} accountData - Initial account data
850
+ * @returns {Object} Final delegated account data
851
+ */
852
+ async getDelegatedAccount(accountData) {
853
+ let redirect = 0;
854
+ let providerAccountData = accountData;
855
+ let delegatedAccountData;
856
+ let seenAccounts = new Set();
857
+ let hopsAllowed = 20;
858
+
859
+ // Follow delegation chain
860
+ while (redirect++ < hopsAllowed) {
861
+ // Load delegated account if not cached
862
+ if (!this.delegatedAccountObject || this.delegatedAccountObject.account !== providerAccountData.oauth2.auth.delegatedAccount) {
863
+ this.delegatedAccountObject = new Account({
864
+ account: accountData.oauth2.auth.delegatedAccount,
865
+ redis: this.accountObject.redis,
866
+ call: this.accountObject.call,
867
+ secret: this.accountObject.secret,
868
+ timeout: this.accountObject.timeout
869
+ });
870
+ }
871
+ delegatedAccountData = await this.delegatedAccountObject.loadAccountData();
872
+
873
+ // Check if this account also delegates to another
874
+ if (delegatedAccountData.oauth2.auth.delegatedUser && delegatedAccountData.oauth2.auth.delegatedAccount) {
875
+ // Detect delegation loops
876
+ if (seenAccounts.has(providerAccountData.account)) {
877
+ // loop detected
878
+ let error = new Error('Delegation looping detected');
879
+ throw error;
880
+ }
881
+ seenAccounts.add(providerAccountData.account);
882
+ providerAccountData = delegatedAccountData;
883
+ continue;
884
+ }
885
+ break;
886
+ }
887
+
888
+ if (redirect >= hopsAllowed) {
889
+ let error = new Error('Too many delegation hops');
890
+ throw error;
891
+ }
892
+
893
+ return delegatedAccountData;
894
+ }
895
+
896
+ /**
897
+ * Loads OAuth2 credentials handling delegated accounts
898
+ * @param {Object} accountData - Account configuration
899
+ * @param {Object} ctx - Context object
900
+ * @param {string} target - Target service ('imap' or 'smtp')
901
+ * @returns {Object} OAuth2 credentials
902
+ */
903
+ async loadOAuth2AccountCredentials(accountData, ctx, target) {
904
+ let oauthCredentials;
905
+
906
+ // Handle delegated accounts (shared mailboxes)
907
+ if (accountData.oauth2.auth.delegatedUser && accountData.oauth2.auth.delegatedAccount) {
908
+ const delegatedAccountData = await this.getDelegatedAccount(accountData);
909
+ oauthCredentials = await this.loadOAuth2LoginCredentials(this.delegatedAccountObject, delegatedAccountData, ctx, target);
910
+ if (target !== 'smtp') {
911
+ // Use delegated user for IMAP
912
+ oauthCredentials.oauth2User = accountData.oauth2.auth.delegatedUser || oauthCredentials.oauth2User;
913
+ }
914
+ } else {
915
+ // Direct OAuth2 authentication
916
+ oauthCredentials = await this.loadOAuth2LoginCredentials(this.accountObject, accountData, ctx, target);
917
+ if (accountData.oauth2.auth.delegatedUser && target === 'smtp') {
918
+ // override SMTP username, do not use the shared user
919
+ oauthCredentials.oauth2User = accountData.oauth2.auth.user;
920
+ }
921
+ }
922
+
923
+ return oauthCredentials;
924
+ }
925
+
926
+ /**
927
+ * Checks and manages idempotency for operations to prevent duplicates
928
+ * Uses Redis to track operation status across workers
929
+ * @param {string} objName - Object type name for namespacing
930
+ * @param {string} idempotencyKey - Unique key for the operation
931
+ * @returns {Object|null} Idempotency data if exists
932
+ */
933
+ async checkIdempotencyKey(objName, idempotencyKey) {
934
+ if (!idempotencyKey) {
935
+ return null;
936
+ }
937
+
938
+ const idempotencyKeyName = objName ? `${objName}/${idempotencyKey}` : idempotencyKey;
939
+
940
+ // check last 24-48 hours, so probably will return 2 keys, at rare cases 1
941
+ const { bucketKeys } = getDateBuckets(1 * 24 * 3600);
942
+
943
+ // Use custom Redis command to check idempotency atomically
944
+ let idempotencyResultStr = await this.redis.eeGetIdempotency(
945
+ `${REDIS_PREFIX}idempotency:bucket:`,
946
+ idempotencyKeyName,
947
+ this.runIndex,
948
+ workerThreadId,
949
+ bucketKeys.join(',')
950
+ );
951
+
952
+ let idempotencyData;
953
+ try {
954
+ idempotencyData = JSON.parse(idempotencyResultStr);
955
+ idempotencyData.idempotencyKey = idempotencyKey;
956
+ idempotencyData.idempotencyKeyName = idempotencyKeyName;
957
+ } catch (err) {
958
+ this.logger.error({ msg: 'Failed to parse idempotency data', idempotencyKey, cachedValue: idempotencyResultStr, err });
959
+ }
960
+
961
+ // Track pending operations to handle concurrent requests
962
+ if (idempotencyData?.status === 'new') {
963
+ if (pendingIdempotencyOperations.has(idempotencyKeyName)) {
964
+ let error = new Error('Cancelling pending operation');
965
+ for (let promise of pendingIdempotencyOperations.get(idempotencyKeyName)) {
966
+ promise.reject(error);
967
+ }
968
+ pendingIdempotencyOperations.delete(idempotencyKeyName);
969
+ }
970
+ pendingIdempotencyOperations.set(idempotencyKeyName, []);
971
+ }
972
+
973
+ // use existing response
974
+ switch (idempotencyData.status) {
975
+ case 'completed':
976
+ // Return cached result
977
+ idempotencyData.returnValue = Object.assign({}, idempotencyData.result, {
978
+ idempotency: { key: idempotencyData.idempotencyKey, status: 'HIT' }
979
+ });
980
+ break;
981
+ case 'pending': {
982
+ // Wait for operation to complete
983
+ let queueResult = await new Promise((resolve, reject) => {
984
+ if (!pendingIdempotencyOperations.has(idempotencyData.idempotencyKeyName)) {
985
+ pendingIdempotencyOperations.set(idempotencyData.idempotencyKeyName, []);
986
+ }
987
+ pendingIdempotencyOperations.get(idempotencyData.idempotencyKeyName).push({ resolve, reject });
988
+ });
989
+ idempotencyData.returnValue = Object.assign({}, queueResult, { idempotency: { key: idempotencyData.idempotencyKey, status: 'HIT' } });
990
+ break;
991
+ }
992
+ }
993
+
994
+ return idempotencyData || null;
995
+ }
996
+
997
+ /**
998
+ * Updates idempotency cache with operation result
999
+ * @param {Object} idempotencyData - Idempotency tracking data
1000
+ * @param {Object} result - Operation result to cache
1001
+ */
1002
+ async updateIdempotencyData(idempotencyData, result) {
1003
+ if (idempotencyData?.bucketKey && idempotencyData?.idempotencyKeyName) {
1004
+ // update status and result
1005
+ try {
1006
+ await this.redis.hset(
1007
+ idempotencyData?.bucketKey,
1008
+ idempotencyData.idempotencyKeyName,
1009
+ JSON.stringify({
1010
+ status: 'completed',
1011
+ runIndex: idempotencyData.runIndex,
1012
+ threadId: idempotencyData.threadId,
1013
+ result
1014
+ })
1015
+ );
1016
+ } catch (err) {
1017
+ this.logger.error({
1018
+ msg: 'Failed to update idempotency data',
1019
+ idempotencyKey: idempotencyData.idempotencyKey,
1020
+ err
1021
+ });
1022
+ }
1023
+
1024
+ // Resolve any waiting promises
1025
+ for (let promise of pendingIdempotencyOperations.get(idempotencyData.idempotencyKeyName)) {
1026
+ promise.resolve(result);
1027
+ }
1028
+ pendingIdempotencyOperations.delete(idempotencyData.idempotencyKeyName);
1029
+ }
1030
+ }
1031
+
1032
+ /**
1033
+ * Clears idempotency data on operation failure
1034
+ * @param {Object} idempotencyData - Idempotency tracking data
1035
+ * @param {Error} error - Error that caused the failure
1036
+ */
1037
+ async clearIdempotencyData(idempotencyData, error) {
1038
+ if (idempotencyData?.status === 'new' && idempotencyData?.bucketKey && idempotencyData?.idempotencyKeyName) {
1039
+ // delete failed attempt information
1040
+ try {
1041
+ await this.redis.hdel(idempotencyData?.bucketKey, idempotencyData.idempotencyKeyName);
1042
+ } catch (err) {
1043
+ this.logger.error({
1044
+ msg: 'Failed to clear idempotency data',
1045
+ idempotencyKey: idempotencyData.idempotencyKey,
1046
+ err
1047
+ });
1048
+ }
1049
+
1050
+ // Reject any waiting promises
1051
+ if (pendingIdempotencyOperations.has(idempotencyData.idempotencyKeyName)) {
1052
+ for (let promise of pendingIdempotencyOperations.get(idempotencyData.idempotencyKeyName)) {
1053
+ promise.reject(error);
1054
+ }
1055
+ pendingIdempotencyOperations.delete(idempotencyData.idempotencyKeyName);
1056
+ }
1057
+ }
1058
+ }
1059
+
1060
+ /**
1061
+ * Queues a message for sending with idempotency support
1062
+ * @param {Object} data - Message data
1063
+ * @param {Object} meta - Metadata
1064
+ * @param {Object} connectionOptions - Connection options
1065
+ * @returns {Object} Queue result
1066
+ */
1067
+ async queueMessage(data, meta, connectionOptions) {
1068
+ let idempotencyData;
1069
+
1070
+ // Check for duplicate operations
1071
+ if (meta.idempotencyKey) {
1072
+ idempotencyData = await this.checkIdempotencyKey(`mq/${this.account}`, meta.idempotencyKey);
1073
+ if (idempotencyData?.returnValue) {
1074
+ return idempotencyData?.returnValue;
1075
+ }
1076
+ }
1077
+
1078
+ let queueResult;
1079
+ try {
1080
+ queueResult = await this.queueMessageHandler(data, meta, connectionOptions);
1081
+ await this.updateIdempotencyData(idempotencyData, queueResult);
1082
+ } catch (err) {
1083
+ await this.clearIdempotencyData(idempotencyData, err);
1084
+ throw err;
1085
+ }
1086
+
1087
+ if (idempotencyData?.status === 'new') {
1088
+ return Object.assign({}, queueResult, { idempotency: { key: idempotencyData.idempotencyKey, status: 'MISS' } });
1089
+ }
1090
+
1091
+ return queueResult;
1092
+ }
1093
+
1094
+ /**
1095
+ * Main handler for queuing messages for delivery
1096
+ * Processes templates, handles mail merge, and schedules delivery
1097
+ * @param {Object} data - Message data
1098
+ * @param {Object} meta - Metadata
1099
+ * @param {Object} connectionOptions - Connection options
1100
+ * @returns {Object} Queue result or mail merge results
1101
+ */
1102
+ async queueMessageHandler(data, meta, connectionOptions) {
1103
+ let accountData = await this.accountObject.loadAccountData();
1104
+
1105
+ // Load gateway configuration if specified
1106
+ let gatewayData;
1107
+ let gatewayObject;
1108
+ if (data.gateway) {
1109
+ gatewayObject = new Gateway({ gateway: data.gateway, redis: this.redis, secret: this.secret });
1110
+ try {
1111
+ gatewayData = await gatewayObject.loadGatewayData();
1112
+ } catch (err) {
1113
+ this.logger.info({ msg: 'Failed to load gateway data', messageId: data.messageId, gateway: data.gateway, err });
1114
+ }
1115
+ }
1116
+
1117
+ // Verify SMTP capability
1118
+ if (!accountData.smtp && !accountData.oauth2 && !gatewayData) {
1119
+ // can not make connection
1120
+ let err = new Error('SMTP configuration not found');
1121
+ err.code = 'SMTPUnavailable';
1122
+ err.statusCode = 404;
1123
+ throw err;
1124
+ }
1125
+
1126
+ let licenseInfo = await this.call({ cmd: 'license' });
1127
+
1128
+ // Configure message defaults
1129
+ data.disableFileAccess = true;
1130
+ data.disableUrlAccess = true;
1131
+ data.boundaryPrefix = MIME_BOUNDARY_PREFIX;
1132
+ data.baseBoundary = genBaseBoundary();
1133
+
1134
+ // convert data uri images to attachments
1135
+ convertDataUrisToAttachments(data);
1136
+
1137
+ // Process template if specified
1138
+ if (data.template) {
1139
+ let templateData = await templates.get(data.template);
1140
+ if (!templateData || (templateData.account && templateData.account !== accountData.account)) {
1141
+ let err = new Error(`Requested template was not found [${data.template}]`);
1142
+ err.code = 'TemplateNotFound';
1143
+ err.statusCode = 404;
1144
+ throw err;
1145
+ }
1146
+
1147
+ if (templateData.content && templateData.content.html && templateData.format) {
1148
+ data.render = data.render || {};
1149
+ data.render.format = templateData.format;
1150
+ }
1151
+
1152
+ // Merge template content into message
1153
+ for (let key of Object.keys(templateData.content || {})) {
1154
+ data[key] = templateData.content[key];
1155
+ }
1156
+
1157
+ delete data.template;
1158
+ }
1159
+
1160
+ // Handle single message or mail merge
1161
+ if (!data.mailMerge || !data.mailMerge.length) {
1162
+ return this.queueMessageEntry(data, meta, licenseInfo, connectionOptions);
1163
+ }
1164
+
1165
+ // Process mail merge batch
1166
+ let mailMergeList = data.mailMerge;
1167
+ delete data.mailMerge;
1168
+ delete data.messageId;
1169
+ delete data.to;
1170
+
1171
+ let messageProcessors = [];
1172
+
1173
+ // Create individual messages for each recipient
1174
+ for (let mailMergeEntry of mailMergeList) {
1175
+ let messageCopy = structuredClone(data);
1176
+ if (messageCopy.sendAt) {
1177
+ // date values do not survive JSON based copying
1178
+ messageCopy.sendAt = new Date(messageCopy.sendAt);
1179
+ }
1180
+
1181
+ messageCopy.to = [mailMergeEntry.to];
1182
+
1183
+ // Apply recipient-specific overrides
1184
+ for (let key of ['messageId', 'sendAt']) {
1185
+ if (mailMergeEntry[key]) {
1186
+ messageCopy[key] = mailMergeEntry[key];
1187
+ }
1188
+ }
1189
+
1190
+ // Apply recipient-specific template parameters
1191
+ if (mailMergeEntry.params) {
1192
+ messageCopy.render = messageCopy.render || {};
1193
+ messageCopy.render.params = mailMergeEntry.params;
1194
+ }
1195
+
1196
+ messageProcessors.push(this.queueMessageEntry(messageCopy, meta, licenseInfo, connectionOptions));
1197
+ }
1198
+
1199
+ let response = {
1200
+ mailMerge: []
1201
+ };
1202
+
1203
+ // Process all messages in parallel
1204
+ let results = await Promise.allSettled(messageProcessors);
1205
+ for (let i = 0; i < mailMergeList.length; i++) {
1206
+ let mailMergeEntry = mailMergeList[i];
1207
+ let resultEntry = results[i];
1208
+
1209
+ let result = Object.assign(
1210
+ {
1211
+ success: resultEntry.status === 'fulfilled',
1212
+ to: mailMergeEntry.to
1213
+ },
1214
+ resultEntry.status === 'fulfilled'
1215
+ ? resultEntry.value.responseValue || {
1216
+ messageId: resultEntry.value.messageId,
1217
+ queueId: resultEntry.value.queueId,
1218
+ sendAt: resultEntry.value.sendAt
1219
+ }
1220
+ : {
1221
+ error: (resultEntry.reason && resultEntry.reason.message) || resultEntry.status,
1222
+ code: (resultEntry.reason && resultEntry.reason.code) || 'SubmitFail',
1223
+ statusCode: (resultEntry.reason && Number(resultEntry.reason.statusCode)) || null
1224
+ }
1225
+ );
1226
+
1227
+ response.mailMerge.push(result);
1228
+ }
1229
+
1230
+ return response;
1231
+ }
1232
+
1233
+ // placeholder - to be implemented by subclasses
1234
+ async checkIMAPConnection() {
1235
+ return true;
1236
+ }
1237
+
1238
+ /**
1239
+ * Prepares raw email message for sending/storing
1240
+ * Handles references, inline content, and attachments
1241
+ * @param {Object} data - Message data
1242
+ * @param {Object} options - Processing options
1243
+ * @param {Object} connectionOptions - Connection options
1244
+ * @returns {Object} Raw message and metadata
1245
+ */
1246
+ async prepareRawMessage(data, options, connectionOptions) {
1247
+ options = options || {};
1248
+
1249
+ // Configure message defaults
1250
+ data.disableFileAccess = true;
1251
+ data.disableUrlAccess = true;
1252
+ data.boundaryPrefix = MIME_BOUNDARY_PREFIX;
1253
+ data.baseBoundary = genBaseBoundary();
1254
+
1255
+ // convert data uri images to attachments
1256
+ convertDataUrisToAttachments(data);
1257
+
1258
+ let accountData = await this.accountObject.loadAccountData();
1259
+
1260
+ // Check if send-only account is trying to use reference.action
1261
+ if (data.reference && data.reference.message && data.reference.action) {
1262
+ let app;
1263
+ if (accountData.oauth2 && accountData.oauth2.provider) {
1264
+ try {
1265
+ app = await this.accountObject.getOauth2App();
1266
+ } catch (err) {
1267
+ // ignore
1268
+ }
1269
+ }
1270
+
1271
+ if (this.accountObject.isSendOnlyAccount(accountData, app)) {
1272
+ let err = new Error('Reference actions (reply, forward) are not supported for send-only accounts');
1273
+ err.code = 'ReferenceNotSupported';
1274
+ err.statusCode = 400;
1275
+ throw err;
1276
+ }
1277
+ }
1278
+
1279
+ // Set default from address for reference actions
1280
+ if (!data.from && data.reference?.action) {
1281
+ data.from = {
1282
+ name: accountData.name,
1283
+ address: accountData.email
1284
+ };
1285
+ }
1286
+
1287
+ // Configure locale and timezone for inline content
1288
+ let inlineOptions = {
1289
+ locale: data.locale || accountData.locale || (await settings.get('locale')),
1290
+ tz: data.tz || accountData.tz || (await settings.get('timezone'))
1291
+ };
1292
+
1293
+ delete data.locale;
1294
+ delete data.tz;
1295
+
1296
+ let referencedMessage;
1297
+ let documentStoreUsed = false;
1298
+
1299
+ // Resolve reference and update reference/in-reply-to headers
1300
+ if (data.reference && data.reference.message) {
1301
+ // Try document store first if enabled
1302
+ if (data.reference.documentStore && (await settings.get('documentStoreEnabled'))) {
1303
+ try {
1304
+ referencedMessage = await this.accountObject.getMessage(data.reference.message, {
1305
+ documentStore: true,
1306
+ textType: '*'
1307
+ });
1308
+ } catch (err) {
1309
+ if (err.meta && err.meta.statusCode === 404) {
1310
+ // not found
1311
+ } else {
1312
+ let error = new Error('ElasticSearch request failed');
1313
+ error.info = {
1314
+ response: (err.meta && err.meta.body) || err.message,
1315
+ statusCode: err.meta && err.meta.statusCode
1316
+ };
1317
+ error.code = 'ESRequestError';
1318
+ error.statusCode = (err.meta && err.meta.statusCode) || 500;
1319
+ throw error;
1320
+ }
1321
+ }
1322
+ documentStoreUsed = true;
1323
+ } else {
1324
+ // Fetch from IMAP/API with retry logic for transient errors
1325
+ let extendedData = data.reference.inline || data.reference.forwardAttachments;
1326
+
1327
+ try {
1328
+ referencedMessage = await retryOnTransientError(
1329
+ async () => {
1330
+ return await this.getMessage(
1331
+ data.reference.message,
1332
+ {
1333
+ fields: !extendedData
1334
+ ? {
1335
+ uid: true,
1336
+ flags: true,
1337
+ envelope: true,
1338
+ headers: ['references']
1339
+ }
1340
+ : false,
1341
+ header: extendedData ? true : false,
1342
+ textType: extendedData ? '*' : false
1343
+ },
1344
+ connectionOptions
1345
+ );
1346
+ },
1347
+ {
1348
+ maxAttempts: 3,
1349
+ baseDelay: 1000,
1350
+ logger: this.logger
1351
+ }
1352
+ );
1353
+ } catch (err) {
1354
+ // Check if this is a transient error that exhausted retries
1355
+ const isTransient = isTransientError(err);
1356
+
1357
+ if (isTransient && data.reference.ignoreMissing) {
1358
+ // Transient error but ignoreMissing is true - log and continue without reference
1359
+ this.logger.warn({
1360
+ msg: 'Failed to fetch referenced message due to transient error, continuing without reference',
1361
+ messageId: data.reference.message,
1362
+ err: err.message,
1363
+ statusCode: err.statusCode || (err.oauthRequest && err.oauthRequest.status)
1364
+ });
1365
+ referencedMessage = null;
1366
+ } else if (!isTransient && err.statusCode === 404) {
1367
+ // Permanent 404 error - respect ignoreMissing
1368
+ if (!data.reference.ignoreMissing) {
1369
+ let error = new Error('Referenced message was not found');
1370
+ error.code = 'MessageNotFound';
1371
+ error.statusCode = 404;
1372
+ throw error;
1373
+ }
1374
+ referencedMessage = null;
1375
+ } else {
1376
+ // Either transient error with ignoreMissing=false, or other permanent error
1377
+ throw err;
1378
+ }
1379
+ }
1380
+ }
1381
+
1382
+ if (!referencedMessage && !data.reference.ignoreMissing) {
1383
+ let err = new Error('Referenced message was not found');
1384
+ err.code = 'MessageNotFound';
1385
+ err.statusCode = 404;
1386
+ throw err;
1387
+ }
1388
+
1389
+ if (referencedMessage) {
1390
+ // Verify Message-ID if specified
1391
+ if (data.reference.messageId && data.reference.messageId !== referencedMessage.messageId) {
1392
+ let err = new Error('The referenced message was found, but its Message-ID does not match the expected value');
1393
+ err.code = 'MessageNotFound';
1394
+ err.statusCode = 404;
1395
+ throw err;
1396
+ }
1397
+
1398
+ // Build references header chain
1399
+ let references = []
1400
+ .concat(referencedMessage.messageId || [])
1401
+ .concat(referencedMessage.inReplyTo || [])
1402
+ .concat((referencedMessage.headers && referencedMessage.headers.references) || [])
1403
+ .flatMap(line => line.split(/\s+/))
1404
+ .map(ref => ref.trim())
1405
+ .filter(ref => ref)
1406
+ .map(ref => {
1407
+ // Ensure proper Message-ID format
1408
+ if (!/^</.test(ref)) {
1409
+ ref = '<' + ref;
1410
+ }
1411
+ if (!/>$/.test(ref)) {
1412
+ ref = ref + '>';
1413
+ }
1414
+ return ref;
1415
+ });
1416
+
1417
+ references = Array.from(new Set(references));
1418
+ if (references.length) {
1419
+ if (!data.headers) {
1420
+ data.headers = {};
1421
+ }
1422
+ data.headers.references = references.join(' ');
1423
+ }
1424
+
1425
+ // Set In-Reply-To header for replies
1426
+ if (['reply', 'reply-all'].includes(data.reference.action) && referencedMessage.messageId) {
1427
+ data.headers['in-reply-to'] = referencedMessage.messageId;
1428
+ }
1429
+
1430
+ // Generate subject with appropriate prefix
1431
+ if (!data.subject && referencedMessage.subject) {
1432
+ let subject = referencedMessage.subject;
1433
+ let prefix;
1434
+ switch (data.reference.action) {
1435
+ case 'reply':
1436
+ case 'reply-all':
1437
+ if (!/^Re:/i.test(subject)) {
1438
+ prefix = 'Re';
1439
+ }
1440
+ break;
1441
+ case 'forward':
1442
+ if (!/^Fwd:/i.test(subject)) {
1443
+ prefix = 'Fwd';
1444
+ }
1445
+ break;
1446
+ }
1447
+ data.subject = `${prefix ? prefix + ': ' : ''}${subject}`;
1448
+ }
1449
+
1450
+ let cidAttachments = [];
1451
+
1452
+ // Process inline content for replies/forwards
1453
+ if (data.reference.inline) {
1454
+ let inlineMessageData = {
1455
+ text: referencedMessage.text && referencedMessage.text.plain,
1456
+ html: referencedMessage.text && referencedMessage.text.html
1457
+ };
1458
+
1459
+ for (let key of ['from', 'to', 'cc', 'bcc', 'date', 'subject']) {
1460
+ inlineMessageData[key] = referencedMessage[key];
1461
+ }
1462
+
1463
+ if (inlineMessageData.html) {
1464
+ // Find inline attachments referenced in HTML
1465
+ if (referencedMessage.attachments) {
1466
+ // find all attachments that are referenced in the HTML
1467
+ for (let attachment of referencedMessage.attachments) {
1468
+ if (attachment.contentId && inlineMessageData.html.indexOf(`cid:${attachment.contentId.replace(/^<|>$/g, '')}`) >= 0) {
1469
+ cidAttachments.push(attachment);
1470
+ }
1471
+ }
1472
+ }
1473
+
1474
+ try {
1475
+ let html = data.html;
1476
+ if (!(html || '').toString().trim() && data.text) {
1477
+ html = textToHtml(data.text); // convert text to html
1478
+ }
1479
+
1480
+ data.html = inlineHtml(data.reference.action, html, inlineMessageData, inlineOptions);
1481
+ } catch (err) {
1482
+ this.logger.error({ msg: 'Failed to generate inline HTML content', err });
1483
+ }
1484
+ }
1485
+
1486
+ if (data.text) {
1487
+ try {
1488
+ data.text = inlineText(data.reference.action, data.text, inlineMessageData, inlineOptions);
1489
+ } catch (err) {
1490
+ this.logger.error({ msg: 'Failed to generate inline text content', err });
1491
+ }
1492
+ }
1493
+ }
1494
+
1495
+ // Set recipient for reply
1496
+ if (!data.to && data.reference.action === 'reply') {
1497
+ data.to =
1498
+ referencedMessage.replyTo && referencedMessage.replyTo.length
1499
+ ? referencedMessage.replyTo
1500
+ : referencedMessage.from
1501
+ ? referencedMessage.from
1502
+ : false;
1503
+ }
1504
+
1505
+ // Handle reply-all recipients
1506
+ if (data.reference.action === 'reply-all') {
1507
+ let envelope = {
1508
+ to: [].concat(
1509
+ referencedMessage.replyTo && referencedMessage.replyTo.length
1510
+ ? referencedMessage.replyTo
1511
+ : referencedMessage.from
1512
+ ? referencedMessage.from
1513
+ : []
1514
+ ),
1515
+ cc: [],
1516
+ bcc: []
1517
+ };
1518
+
1519
+ // Deduplicate recipients
1520
+ let addressesSeen = new Set([].concat(data.from?.address || []).concat(envelope.to.map(addr => addr.address)));
1521
+ for (let rcpt of ['to', 'cc', 'bcc']) {
1522
+ for (let addr of referencedMessage[rcpt] || []) {
1523
+ if (addressesSeen.has(addr.address)) {
1524
+ continue;
1525
+ }
1526
+ addressesSeen.add(addr.address);
1527
+ envelope[rcpt].push(addr);
1528
+ }
1529
+
1530
+ for (let addr of data[rcpt] || []) {
1531
+ if (addressesSeen.has(addr.address)) {
1532
+ continue;
1533
+ }
1534
+ addressesSeen.add(addr.address);
1535
+ envelope[rcpt].push(addr);
1536
+ }
1537
+
1538
+ if (envelope[rcpt].length) {
1539
+ data[rcpt] = envelope[rcpt];
1540
+ }
1541
+ }
1542
+ }
1543
+
1544
+ // Determine which attachments to include
1545
+ let attachmentsToDownload;
1546
+
1547
+ if (
1548
+ data.reference.action === 'forward' &&
1549
+ data.reference.forwardAttachments &&
1550
+ referencedMessage.attachments &&
1551
+ referencedMessage.attachments.length
1552
+ ) {
1553
+ // download all
1554
+ attachmentsToDownload = referencedMessage.attachments;
1555
+ } else if (cidAttachments.length) {
1556
+ // download referenced attachments
1557
+ attachmentsToDownload = cidAttachments;
1558
+ }
1559
+
1560
+ // Download and attach referenced attachments
1561
+ if (attachmentsToDownload && attachmentsToDownload.length) {
1562
+ this.checkIMAPConnection(connectionOptions);
1563
+
1564
+ this.logger.info({
1565
+ msg: 'Fetching attachments from the referenced email',
1566
+ attachments: attachmentsToDownload.map(a => ({ id: a.id, hasContent: !!a.content }))
1567
+ });
1568
+
1569
+ // fetch and add attachments to the message
1570
+ if (!data.attachments) {
1571
+ data.attachments = [];
1572
+ }
1573
+ for (let attachment of attachmentsToDownload) {
1574
+ let content;
1575
+ if (attachment.content) {
1576
+ // use local cache
1577
+ content = Buffer.from(attachment.content, 'base64');
1578
+ this.logger.trace({ msg: 'Using cached email content', attachment: attachment.id, size: content.length });
1579
+ } else {
1580
+ // fetch from IMAP
1581
+ content = await this.getAttachmentContent(
1582
+ attachment.id,
1583
+ {
1584
+ chunkSize: Math.max(DOWNLOAD_CHUNK_SIZE, 2 * 1024 * 1024),
1585
+ contentOnly: true
1586
+ },
1587
+ connectionOptions
1588
+ );
1589
+ }
1590
+ if (!content) {
1591
+ // skip missing?
1592
+ continue;
1593
+ }
1594
+ data.attachments.push({
1595
+ filename: attachment.filename,
1596
+ content,
1597
+ contentType: attachment.contentType,
1598
+ contentDisposition: attachment.inline ? 'inline' : 'attachment',
1599
+ cid: attachment.contentId || null
1600
+ });
1601
+ }
1602
+ }
1603
+ }
1604
+ }
1605
+
1606
+ // resolve referenced attachments
1607
+ for (let attachment of data.attachments || []) {
1608
+ if (attachment.reference && !attachment.content) {
1609
+ this.checkIMAPConnection(connectionOptions);
1610
+
1611
+ let content = await this.getAttachmentContent(
1612
+ attachment.reference,
1613
+ {
1614
+ chunkSize: Math.max(DOWNLOAD_CHUNK_SIZE, 2 * 1024 * 1024),
1615
+ contentOnly: true
1616
+ },
1617
+ connectionOptions
1618
+ );
1619
+ if (!content) {
1620
+ let error = new Error('Referenced attachment was not found');
1621
+ error.code = 'ReferenceNotFound';
1622
+ error.statusCode = 404;
1623
+ throw error;
1624
+ }
1625
+
1626
+ attachment.content = content;
1627
+ }
1628
+ }
1629
+
1630
+ // Final message configuration
1631
+ data.disableUrlAccess = true;
1632
+ data.disableFileAccess = true;
1633
+ data.boundaryPrefix = MIME_BOUNDARY_PREFIX;
1634
+ data.baseBoundary = genBaseBoundary();
1635
+ data.newline = '\r\n';
1636
+
1637
+ if (data.internalDate && !data.date) {
1638
+ // Update Date: header as well
1639
+ data.date = new Date(data.internalDate);
1640
+ }
1641
+
1642
+ // Generate raw MIME message
1643
+ let { raw, emailObject, messageId } = await getRawEmail(data, null, options);
1644
+
1645
+ return { raw, emailObject, messageId, documentStoreUsed, referencedMessage };
1646
+ }
1647
+
1648
+ /**
1649
+ * Processes a single message for queuing
1650
+ * Handles templates, references, tracking, and scheduling
1651
+ * @param {Object} data - Message data
1652
+ * @param {Object} meta - Metadata
1653
+ * @param {Object} licenseInfo - License information
1654
+ * @param {Object} connectionOptions - Connection options
1655
+ * @returns {Object} Queue result
1656
+ */
1657
+ async queueMessageEntry(data, meta, licenseInfo, connectionOptions) {
1658
+ let accountData = await this.accountObject.loadAccountData();
1659
+
1660
+ // Configure message defaults
1661
+ data.disableFileAccess = true;
1662
+ data.disableUrlAccess = true;
1663
+ data.boundaryPrefix = MIME_BOUNDARY_PREFIX;
1664
+ data.baseBoundary = genBaseBoundary();
1665
+
1666
+ let baseUrl = data.baseUrl || (await settings.get('serviceUrl')) || null;
1667
+
1668
+ // Build template context
1669
+ let context = {
1670
+ params: (data.render && data.render.params) || {},
1671
+ account: {
1672
+ name: accountData.name,
1673
+ email: accountData.email
1674
+ },
1675
+ service: {
1676
+ url: baseUrl
1677
+ }
1678
+ };
1679
+
1680
+ // Set default from address
1681
+ if (!data.from) {
1682
+ data.from = {
1683
+ name: accountData.name,
1684
+ address: accountData.email,
1685
+ _default: true
1686
+ };
1687
+ }
1688
+
1689
+ // Handle list unsubscribe headers
1690
+ if (baseUrl && data.listId && data.to && data.to.length === 1 && data.to[0].address) {
1691
+ // check if not already blocked
1692
+
1693
+ let blockData;
1694
+
1695
+ try {
1696
+ blockData = await this.redis.hget(`${REDIS_PREFIX}lists:unsub:entries:${data.listId}`, data.to[0].address.toLowerCase().trim());
1697
+ blockData = JSON.parse(blockData);
1698
+ } catch (err) {
1699
+ blockData = false;
1700
+ }
1701
+
1702
+ // Skip if recipient has unsubscribed
1703
+ if (blockData) {
1704
+ return {
1705
+ responseValue: {
1706
+ skipped: {
1707
+ reason: blockData.reason,
1708
+ listId: data.listId
1709
+ }
1710
+ }
1711
+ };
1712
+ }
1713
+
1714
+ if (!data.headers) {
1715
+ data.headers = {};
1716
+ }
1717
+
1718
+ // Generate List headers
1719
+ let baseDomain;
1720
+ try {
1721
+ baseDomain = (new URL(baseUrl).hostname || '').toLowerCase().trim();
1722
+ } catch (err) {
1723
+ // ignore error
1724
+ }
1725
+ baseDomain = baseDomain || (os.hostname() || '').toLowerCase().trim() || 'localhost';
1726
+
1727
+ if (baseDomain) {
1728
+ let unsubscribeUrlObj = new URL('/unsubscribe', baseUrl);
1729
+
1730
+ const serviceSecret = await getServiceSecret();
1731
+
1732
+ let fromDomain = ((data.from && data.from.address) || '').split('@').pop().trim().toLowerCase() || baseDomain;
1733
+ try {
1734
+ fromDomain = punycode.toASCII(fromDomain);
1735
+ } catch (err) {
1736
+ // ignore
1737
+ }
1738
+
1739
+ data.headers['List-ID'] = `<${data.listId}.${baseDomain}>`;
1740
+
1741
+ if (!data.messageId) {
1742
+ data.messageId = `<${uuid()}@${fromDomain}>`;
1743
+ }
1744
+
1745
+ // Create signed unsubscribe URL
1746
+ let { data: signedData, signature } = getSignedFormDataSync(
1747
+ serviceSecret,
1748
+ {
1749
+ act: 'unsub',
1750
+ acc: accountData.account,
1751
+ list: data.listId,
1752
+ rcpt: data.to[0].address,
1753
+ msg: data.messageId
1754
+ },
1755
+ true
1756
+ );
1757
+
1758
+ unsubscribeUrlObj.searchParams.append('data', signedData);
1759
+ if (signature) {
1760
+ unsubscribeUrlObj.searchParams.append('sig', signature);
1761
+ }
1762
+
1763
+ context.rcpt = Object.assign({}, data.to[0], { unsubscribeUrl: unsubscribeUrlObj.href });
1764
+
1765
+ // Add RFC 8058 compliant unsubscribe headers
1766
+ data.headers['List-Unsubscribe'] = `<${context.rcpt.unsubscribeUrl}>`;
1767
+ data.headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click';
1768
+ }
1769
+ }
1770
+
1771
+ // Render templates
1772
+ for (let key of ['subject', 'html', 'text', 'previewText']) {
1773
+ if (data.render || data.listId) {
1774
+ data[key] = this.render(data[key], context, key, data.render && data.render.format);
1775
+ }
1776
+ }
1777
+
1778
+ delete data.render;
1779
+
1780
+ // Configure locale and timezone for inline content
1781
+ let inlineOptions = {
1782
+ locale: data.locale || accountData.locale || (await settings.get('locale')),
1783
+ tz: data.tz || accountData.tz || (await settings.get('timezone'))
1784
+ };
1785
+
1786
+ delete data.locale;
1787
+ delete data.tz;
1788
+
1789
+ // Generate plain text from HTML if needed
1790
+ if (data.html && !data.text) {
1791
+ try {
1792
+ data.text = htmlToText(data.html);
1793
+ } catch (err) {
1794
+ this.logger.error({ msg: 'Failed to generate plaintext content from html', err });
1795
+ }
1796
+ }
1797
+
1798
+ let referencedMessage;
1799
+ let documentStoreUsed = false;
1800
+
1801
+ // Resolve reference and update reference/in-reply-to headers
1802
+ if (data.reference && data.reference.message) {
1803
+ // Try document store first if enabled
1804
+ if (data.reference.documentStore && (await settings.get('documentStoreEnabled'))) {
1805
+ try {
1806
+ referencedMessage = await this.accountObject.getMessage(data.reference.message, {
1807
+ documentStore: true,
1808
+ textType: '*'
1809
+ });
1810
+ } catch (err) {
1811
+ if (err.meta && err.meta.statusCode === 404) {
1812
+ // not found
1813
+ } else {
1814
+ let error = new Error('ElasticSearch request failed');
1815
+ error.info = {
1816
+ response: (err.meta && err.meta.body) || err.message,
1817
+ statusCode: err.meta && err.meta.statusCode
1818
+ };
1819
+ error.code = 'ESRequestError';
1820
+ error.statusCode = (err.meta && err.meta.statusCode) || 500;
1821
+ throw error;
1822
+ }
1823
+ }
1824
+ documentStoreUsed = true;
1825
+ } else {
1826
+ // Fetch from IMAP/API with retry logic for transient errors
1827
+ this.checkIMAPConnection(connectionOptions);
1828
+
1829
+ let extendedData = data.reference.inline || data.reference.forwardAttachments;
1830
+
1831
+ try {
1832
+ referencedMessage = await retryOnTransientError(
1833
+ async () => {
1834
+ return await this.getMessage(
1835
+ data.reference.message,
1836
+ {
1837
+ fields: !extendedData
1838
+ ? {
1839
+ uid: true,
1840
+ flags: true,
1841
+ envelope: true,
1842
+ headers: ['references']
1843
+ }
1844
+ : false,
1845
+ header: extendedData ? true : false,
1846
+ textType: extendedData ? '*' : false
1847
+ },
1848
+ connectionOptions
1849
+ );
1850
+ },
1851
+ {
1852
+ maxAttempts: 3,
1853
+ baseDelay: 1000,
1854
+ logger: this.logger
1855
+ }
1856
+ );
1857
+ } catch (err) {
1858
+ // Check if this is a transient error that exhausted retries
1859
+ const isTransient = isTransientError(err);
1860
+
1861
+ if (isTransient && data.reference.ignoreMissing) {
1862
+ // Transient error but ignoreMissing is true - log and continue without reference
1863
+ this.logger.warn({
1864
+ msg: 'Failed to fetch referenced message due to transient error, continuing without reference',
1865
+ messageId: data.reference.message,
1866
+ err: err.message,
1867
+ statusCode: err.statusCode || (err.oauthRequest && err.oauthRequest.status)
1868
+ });
1869
+ referencedMessage = null;
1870
+ } else if (!isTransient && err.statusCode === 404) {
1871
+ // Permanent 404 error - respect ignoreMissing
1872
+ if (!data.reference.ignoreMissing) {
1873
+ let error = new Error('Referenced message was not found');
1874
+ error.code = 'ReferenceNotFound';
1875
+ error.statusCode = 404;
1876
+ throw error;
1877
+ }
1878
+ referencedMessage = null;
1879
+ } else {
1880
+ // Either transient error with ignoreMissing=false, or other permanent error
1881
+ throw err;
1882
+ }
1883
+ }
1884
+ }
1885
+
1886
+ if (!referencedMessage && !data.reference.ignoreMissing) {
1887
+ let error = new Error('Referenced message was not found');
1888
+ error.code = 'ReferenceNotFound';
1889
+ error.statusCode = 404;
1890
+ throw error;
1891
+ }
1892
+
1893
+ if (referencedMessage) {
1894
+ // Verify Message-ID if specified
1895
+ if (data.reference.messageId && data.reference.messageId !== referencedMessage.messageId) {
1896
+ let err = new Error('The referenced message was found, but its Message-ID does not match the expected value');
1897
+ err.code = 'MessageNotFound';
1898
+ err.statusCode = 404;
1899
+ throw err;
1900
+ }
1901
+
1902
+ // Build references header chain
1903
+ let references = []
1904
+ .concat(referencedMessage.messageId || [])
1905
+ .concat(referencedMessage.inReplyTo || [])
1906
+ .concat((referencedMessage.headers && referencedMessage.headers.references) || [])
1907
+ .flatMap(line => line.split(/\s+/))
1908
+ .map(ref => ref.trim())
1909
+ .filter(ref => ref)
1910
+ .map(ref => {
1911
+ // Ensure proper Message-ID format
1912
+ if (!/^</.test(ref)) {
1913
+ ref = '<' + ref;
1914
+ }
1915
+ if (!/>$/.test(ref)) {
1916
+ ref = ref + '>';
1917
+ }
1918
+ return ref;
1919
+ });
1920
+
1921
+ references = Array.from(new Set(references));
1922
+ if (references.length) {
1923
+ if (!data.headers) {
1924
+ data.headers = {};
1925
+ }
1926
+ data.headers.references = references.join(' ');
1927
+ }
1928
+
1929
+ // Set In-Reply-To header for replies
1930
+ if (['reply', 'reply-all'].includes(data.reference.action) && referencedMessage.messageId) {
1931
+ data.headers['in-reply-to'] = referencedMessage.messageId;
1932
+ }
1933
+
1934
+ // Check if we need to update flags on referenced message
1935
+ let referenceFlags = ['\\Answered'].concat(data.reference.action === 'forward' ? '$Forwarded' : []);
1936
+ if (!referencedMessage.flags || !referencedMessage.flags.length || !referenceFlags.some(flag => referencedMessage.flags.includes(flag))) {
1937
+ data.reference.update = true;
1938
+ }
1939
+
1940
+ // Preserve thread ID
1941
+ if (referencedMessage.threadId) {
1942
+ data.reference.threadId = referencedMessage.threadId;
1943
+ }
1944
+
1945
+ // Generate subject with appropriate prefix
1946
+ if (!data.subject && referencedMessage.subject) {
1947
+ let subject = referencedMessage.subject;
1948
+ let prefix;
1949
+ switch (data.reference.action) {
1950
+ case 'reply':
1951
+ case 'reply-all':
1952
+ if (!/^Re:/i.test(subject)) {
1953
+ prefix = 'Re';
1954
+ }
1955
+ break;
1956
+ case 'forward':
1957
+ if (!/^Fwd:/i.test(subject)) {
1958
+ prefix = 'Fwd';
1959
+ }
1960
+ break;
1961
+ }
1962
+ data.subject = `${prefix ? prefix + ': ' : ''}${subject}`;
1963
+ }
1964
+
1965
+ let cidAttachments = [];
1966
+
1967
+ // Process inline content for replies/forwards
1968
+ if (data.reference.inline) {
1969
+ let inlineMessageData = {
1970
+ text: referencedMessage.text && referencedMessage.text.plain,
1971
+ html: referencedMessage.text && referencedMessage.text.html
1972
+ };
1973
+
1974
+ for (let key of ['from', 'to', 'cc', 'bcc', 'date', 'subject']) {
1975
+ inlineMessageData[key] = referencedMessage[key];
1976
+ }
1977
+
1978
+ if (inlineMessageData.html) {
1979
+ // Find inline attachments referenced in HTML
1980
+ if (referencedMessage.attachments) {
1981
+ // find all attachments that are referenced in the HTML
1982
+ for (let attachment of referencedMessage.attachments) {
1983
+ if (attachment.contentId && inlineMessageData.html.indexOf(`cid:${attachment.contentId.replace(/^<|>$/g, '')}`) >= 0) {
1984
+ cidAttachments.push(attachment);
1985
+ }
1986
+ }
1987
+ }
1988
+
1989
+ try {
1990
+ let html = data.html;
1991
+ if (!(html || '').toString().trim() && data.text) {
1992
+ html = textToHtml(data.text); // convert text to html
1993
+ }
1994
+
1995
+ data.html = inlineHtml(data.reference.action, html, inlineMessageData, inlineOptions);
1996
+ } catch (err) {
1997
+ this.logger.error({ msg: 'Failed to generate inline HTML content', err });
1998
+ }
1999
+ }
2000
+
2001
+ if (data.text) {
2002
+ try {
2003
+ data.text = inlineText(data.reference.action, data.text, inlineMessageData, inlineOptions);
2004
+ } catch (err) {
2005
+ this.logger.error({ msg: 'Failed to generate inline text content', err });
2006
+ }
2007
+ }
2008
+ }
2009
+
2010
+ // Set recipient for reply
2011
+ if (!data.to && data.reference.action === 'reply') {
2012
+ data.to =
2013
+ referencedMessage.replyTo && referencedMessage.replyTo.length
2014
+ ? referencedMessage.replyTo
2015
+ : referencedMessage.from
2016
+ ? referencedMessage.from
2017
+ : false;
2018
+ }
2019
+
2020
+ // Handle reply-all recipients
2021
+ if (data.reference.action === 'reply-all') {
2022
+ let envelope = {
2023
+ to: [].concat(
2024
+ referencedMessage.replyTo && referencedMessage.replyTo.length
2025
+ ? referencedMessage.replyTo
2026
+ : referencedMessage.from
2027
+ ? referencedMessage.from
2028
+ : []
2029
+ ),
2030
+ cc: [],
2031
+ bcc: []
2032
+ };
2033
+
2034
+ // Deduplicate recipients
2035
+ let addressesSeen = new Set([].concat(data.from?.address || []).concat(envelope.to.map(addr => addr.address)));
2036
+ for (let rcpt of ['to', 'cc', 'bcc']) {
2037
+ for (let addr of referencedMessage[rcpt] || []) {
2038
+ if (addressesSeen.has(addr.address)) {
2039
+ continue;
2040
+ }
2041
+ addressesSeen.add(addr.address);
2042
+ envelope[rcpt].push(addr);
2043
+ }
2044
+
2045
+ for (let addr of data[rcpt] || []) {
2046
+ if (addressesSeen.has(addr.address)) {
2047
+ continue;
2048
+ }
2049
+ addressesSeen.add(addr.address);
2050
+ envelope[rcpt].push(addr);
2051
+ }
2052
+
2053
+ if (envelope[rcpt].length) {
2054
+ data[rcpt] = envelope[rcpt];
2055
+ }
2056
+ }
2057
+ }
2058
+
2059
+ // Determine which attachments to include
2060
+ let attachmentsToDownload;
2061
+
2062
+ if (
2063
+ data.reference.action === 'forward' &&
2064
+ data.reference.forwardAttachments &&
2065
+ referencedMessage.attachments &&
2066
+ referencedMessage.attachments.length
2067
+ ) {
2068
+ // download all
2069
+ attachmentsToDownload = referencedMessage.attachments;
2070
+ } else if (cidAttachments.length) {
2071
+ // download referenced attachments
2072
+ attachmentsToDownload = cidAttachments;
2073
+ }
2074
+
2075
+ // Download and attach referenced attachments
2076
+ if (attachmentsToDownload && attachmentsToDownload.length) {
2077
+ this.checkIMAPConnection(connectionOptions);
2078
+
2079
+ this.logger.info({
2080
+ msg: 'Fetching attachments from the referenced email',
2081
+ attachments: attachmentsToDownload.map(a => ({ id: a.id, hasContent: !!a.content }))
2082
+ });
2083
+
2084
+ // fetch and add attachments to the message
2085
+ if (!data.attachments) {
2086
+ data.attachments = [];
2087
+ }
2088
+ for (let attachment of attachmentsToDownload) {
2089
+ let content;
2090
+ if (attachment.content) {
2091
+ // use local cache
2092
+ content = Buffer.from(attachment.content, 'base64');
2093
+ this.logger.trace({ msg: 'Using cached email content', attachment: attachment.id, size: content.length });
2094
+ } else {
2095
+ // fetch from IMAP
2096
+ content = await this.getAttachmentContent(
2097
+ attachment.id,
2098
+ {
2099
+ chunkSize: Math.max(DOWNLOAD_CHUNK_SIZE, 2 * 1024 * 1024),
2100
+ contentOnly: true
2101
+ },
2102
+ connectionOptions
2103
+ );
2104
+ }
2105
+ if (!content) {
2106
+ // skip missing?
2107
+ continue;
2108
+ }
2109
+ data.attachments.push({
2110
+ filename: attachment.filename,
2111
+ content,
2112
+ contentType: attachment.contentType,
2113
+ contentDisposition: attachment.inline ? 'inline' : 'attachment',
2114
+ cid: attachment.contentId || null
2115
+ });
2116
+ }
2117
+ }
2118
+ }
2119
+ }
2120
+
2121
+ // resolve referenced attachments by ID
2122
+ for (let attachment of data.attachments || []) {
2123
+ if (attachment.reference && !attachment.content) {
2124
+ this.checkIMAPConnection(connectionOptions);
2125
+
2126
+ let content = await this.getAttachmentContent(
2127
+ attachment.reference,
2128
+ {
2129
+ chunkSize: Math.max(DOWNLOAD_CHUNK_SIZE, 2 * 1024 * 1024),
2130
+ contentOnly: true
2131
+ },
2132
+ connectionOptions
2133
+ );
2134
+
2135
+ if (!content) {
2136
+ let error = new Error('Referenced attachment was not found');
2137
+ error.code = 'ReferenceNotFound';
2138
+ error.statusCode = 404;
2139
+ throw error;
2140
+ }
2141
+
2142
+ attachment.content = content;
2143
+ }
2144
+ }
2145
+
2146
+ // Generate raw MIME message
2147
+ let { raw, hasBcc, envelope, subject, messageId, sendAt, deliveryAttempts, trackClicks, trackOpens, trackingEnabled, gateway } = await getRawEmail(
2148
+ data,
2149
+ licenseInfo
2150
+ );
2151
+
2152
+ // Handle dry run mode
2153
+ if (data.dryRun) {
2154
+ let response = {
2155
+ response: 'Dry run',
2156
+ messageId
2157
+ };
2158
+
2159
+ if (data.reference && data.reference.message) {
2160
+ response.reference = {
2161
+ message: data.reference.message,
2162
+ documentStore: documentStoreUsed,
2163
+ success: referencedMessage ? true : false
2164
+ };
2165
+
2166
+ if (!referencedMessage) {
2167
+ response.reference.error = 'Referenced message was not found';
2168
+ }
2169
+ }
2170
+
2171
+ response.preview = raw.toString('base64');
2172
+
2173
+ return response;
2174
+ }
2175
+
2176
+ // Load gateway configuration if specified
2177
+ let gatewayData;
2178
+ if (gateway) {
2179
+ let gatewayObject = new Gateway({ gateway, redis: this.redis, secret: this.secret });
2180
+ try {
2181
+ gatewayData = await gatewayObject.loadGatewayData();
2182
+ } catch (err) {
2183
+ this.logger.info({ msg: 'Failed to load gateway data', envelope, messageId, gateway, err });
2184
+ }
2185
+ }
2186
+
2187
+ // Verify SMTP capability
2188
+ if (!accountData.smtp && !accountData.oauth2 && !gatewayData) {
2189
+ // can not make connection
2190
+ let err = new Error('SMTP configuration not found');
2191
+ err.code = 'SMTPUnavailable';
2192
+ err.statusCode = 404;
2193
+ throw err;
2194
+ }
2195
+
2196
+ // Configure tracking settings
2197
+ if (typeof trackingEnabled !== 'boolean') {
2198
+ trackingEnabled = (await settings.get('trackSentMessages')) || false;
2199
+ }
2200
+
2201
+ if (typeof trackClicks !== 'boolean') {
2202
+ trackClicks = await settings.get('trackClicks');
2203
+ if (typeof trackClicks !== 'boolean') {
2204
+ trackClicks = trackingEnabled;
2205
+ }
2206
+ }
2207
+
2208
+ if (typeof trackOpens !== 'boolean') {
2209
+ trackOpens = await settings.get('trackOpens');
2210
+ if (typeof trackOpens !== 'boolean') {
2211
+ trackOpens = trackingEnabled;
2212
+ }
2213
+ }
2214
+
2215
+ // Add tracking pixels and link tracking
2216
+ if (raw && (trackClicks || trackOpens) && baseUrl) {
2217
+ // add open and click tracking
2218
+ raw = await addTrackers(raw, accountData.account, messageId, baseUrl, {
2219
+ trackClicks,
2220
+ trackOpens
2221
+ });
2222
+ }
2223
+
2224
+ let now = new Date();
2225
+
2226
+ //queue for later
2227
+
2228
+ // Use timestamp in the ID to make sure that jobs are ordered by send time
2229
+ let timeBuf = Buffer.allocUnsafe(8);
2230
+
2231
+ timeBuf.writeBigInt64BE(BigInt((sendAt && sendAt.getTime()) || Date.now()), 0);
2232
+
2233
+ let queueId = Buffer.concat([timeBuf.subarray(2), crypto.randomBytes(4)])
2234
+ .toString('hex')
2235
+ .substring(1); // first char is always 0
2236
+
2237
+ // Serialize message data for storage
2238
+ let msgEntry = msgpack.encode({
2239
+ queueId,
2240
+ gateway: gatewayData && gatewayData.gateway,
2241
+ hasBcc,
2242
+ envelope,
2243
+ messageId,
2244
+ reference: data.reference || {},
2245
+ sendAt: (sendAt && sendAt.getTime()) || false,
2246
+ created: now.getTime(),
2247
+ copy: data.copy,
2248
+ sentMailPath: data.sentMailPath,
2249
+ feedbackKey: data.feedbackKey || null,
2250
+ dsn: data.dsn || null,
2251
+ proxy: data.proxy || null,
2252
+ localAddress: data.localAddress || null,
2253
+ raw
2254
+ });
2255
+
2256
+ // Store message in Redis
2257
+ await this.redis.hsetBuffer(`${REDIS_PREFIX}iaq:${this.account}`, queueId, msgEntry);
2258
+
2259
+ let queueKeep = (await settings.get('queueKeep')) || true;
2260
+
2261
+ // Configure delivery retry settings
2262
+ let defaultDeliveryAttempts = await settings.get('deliveryAttempts');
2263
+ if (typeof defaultDeliveryAttempts !== 'number') {
2264
+ defaultDeliveryAttempts = DEFAULT_DELIVERY_ATTEMPTS;
2265
+ }
2266
+
2267
+ // Prepare job data for queue
2268
+ let jobData = Object.assign({}, meta || {}, {
2269
+ account: this.account,
2270
+ queueId,
2271
+ gateway: (gatewayData && gatewayData.gateway) || null,
2272
+ messageId,
2273
+ envelope,
2274
+ subject,
2275
+ proxy: data.proxy,
2276
+ localAddress: data.localAddress,
2277
+ created: now.getTime()
2278
+ });
2279
+
2280
+ // Configure queue job options
2281
+ let queueName = 'queued';
2282
+ let jobOpts = {
2283
+ jobId: queueId,
2284
+ removeOnComplete: queueKeep,
2285
+ removeOnFail: queueKeep,
2286
+ attempts: typeof deliveryAttempts === 'number' ? deliveryAttempts : defaultDeliveryAttempts,
2287
+ backoff: {
2288
+ type: 'exponential',
2289
+ delay: 5000
2290
+ }
2291
+ };
2292
+
2293
+ // Schedule delayed sending
2294
+ if (sendAt && sendAt > now) {
2295
+ queueName = 'delayed';
2296
+ jobOpts.delay = sendAt.getTime() - now.getTime();
2297
+ }
2298
+
2299
+ // Add job to queue
2300
+ let job = await this.submitQueue.add(queueName, jobData, jobOpts);
2301
+
2302
+ try {
2303
+ await job.updateProgress({
2304
+ status: 'queued'
2305
+ });
2306
+ } catch (err) {
2307
+ // ignore
2308
+ }
2309
+
2310
+ this.logger.info({ msg: 'Message queued for delivery', envelope, messageId, sendAt: (sendAt || now).toISOString(), queueId, job: job.id });
2311
+
2312
+ // Build response
2313
+ let response = {
2314
+ response: 'Queued for delivery',
2315
+ messageId,
2316
+ sendAt: (sendAt || now).toISOString(),
2317
+ queueId
2318
+ };
2319
+
2320
+ if (data.reference && data.reference.message) {
2321
+ response.reference = {
2322
+ message: data.reference.message,
2323
+ documentStore: documentStoreUsed,
2324
+ success: referencedMessage ? true : false
2325
+ };
2326
+
2327
+ if (!referencedMessage) {
2328
+ response.reference.error = 'Referenced message was not found';
2329
+ }
2330
+ }
2331
+
2332
+ return response;
2333
+ }
2334
+
2335
+ /**
2336
+ * Renders template strings with context data
2337
+ * @param {string} template - Template string
2338
+ * @param {Object} data - Context data
2339
+ * @param {string} key - Template key (subject, text, html, previewText)
2340
+ * @param {string} renderFormat - Override format for rendering
2341
+ * @returns {string} Rendered template
2342
+ */
2343
+ render(template, data, key, renderFormat) {
2344
+ let format;
2345
+
2346
+ // Determine format based on key
2347
+ switch (key) {
2348
+ case 'subject':
2349
+ case 'text': {
2350
+ format = 'plain';
2351
+ break;
2352
+ }
2353
+
2354
+ case 'html': {
2355
+ format = renderFormat ? renderFormat : 'html';
2356
+ break;
2357
+ }
2358
+
2359
+ case 'previewText':
2360
+ default: {
2361
+ format = 'html';
2362
+ break;
2363
+ }
2364
+ }
2365
+
2366
+ try {
2367
+ const compiledTemplate = getTemplate({
2368
+ format,
2369
+ template
2370
+ });
2371
+
2372
+ return compiledTemplate(data);
2373
+ } catch (err) {
2374
+ logger.error({ msg: `Failed rendering ${key} template`, err });
2375
+ let error = new Error(`Failed rendering ${key} template`);
2376
+ error.code = err.code || 'SubmitFail';
2377
+ error.statusCode = 422;
2378
+ throw error;
2379
+ }
2380
+ }
2381
+
2382
+ /**
2383
+ * Detects if a message is an auto-reply based on headers and subject
2384
+ * @param {Object} messageData - Message data
2385
+ * @returns {boolean} True if message appears to be an auto-reply
2386
+ */
2387
+ isAutoreply(messageData) {
2388
+ // Check subject patterns
2389
+ if (/^(auto:|Out of Office|OOF:|OOO:)/i.test(messageData.subject) && messageData.inReplyTo) {
2390
+ return true;
2391
+ }
2392
+
2393
+ if (!messageData.headers) {
2394
+ return false;
2395
+ }
2396
+
2397
+ // Check Precedence header
2398
+ if (messageData.headers.precedence && messageData.headers.precedence.some(e => /auto[_-]?reply/.test(e))) {
2399
+ return true;
2400
+ }
2401
+
2402
+ // Check Auto-Submitted header
2403
+ if (messageData.headers['auto-submitted'] && messageData.headers['auto-submitted'].some(e => /auto[_-]?replied/.test(e))) {
2404
+ return true;
2405
+ }
2406
+
2407
+ // Check various vendor-specific headers
2408
+ for (let headerKey of ['x-autoresponder', 'x-autorespond', 'x-autoreply']) {
2409
+ if (messageData.headers[headerKey] && messageData.headers[headerKey].length) {
2410
+ return true;
2411
+ }
2412
+ }
2413
+
2414
+ return false;
2415
+ }
2416
+
2417
+ /**
2418
+ * Gets message fetch options based on settings
2419
+ * @returns {Object} Fetch options for IMAP message retrieval
2420
+ */
2421
+ async getMessageFetchOptions() {
2422
+ let messageFetchOptions = {};
2423
+
2424
+ // Configure text content fetching
2425
+ let notifyText = await settings.get('notifyText');
2426
+ if (notifyText) {
2427
+ messageFetchOptions.textType = '*';
2428
+ let notifyTextSize = await settings.get('notifyTextSize');
2429
+
2430
+ if (notifyTextSize) {
2431
+ messageFetchOptions.maxBytes = notifyTextSize;
2432
+ }
2433
+ }
2434
+
2435
+ // Configure header fetching
2436
+ let notifyHeaders = (await settings.get('notifyHeaders')) || [];
2437
+ if (notifyHeaders.length) {
2438
+ messageFetchOptions.headers = notifyHeaders.includes('*') ? true : notifyHeaders.length ? notifyHeaders : false;
2439
+ }
2440
+
2441
+ // also request autoresponse headers
2442
+ if (messageFetchOptions.headers !== true) {
2443
+ let fetchHeaders = new Set(messageFetchOptions.headers || []);
2444
+
2445
+ // Headers for auto-reply detection
2446
+ fetchHeaders.add('x-autoreply');
2447
+ fetchHeaders.add('x-autorespond');
2448
+ fetchHeaders.add('auto-submitted');
2449
+ fetchHeaders.add('precedence');
2450
+
2451
+ // Headers for threading
2452
+ fetchHeaders.add('in-reply-to');
2453
+ fetchHeaders.add('references');
2454
+
2455
+ // Content type for detecting multipart/report
2456
+ fetchHeaders.add('content-type');
2457
+
2458
+ messageFetchOptions.fetchHeaders = Array.from(fetchHeaders);
2459
+ }
2460
+
2461
+ return messageFetchOptions;
2462
+ }
2463
+
2464
+ /**
2465
+ * Main processor for new messages
2466
+ * Handles bounce detection, complaint processing, attachments,
2467
+ * calendar events, and AI-powered features
2468
+ * @param {Object} messageData - Message data
2469
+ * @param {Object} options - Processing options
2470
+ */
2471
+ async processNew(messageData, options) {
2472
+ let requestedHeaders = options.headers;
2473
+
2474
+ let bounceNotifyInfo;
2475
+ let complaintNotifyInfo;
2476
+ let content;
2477
+
2478
+ // Check for abuse report format (ARF) complaints
2479
+ if (this.mightBeAComplaint(messageData)) {
2480
+ try {
2481
+ // Download attachments needed for complaint detection
2482
+ for (let attachment of messageData.attachments) {
2483
+ if (!['message/feedback-report', 'message/rfc822-headers', 'message/rfc822'].includes(attachment.contentType)) {
2484
+ continue;
2485
+ }
2486
+
2487
+ Object.defineProperty(attachment, 'content', {
2488
+ value: (await this.getAttachment(attachment.id))?.data?.toString(),
2489
+ enumerable: false
2490
+ });
2491
+ }
2492
+
2493
+ // Parse ARF report
2494
+ const report = await arfDetect(messageData);
2495
+
2496
+ if (report && report.arf && report.arf['original-rcpt-to'] && report.arf['original-rcpt-to'].length) {
2497
+ // can send report
2498
+ let complaint = {};
2499
+
2500
+ // Convert headers to camelCase
2501
+ for (let subKey of ['arf', 'headers']) {
2502
+ for (let key of Object.keys(report[subKey])) {
2503
+ if (!complaint[subKey]) {
2504
+ complaint[subKey] = {};
2505
+ }
2506
+ complaint[subKey][key.replace(/-(.)/g, (o, c) => c.toUpperCase())] = report[subKey][key];
2507
+ }
2508
+ }
2509
+
2510
+ complaintNotifyInfo = Object.assign({ complaintMessage: messageData.id }, complaint);
2511
+
2512
+ messageData.isComplaint = true;
2513
+
2514
+ // Link to original message
2515
+ if (complaint.headers && complaint.headers.messageId) {
2516
+ messageData.relatedMessageId = complaint.headers.messageId;
2517
+ }
2518
+ }
2519
+ } catch (err) {
2520
+ this.logger.error({
2521
+ msg: 'Failed to process ARF',
2522
+ id: messageData.id,
2523
+ uid: messageData.uid,
2524
+ messageId: messageData.messageId,
2525
+ err
2526
+ });
2527
+ }
2528
+ }
2529
+
2530
+ // Check for delivery status notifications (DSN)
2531
+ if (this.mightBeDSNResponse(messageData)) {
2532
+ try {
2533
+ let raw = await this.getRawMessage(messageData.id);
2534
+
2535
+ let parsed = await simpleParser(raw, { keepDeliveryStatus: true });
2536
+ if (parsed) {
2537
+ content = { parsed };
2538
+
2539
+ // Extract delivery status attachment
2540
+ let deliveryStatus = parsed.attachments.find(attachment => attachment.contentType === 'message/delivery-status');
2541
+ if (deliveryStatus) {
2542
+ let deliveryEntries = libmime.decodeHeaders((deliveryStatus.content || '').toString().trim());
2543
+ let structured = {};
2544
+
2545
+ // Parse delivery status fields
2546
+ for (let key of Object.keys(deliveryEntries)) {
2547
+ if (!key) {
2548
+ continue;
2549
+ }
2550
+ let displayKey = key.replace(/-(.)/g, (m, c) => c.toUpperCase());
2551
+ let value = deliveryEntries[key].at(-1);
2552
+ if (typeof value === 'string') {
2553
+ // Parse structured fields
2554
+ let m = value.match(/^([^\s;]+);/);
2555
+ if (m) {
2556
+ value = {
2557
+ label: m[1],
2558
+ value: value.substring(m[0].length).trim()
2559
+ };
2560
+ } else {
2561
+ switch (key) {
2562
+ case 'arrival-date': {
2563
+ value.trim();
2564
+ let date = new Date(value);
2565
+ if (date.toString() !== 'Invalid Date') {
2566
+ value = date.toISOString();
2567
+ }
2568
+ structured[displayKey] = value;
2569
+ break;
2570
+ }
2571
+ default:
2572
+ structured[displayKey] = value.trim();
2573
+ }
2574
+ }
2575
+ } else {
2576
+ // ???
2577
+ structured[displayKey] = value;
2578
+ }
2579
+ }
2580
+
2581
+ // Check if this is a delivery or delay notification
2582
+ if (/^delivered|^delayed/i.test(structured.action)) {
2583
+ this.logger.debug({
2584
+ msg: 'Detected delivery report',
2585
+ id: messageData.id,
2586
+ uid: messageData.uid,
2587
+ messageId: messageData.messageId,
2588
+ report: structured
2589
+ });
2590
+
2591
+ messageData.deliveryReport = structured;
2592
+ }
2593
+ }
2594
+ }
2595
+ } catch (err) {
2596
+ this.logger.error({
2597
+ msg: 'Failed to process DSN',
2598
+ id: messageData.id,
2599
+ uid: messageData.uid,
2600
+ messageId: messageData.messageId,
2601
+ err
2602
+ });
2603
+ }
2604
+ }
2605
+
2606
+ // Check if this could be a bounce
2607
+ if (this.mightBeABounce(messageData)) {
2608
+ // parse for bounce
2609
+ try {
2610
+ if (!content) {
2611
+ content = await this.getRawMessage(messageData.id);
2612
+ }
2613
+
2614
+ if (content) {
2615
+ let bounce = await bounceDetect(content);
2616
+
2617
+ if (bounce?.response?.message) {
2618
+ try {
2619
+ let bounceType = await this.call({
2620
+ cmd: 'bounceClassify',
2621
+ data: {
2622
+ message: bounce?.response?.message
2623
+ },
2624
+ timeout: 2 * 60 * 1000
2625
+ });
2626
+ if (bounceType?.label) {
2627
+ bounce.response.category = bounceType.label;
2628
+ }
2629
+ if (bounceType?.action) {
2630
+ bounce.response.recommendedAction = bounceType.action;
2631
+ }
2632
+ if (bounceType?.blocklist) {
2633
+ bounce.response.blocklist = bounceType.blocklist;
2634
+ }
2635
+ if (bounceType?.retryAfter) {
2636
+ bounce.response.retryAfter = bounceType.retryAfter;
2637
+ }
2638
+ } catch (err) {
2639
+ // ignore, just do not include this information
2640
+ this.logger.error({
2641
+ msg: 'Failed to classify bounce response',
2642
+ bounceResponse: bounce?.response?.message,
2643
+ err
2644
+ });
2645
+ }
2646
+ }
2647
+
2648
+ let stored = 0;
2649
+ if (bounce.action && bounce.recipient && bounce.messageId) {
2650
+ bounceNotifyInfo = Object.assign({ bounceMessage: messageData.id }, bounce);
2651
+
2652
+ messageData.isBounce = true;
2653
+ messageData.relatedMessageId = bounce.messageId;
2654
+ }
2655
+
2656
+ this.logger.debug({
2657
+ msg: 'Detected bounce message',
2658
+ id: messageData.id,
2659
+ uid: messageData.uid,
2660
+ messageId: messageData.messageId,
2661
+ bounce,
2662
+ stored
2663
+ });
2664
+ }
2665
+ } catch (err) {
2666
+ this.logger.error({
2667
+ msg: 'Failed to process potential bounce',
2668
+ id: messageData.id,
2669
+ uid: messageData.uid,
2670
+ messageId: messageData.messageId,
2671
+ err
2672
+ });
2673
+ }
2674
+ }
2675
+
2676
+ // Download attachment content if configured
2677
+ let notifyAttachments = await settings.get('notifyAttachments');
2678
+ let notifyAttachmentSize = await settings.get('notifyAttachmentSize');
2679
+ if (notifyAttachments && messageData.attachments?.length) {
2680
+ for (let attachment of messageData.attachments || []) {
2681
+ if (notifyAttachmentSize && attachment.encodedSize && attachment.encodedSize > notifyAttachmentSize) {
2682
+ // skip large attachments
2683
+ continue;
2684
+ }
2685
+ if (!attachment.content) {
2686
+ try {
2687
+ attachment.content = (await this.getAttachment(attachment.id))?.data?.toString('base64');
2688
+ } catch (err) {
2689
+ this.logger.error({ msg: 'Failed to load attachment content', attachment, err });
2690
+ }
2691
+ }
2692
+ }
2693
+ }
2694
+
2695
+ // Fetch inline attachments referenced in HTML
2696
+ if (messageData.attachments && messageData.attachments.length && messageData.text && messageData.text.html) {
2697
+ // fetch inline attachments
2698
+ for (let attachment of messageData.attachments) {
2699
+ if (attachment.encodedSize && attachment.encodedSize > MAX_INLINE_ATTACHMENT_SIZE) {
2700
+ // skip large attachments
2701
+ continue;
2702
+ }
2703
+
2704
+ if (!attachment.content && attachment.contentId && messageData.text.html.indexOf(`cid:${attachment.contentId.replace(/^<|>$/g, '')}`) >= 0) {
2705
+ try {
2706
+ attachment.content = (await this.getAttachment(attachment.id))?.data?.toString('base64');
2707
+ } catch (err) {
2708
+ this.logger.error({ msg: 'Failed to load attachment content', attachment, err });
2709
+ }
2710
+ }
2711
+ }
2712
+ }
2713
+
2714
+ // Fetch and process calendar events if needed
2715
+ let notifyCalendarEvents = await settings.get('notifyCalendarEvents');
2716
+ if (notifyCalendarEvents && messageData.attachments && messageData.attachments.length) {
2717
+ let calendarEventMap = new Map();
2718
+
2719
+ // when iterating the attachment array, process text/calendar before application/ics
2720
+ let sortCalendarAttachments = (a, b) => {
2721
+ if (a.contentType !== b.contentType) {
2722
+ if (a.contentType === 'text/calendar') {
2723
+ return -1;
2724
+ }
2725
+ if (b.contentType === 'text/calendar') {
2726
+ return 1;
2727
+ }
2728
+ }
2729
+ return a.contentType.localeCompare(b.contentType);
2730
+ };
2731
+
2732
+ // Process calendar attachments
2733
+ for (let attachment of [...messageData.attachments].sort(sortCalendarAttachments)) {
2734
+ if (['text/calendar', 'application/ics'].includes(attachment.contentType)) {
2735
+ if (!attachment.content) {
2736
+ try {
2737
+ let calendarBuf = (await this.getAttachment(attachment.id))?.data;
2738
+ attachment.content = calendarBuf.toString('base64');
2739
+ } catch (err) {
2740
+ this.logger.error({ msg: 'Failed to load attachment content', attachment, err });
2741
+ }
2742
+ }
2743
+ if (attachment.content) {
2744
+ let contentBuf = Buffer.from(attachment.content, 'base64');
2745
+ try {
2746
+ // Parse iCalendar data
2747
+ const jcalData = ical.parse(contentBuf.toString());
2748
+
2749
+ const comp = new ical.Component(jcalData);
2750
+ if (!comp) {
2751
+ continue;
2752
+ }
2753
+
2754
+ const vevent = comp.getFirstSubcomponent('vevent');
2755
+ if (!vevent) {
2756
+ continue;
2757
+ }
2758
+
2759
+ // Extract method (REQUEST, CANCEL, etc.)
2760
+ let eventMethodProp = comp.getFirstProperty('method');
2761
+ let eventMethodValue = eventMethodProp ? eventMethodProp.getFirstValue() : null;
2762
+
2763
+ const event = new ical.Event(vevent);
2764
+
2765
+ if (!event || !event.uid) {
2766
+ continue;
2767
+ }
2768
+
2769
+ // Skip if we already processed this event
2770
+ if (calendarEventMap.has(event.uid)) {
2771
+ if (attachment.filename) {
2772
+ let existingEntry = calendarEventMap.get(event.uid);
2773
+ if (!existingEntry.filename) {
2774
+ // inject filename
2775
+ existingEntry.filename = attachment.filename;
2776
+ }
2777
+ }
2778
+ continue;
2779
+ }
2780
+
2781
+ // Extract timezone information
2782
+ let timezone;
2783
+ const vtz = comp.getFirstSubcomponent('vtimezone');
2784
+ if (vtz) {
2785
+ const tz = new ical.Timezone(vtz);
2786
+ timezone = tz && tz.tzid;
2787
+ }
2788
+
2789
+ let startDate = event.startDate && event.startDate.toJSDate();
2790
+ let endDate = event.endDate && event.endDate.toJSDate();
2791
+
2792
+ // Store calendar event data
2793
+ calendarEventMap.set(
2794
+ event.uid,
2795
+ filterEmptyObjectValues({
2796
+ eventId: event.uid,
2797
+ attachment: attachment.id,
2798
+ method: attachment.method || eventMethodValue || null,
2799
+
2800
+ summary: event.summary || null,
2801
+ description: event.description || null,
2802
+ timezone: timezone || null,
2803
+ startDate: startDate ? startDate.toISOString() : null,
2804
+ endDate: endDate ? endDate.toISOString() : null,
2805
+ organizer: event.organizer && typeof event.organizer === 'string' ? event.organizer : null,
2806
+
2807
+ filename: attachment.filename,
2808
+ contentType: attachment.contentType,
2809
+ encoding: 'base64',
2810
+ content: attachment.content
2811
+ })
2812
+ );
2813
+ } catch (err) {
2814
+ this.logger.error({
2815
+ msg: 'Failed to parse calendar event',
2816
+ attachment: Object.assign({}, attachment, { content: `${contentBuf.length} bytes` }),
2817
+ err
2818
+ });
2819
+ }
2820
+ }
2821
+ }
2822
+ }
2823
+
2824
+ if (calendarEventMap && calendarEventMap.size) {
2825
+ // Convert map to array and set default filenames
2826
+ messageData.calendarEvents = Array.from(calendarEventMap.values()).map(calendarEvent => {
2827
+ if (!calendarEvent.filename) {
2828
+ switch (calendarEvent.method && calendarEvent.method.toUpperCase()) {
2829
+ case 'CANCEL':
2830
+ case 'REQUEST':
2831
+ calendarEvent.filename = 'invite.ics';
2832
+ break;
2833
+ default:
2834
+ calendarEvent.filename = 'event.ics';
2835
+ break;
2836
+ }
2837
+ }
2838
+ return calendarEvent;
2839
+ });
2840
+ }
2841
+ }
2842
+
2843
+ // Process with AI features for inbox messages
2844
+ if (messageData.messageSpecialUse === '\\Inbox') {
2845
+ let llmMessageData = Object.assign({ account: this.account }, messageData);
2846
+
2847
+ // Check if LLM processing is enabled
2848
+ let canUseLLM = await llmPreProcess.run(llmMessageData);
2849
+
2850
+ if (canUseLLM && (messageData.text.plain || messageData.text.html)) {
2851
+ // Generate email summary using AI
2852
+ if (canUseLLM.generateEmailSummary) {
2853
+ try {
2854
+ messageData.summary = await this.call({
2855
+ cmd: 'generateSummary',
2856
+ data: {
2857
+ message: {
2858
+ headers: Object.keys(messageData.headers || {}).map(key => ({ key, value: [].concat(messageData.headers[key] || []) })),
2859
+ attachments: messageData.attachments,
2860
+ from: messageData.from,
2861
+ subject: messageData.subject,
2862
+ text: messageData.text.plain,
2863
+ html: messageData.text.html
2864
+ },
2865
+ account: this.account
2866
+ },
2867
+ timeout: 2 * 60 * 1000
2868
+ });
2869
+
2870
+ if (messageData.summary) {
2871
+ // Clean up summary output
2872
+ for (let key of Object.keys(messageData.summary)) {
2873
+ // remove meta keys from output
2874
+ if (key.charAt(0) === '_' || messageData.summary[key] === '') {
2875
+ delete messageData.summary[key];
2876
+ }
2877
+ if (key === 'riskAssessment') {
2878
+ messageData.riskAssessment = messageData.summary.riskAssessment;
2879
+ delete messageData.summary.riskAssessment;
2880
+ }
2881
+ }
2882
+
2883
+ this.logger.trace({ msg: 'Fetched summary from OpenAI', summary: messageData.summary });
2884
+ }
2885
+
2886
+ await this.redis.del(`${REDIS_PREFIX}:openai:error`);
2887
+ } catch (err) {
2888
+ // Track OpenAI errors
2889
+ await this.redis.set(
2890
+ `${REDIS_PREFIX}:openai:error`,
2891
+ JSON.stringify({
2892
+ message: err.message,
2893
+ code: err.code,
2894
+ statusCode: err.statusCode,
2895
+ created: Date.now()
2896
+ })
2897
+ );
2898
+ this.logger.error({ msg: 'Failed to fetch summary from OpenAI', err });
2899
+ }
2900
+ }
2901
+
2902
+ // Generate embeddings for semantic search
2903
+ if (canUseLLM.generateEmbeddings) {
2904
+ try {
2905
+ messageData.embeddings = await this.call({
2906
+ cmd: 'generateEmbeddings',
2907
+ data: {
2908
+ message: {
2909
+ headers: Object.keys(messageData.headers || {}).map(key => ({ key, value: [].concat(messageData.headers[key] || []) })),
2910
+ attachments: messageData.attachments,
2911
+ from: messageData.from,
2912
+ subject: messageData.subject,
2913
+ text: messageData.text.plain,
2914
+ html: messageData.text.html
2915
+ },
2916
+ account: this.account
2917
+ },
2918
+ timeout: 2 * 60 * 1000
2919
+ });
2920
+ } catch (err) {
2921
+ await this.redis.set(
2922
+ `${REDIS_PREFIX}:openai:error`,
2923
+ JSON.stringify({
2924
+ message: err.message,
2925
+ code: err.code,
2926
+ statusCode: err.statusCode,
2927
+ time: Date.now()
2928
+ })
2929
+ );
2930
+ this.logger.error({ msg: 'Failed to fetch embeddings OpenAI', err });
2931
+ }
2932
+ }
2933
+ }
2934
+ }
2935
+
2936
+ // Convert message HTML to web safe HTML
2937
+ let notifyWebSafeHtml = await settings.get('notifyWebSafeHtml');
2938
+ if (notifyWebSafeHtml && messageData.text && (messageData.text.html || messageData.text.plain)) {
2939
+ // convert to web safe
2940
+ messageData.text._generatedHtml = mimeHtml({
2941
+ html: messageData.text.html,
2942
+ text: messageData.text.plain
2943
+ });
2944
+
2945
+ // embed inline images as data URIs
2946
+ if (messageData.text.html && messageData.attachments) {
2947
+ let attachmentList = new Map();
2948
+
2949
+ // Find CID references in HTML
2950
+ for (let attachment of messageData.attachments) {
2951
+ let contentId = attachment.contentId && attachment.contentId.replace(/^<|>$/g, '');
2952
+ if (contentId && messageData.text.html.indexOf(contentId) >= 0) {
2953
+ if (attachment.content) {
2954
+ // already downloaded in a previous step
2955
+ continue;
2956
+ } else {
2957
+ attachment.content = (await this.getAttachment(attachment.id))?.data?.toString('base64');
2958
+ }
2959
+
2960
+ attachmentList.set(contentId, {
2961
+ attachment,
2962
+ content: attachment.content || null
2963
+ });
2964
+ }
2965
+ }
2966
+
2967
+ // Replace CID references with data URIs
2968
+ if (attachmentList.size) {
2969
+ messageData.text.html = messageData.text.html.replace(/\bcid:([^"'\s>]+)/g, (fullMatch, cidMatch) => {
2970
+ if (attachmentList.has(cidMatch)) {
2971
+ let { attachment, content } = attachmentList.get(cidMatch);
2972
+ if (content) {
2973
+ return `data:${attachment.contentType || 'application/octet-stream'};base64,${content}`;
2974
+ }
2975
+ }
2976
+ return fullMatch;
2977
+ });
2978
+ }
2979
+ }
2980
+
2981
+ messageData.text.webSafe = true;
2982
+ }
2983
+
2984
+ // Filter headers based on request
2985
+ // we might have fetched more headers than was asked for, so filter out all the unneeded ones
2986
+ if (options.headers && Array.isArray(requestedHeaders)) {
2987
+ let filteredHeaders = {};
2988
+ for (let key of Object.keys(messageData.headers || {})) {
2989
+ if (requestedHeaders.includes(key)) {
2990
+ filteredHeaders[key] = messageData.headers[key];
2991
+ }
2992
+ }
2993
+ messageData.headers = filteredHeaders;
2994
+ } else if (options.headers && requestedHeaders === false) {
2995
+ delete messageData.headers;
2996
+ }
2997
+
2998
+ let path = messageData.path || this.path;
2999
+ let specialUse = messageData.path ? messageData.messageSpecialUse : this.listingEntry.specialUse;
3000
+
3001
+ if (messageData.path) {
3002
+ // unset path from the message level
3003
+ messageData.path = undefined;
3004
+ }
3005
+
3006
+ // Send new message notification
3007
+ await this.notify(
3008
+ {
3009
+ path,
3010
+ specialUse
3011
+ },
3012
+ MESSAGE_NEW_NOTIFY,
3013
+ messageData
3014
+ );
3015
+
3016
+ // Send additional notifications for bounces and complaints
3017
+ if (bounceNotifyInfo) {
3018
+ // send bounce notification _after_ bounce email notification
3019
+ await this.notify(false, EMAIL_BOUNCE_NOTIFY, bounceNotifyInfo);
3020
+ }
3021
+
3022
+ if (complaintNotifyInfo) {
3023
+ // send complaint notification _after_ complaint email notification
3024
+ await this.notify(false, EMAIL_COMPLAINT_NOTIFY, complaintNotifyInfo);
3025
+ }
3026
+ }
3027
+
3028
+ /**
3029
+ * Checks if message might be an abuse complaint based on headers and content
3030
+ * @param {Object} messageData - Message data
3031
+ * @returns {boolean} True if message might be a complaint
3032
+ */
3033
+ mightBeAComplaint(messageData) {
3034
+ if (messageData.messageSpecialUse !== '\\Inbox') {
3035
+ return false;
3036
+ }
3037
+
3038
+ let hasEmbeddedMessage = false;
3039
+ for (let attachment of messageData.attachments || []) {
3040
+ // ARF format indicator
3041
+ if (attachment.contentType === 'message/feedback-report') {
3042
+ return true;
3043
+ }
3044
+
3045
+ if (['message/rfc822', 'message/rfc822-headers'].includes(attachment.contentType)) {
3046
+ hasEmbeddedMessage = true;
3047
+ }
3048
+ }
3049
+
3050
+ let fromAddress = (messageData.from && messageData.from.address) || '';
3051
+
3052
+ // Hotmail-specific complaint format
3053
+ if (hasEmbeddedMessage && fromAddress === 'staff@hotmail.com' && /complaint/i.test(messageData.subject)) {
3054
+ return true;
3055
+ }
3056
+
3057
+ return false;
3058
+ }
3059
+
3060
+ /**
3061
+ * Checks if message is a delivery status notification
3062
+ * @param {Object} messageData - Message data
3063
+ * @returns {boolean} True if message is a DSN
3064
+ */
3065
+ mightBeDSNResponse(messageData) {
3066
+ if (messageData.messageSpecialUse !== '\\Inbox') {
3067
+ return false;
3068
+ }
3069
+
3070
+ // Check for multipart/report with delivery-status
3071
+ if (messageData.headers && messageData.headers['content-type'] && messageData.headers['content-type'].length) {
3072
+ let parsedContentType = libmime.parseHeaderValue(messageData.headers['content-type'].at(-1));
3073
+ if (
3074
+ parsedContentType &&
3075
+ parsedContentType.value &&
3076
+ parsedContentType.value.toLowerCase().trim() === 'multipart/report' &&
3077
+ parsedContentType.params['report-type'] === 'delivery-status'
3078
+ ) {
3079
+ return true;
3080
+ }
3081
+ }
3082
+
3083
+ return false;
3084
+ }
3085
+
3086
+ /**
3087
+ * Checks if message might be a bounce based on various heuristics
3088
+ * @param {Object} messageData - Message data
3089
+ * @returns {boolean} True if message might be a bounce
3090
+ */
3091
+ mightBeABounce(messageData) {
3092
+ if (!['\\Inbox', '\\Junk'].includes(messageData.messageSpecialUse)) {
3093
+ return false;
3094
+ }
3095
+
3096
+ if (messageData.deliveryReport) {
3097
+ // already processed
3098
+ return false;
3099
+ }
3100
+
3101
+ let name = (messageData.from && messageData.from.name) || '';
3102
+ let address = (messageData.from && messageData.from.address) || '';
3103
+
3104
+ // Common bounce sender names
3105
+ if (/Mail Delivery System|Mail Delivery Subsystem|Internet Mail Delivery/i.test(name)) {
3106
+ return true;
3107
+ }
3108
+
3109
+ // Common bounce addresses
3110
+ if (/mailer-daemon@|postmaster@/i.test(address)) {
3111
+ return true;
3112
+ }
3113
+
3114
+ // Exchange-style bounce
3115
+ if (/^Undeliverable:/.test(messageData.subject) && messageData.headers?.['auto-submitted']) {
3116
+ return true;
3117
+ }
3118
+
3119
+ // Check for delivery-status attachment with undeliverable subject
3120
+ let hasDeliveryStatus = false;
3121
+ for (let attachment of messageData.attachments || []) {
3122
+ if (attachment.contentType === 'message/delivery-status') {
3123
+ hasDeliveryStatus = true;
3124
+ }
3125
+ }
3126
+
3127
+ if (hasDeliveryStatus && /Undeliver(able|ed)/i.test(messageData.subject)) {
3128
+ return true;
3129
+ }
3130
+
3131
+ return false;
3132
+ }
3133
+
3134
+ /**
3135
+ * Submits a message for SMTP delivery
3136
+ * Handles authentication, connection pooling, and error tracking
3137
+ * @param {Object} data - Message data with raw content and metadata
3138
+ * @returns {Object} Delivery result with response and messageId
3139
+ */
3140
+ async submitMessage(data) {
3141
+ let accountData = await this.accountObject.loadAccountData();
3142
+
3143
+ // Verify SMTP capability
3144
+ if (!accountData.smtp && !accountData.oauth2 && !data.gateway) {
3145
+ // can not make connection
3146
+ let err = new Error('SMTP configuration not found');
3147
+ err.code = 'SMTPUnavailable';
3148
+ err.statusCode = 404;
3149
+ throw err;
3150
+ }
3151
+
3152
+ // Load gateway configuration if specified
3153
+ let gatewayData;
3154
+ let gatewayObject;
3155
+ if (data.gateway) {
3156
+ gatewayObject = new Gateway({ gateway: data.gateway, redis: this.redis, secret: this.secret });
3157
+ try {
3158
+ gatewayData = await gatewayObject.loadGatewayData();
3159
+ } catch (err) {
3160
+ this.logger.info({ msg: 'Failed to load gateway data', messageId: data.messageId, gateway: data.gateway, err });
3161
+ }
3162
+ }
3163
+
3164
+ let smtpConnectionConfig;
3165
+
3166
+ // Configure SMTP connection based on authentication type
3167
+ if (gatewayData) {
3168
+ // Use gateway configuration
3169
+ smtpConnectionConfig = {
3170
+ host: gatewayData.host,
3171
+ port: gatewayData.port,
3172
+ secure: gatewayData.secure
3173
+ };
3174
+ if (gatewayData.user || gatewayData.pass) {
3175
+ smtpConnectionConfig.auth = {
3176
+ user: gatewayData.user || '',
3177
+ pass: gatewayData.pass || ''
3178
+ };
3179
+ }
3180
+ } else if (accountData.oauth2 && accountData.oauth2.auth) {
3181
+ // load OAuth2 tokens
3182
+ const { oauth2User, accessToken, oauth2App } = await this.loadOAuth2AccountCredentials(accountData, this, 'smtp');
3183
+ const providerData = oauth2ProviderData(oauth2App.provider, oauth2App.cloud);
3184
+
3185
+ smtpConnectionConfig = Object.assign(
3186
+ {
3187
+ auth: {
3188
+ user: oauth2User,
3189
+ accessToken
3190
+ },
3191
+ resyncDelay: 900
3192
+ },
3193
+ providerData.smtp || {}
3194
+ );
3195
+ } else {
3196
+ // deep copy of smtp settings
3197
+ smtpConnectionConfig = JSON.parse(JSON.stringify(accountData.smtp));
3198
+ }
3199
+
3200
+ let { raw, hasBcc, envelope, messageId, queueId, reference, job: jobData } = data;
3201
+
3202
+ let smtpAuth = smtpConnectionConfig.auth;
3203
+ // If authentication server is set then it overrides authentication data
3204
+ if (smtpConnectionConfig.useAuthServer) {
3205
+ try {
3206
+ smtpAuth = await resolveCredentials(this.account, 'smtp');
3207
+ } catch (err) {
3208
+ err.authenticationFailed = true;
3209
+ this.logger.error({
3210
+ account: this.account,
3211
+ err
3212
+ });
3213
+ throw err;
3214
+ }
3215
+ }
3216
+
3217
+ // Select local address for outbound connection
3218
+ let { localAddress: address, name, addressSelector: selector } = await getLocalAddress(this.redis, 'smtp', this.account, data.localAddress);
3219
+ this.logger.info({
3220
+ msg: 'Selected local address',
3221
+ account: this.account,
3222
+ proto: 'SMTP',
3223
+ address,
3224
+ name,
3225
+ selector,
3226
+ requestedLocalAddress: data.localAddress
3227
+ });
3228
+
3229
+ // Configure SMTP logger
3230
+ let smtpLogger = {};
3231
+ let smtpSettings = Object.assign(
3232
+ {
3233
+ name,
3234
+ localAddress: address,
3235
+ transactionLog: true,
3236
+ logger: smtpLogger
3237
+ },
3238
+ smtpConnectionConfig
3239
+ );
3240
+
3241
+ // Set authentication
3242
+ if (smtpAuth) {
3243
+ smtpSettings.auth = {
3244
+ user: smtpAuth.user
3245
+ };
3246
+
3247
+ if (smtpAuth.accessToken) {
3248
+ smtpSettings.auth.type = 'OAuth2';
3249
+ smtpSettings.auth.accessToken = smtpAuth.accessToken;
3250
+ } else {
3251
+ smtpSettings.auth.pass = smtpAuth.pass;
3252
+ }
3253
+ }
3254
+
3255
+ // Configure TLS with defaults
3256
+ if (!smtpSettings.tls) {
3257
+ smtpSettings.tls = {};
3258
+ }
3259
+ for (let key of Object.keys(TLS_DEFAULTS)) {
3260
+ if (!(key in smtpSettings.tls)) {
3261
+ smtpSettings.tls[key] = TLS_DEFAULTS[key];
3262
+ }
3263
+ }
3264
+
3265
+ // Set up logger wrapper
3266
+ for (let level of ['trace', 'debug', 'info', 'warn', 'error', 'fatal']) {
3267
+ smtpLogger[level] = (data, message, ...args) => {
3268
+ if (args && args.length) {
3269
+ message = util.format(message, ...args);
3270
+ }
3271
+ data.msg = message;
3272
+ data.sub = 'nodemailer';
3273
+ if (typeof this.logger[level] === 'function') {
3274
+ this.logger[level](data);
3275
+ } else {
3276
+ this.logger.debug(data);
3277
+ }
3278
+ };
3279
+ }
3280
+
3281
+ // set up proxy if needed
3282
+ if (data.proxy) {
3283
+ smtpSettings.proxy = data.proxy;
3284
+ } else if (accountData.proxy) {
3285
+ smtpSettings.proxy = accountData.proxy;
3286
+ } else {
3287
+ let proxyUrl = await settings.get('proxyUrl');
3288
+ let proxyEnabled = await settings.get('proxyEnabled');
3289
+ if (proxyEnabled && proxyUrl && !smtpSettings.proxy) {
3290
+ smtpSettings.proxy = proxyUrl;
3291
+ }
3292
+ }
3293
+
3294
+ // Override EHLO hostname if configured
3295
+ if (accountData.smtpEhloName) {
3296
+ smtpSettings.name = accountData.smtpEhloName;
3297
+ }
3298
+
3299
+ // Verify job still exists
3300
+ const submitJobEntry = await this.submitQueue.getJob(jobData.id);
3301
+ if (!submitJobEntry) {
3302
+ // already failed?
3303
+ this.logger.error({
3304
+ msg: 'Submit job was not found',
3305
+ job: jobData.id
3306
+ });
3307
+ return false;
3308
+ }
3309
+
3310
+ // Handle certificate errors if configured
3311
+ const ignoreMailCertErrors = await settings.get('ignoreMailCertErrors');
3312
+ if (ignoreMailCertErrors && smtpSettings?.tls?.rejectUnauthorized !== false) {
3313
+ smtpSettings.tls = smtpSettings.tls || {};
3314
+ smtpSettings.tls.rejectUnauthorized = false;
3315
+ }
3316
+
3317
+ // Prepare network routing info for notifications
3318
+ const networkRouting = smtpSettings.localAddress || smtpSettings.proxy ? {} : null;
3319
+
3320
+ if (networkRouting && smtpSettings.localAddress) {
3321
+ networkRouting.localAddress = smtpSettings.localAddress;
3322
+ }
3323
+
3324
+ if (networkRouting && smtpSettings.proxy) {
3325
+ networkRouting.proxy = smtpSettings.proxy;
3326
+ }
3327
+
3328
+ if (networkRouting && smtpSettings.name) {
3329
+ networkRouting.name = smtpSettings.name;
3330
+ }
3331
+
3332
+ if (networkRouting && data.localAddress && data.localAddress !== networkRouting.localAddress) {
3333
+ networkRouting.requestedLocalAddress = data.localAddress;
3334
+ }
3335
+
3336
+ // Get or create SMTP transport from pool
3337
+ const transporter = getMailTransport(smtpSettings);
3338
+
3339
+ try {
3340
+ try {
3341
+ // try to update job progress
3342
+ await submitJobEntry.updateProgress({
3343
+ status: 'smtp-starting'
3344
+ });
3345
+ } catch (err) {
3346
+ // ignore
3347
+ }
3348
+
3349
+ // Send the message
3350
+ const info = await transporter.sendMail({
3351
+ envelope,
3352
+ messageId,
3353
+ // make sure that Bcc line is removed from the version sent to SMTP
3354
+ raw: !hasBcc ? raw : await removeBcc(raw),
3355
+ dsn: data.dsn || null
3356
+ });
3357
+
3358
+ // Store EHLO response for debugging
3359
+ if (info.ehlo) {
3360
+ await this.redis.hSetExists(this.getAccountKey(), 'smtpServerEhlo', JSON.stringify(info.ehlo));
3361
+ }
3362
+
3363
+ // special rules for MTA servers
3364
+
3365
+ let originalMessageId;
3366
+
3367
+ // Hotmail - extract actual Message-ID from response
3368
+ let hotmailMessageIdMatch = (info.response || '').toString().match(/^250 2.0.0 OK (<[^>]+\.prod\.outlook\.com>)/);
3369
+ if (hotmailMessageIdMatch && hotmailMessageIdMatch[1] !== info.messageId) {
3370
+ // MessageId was overridden
3371
+ originalMessageId = info.messageId;
3372
+ info.messageId = hotmailMessageIdMatch[1];
3373
+ }
3374
+
3375
+ // AWS SES - construct Message-ID from response
3376
+ let awsSesHostMatch = (smtpSettings.host || '').toString().match(/\.([^.]+)\.(amazonaws\.com|awsapps\.com)$/i);
3377
+ let awsSesMessageIdMatch = (info.response || '').toString().match(/^250 Ok ([0-9a-f-]+)$/);
3378
+ if (awsSesHostMatch && awsSesMessageIdMatch) {
3379
+ let region = awsSesHostMatch[1].toLowerCase().trim();
3380
+ let messageId = awsSesMessageIdMatch[1].toLowerCase().trim();
3381
+ if (region === 'us-east-1') {
3382
+ region = 'email';
3383
+ }
3384
+
3385
+ // MessageId was overridden
3386
+ originalMessageId = info.messageId;
3387
+ info.messageId = '<' + messageId + (!/@/.test(messageId) ? '@' + region + '.amazonses.com' : '') + '>';
3388
+ }
3389
+
3390
+ // done
3391
+
3392
+ try {
3393
+ // try to update job progress
3394
+ await submitJobEntry.updateProgress({
3395
+ status: 'smtp-completed',
3396
+
3397
+ response: info.response,
3398
+ messageId: info.messageId,
3399
+ originalMessageId
3400
+ });
3401
+ } catch (err) {
3402
+ // ignore
3403
+ }
3404
+
3405
+ // Send success notification
3406
+ await this.notify(false, EMAIL_SENT_NOTIFY, {
3407
+ messageId: info.messageId,
3408
+ originalMessageId,
3409
+ response: info.response,
3410
+ queueId,
3411
+ envelope,
3412
+ networkRouting
3413
+ });
3414
+
3415
+ // clean up possible cached SMTP error
3416
+ try {
3417
+ await this.redis.hset(
3418
+ this.getAccountKey(),
3419
+ 'smtpStatus',
3420
+ JSON.stringify({
3421
+ created: Date.now(),
3422
+ status: 'ok',
3423
+ response: info.response
3424
+ })
3425
+ );
3426
+ } catch (err) {
3427
+ // ignore?
3428
+ }
3429
+
3430
+ // The default is to copy message to Sent Mail folder
3431
+ let shouldCopy = !Object.prototype.hasOwnProperty.call(accountData, 'copy');
3432
+
3433
+ // Account specific setting
3434
+ if (typeof accountData.copy === 'boolean') {
3435
+ shouldCopy = accountData.copy;
3436
+ }
3437
+
3438
+ // Suppress uploads for Gmail and Outlook
3439
+ // Unfortunately, previous default schema for all added accounts was copy=true, so can't prefer account specific setting here
3440
+
3441
+ // Emails for delegated accounts will be uploaded as the sender is different.
3442
+ // SMTP is disabled for shared mailboxes, so we need to send using the main account.
3443
+ let skipIfOutlook = this.isOutlook && (!accountData.oauth2 || !accountData.oauth2.auth || !accountData.oauth2.auth.delegatedUser);
3444
+
3445
+ if ((this.isGmail || skipIfOutlook) && !gatewayData) {
3446
+ shouldCopy = false;
3447
+ }
3448
+
3449
+ // Message specific setting, overrides all other settings
3450
+ if (typeof data.copy === 'boolean') {
3451
+ shouldCopy = data.copy;
3452
+ }
3453
+
3454
+ // Check if IMAP is available
3455
+ if ((!accountData.imap && !accountData.oauth2) || (accountData.imap && accountData.imap.disabled)) {
3456
+ // IMAP is disabled for this account
3457
+ shouldCopy = false;
3458
+ }
3459
+
3460
+ let connectionOptions = { allowSecondary: true };
3461
+
3462
+ if (shouldCopy) {
3463
+ // NB! IMAP only
3464
+ // Upload the message to Sent Mail folder
3465
+
3466
+ try {
3467
+ this.checkIMAPConnection(connectionOptions);
3468
+
3469
+ // Find or use specified sent folder
3470
+ let sentMailbox =
3471
+ data.sentMailPath && typeof data.sentMailPath === 'string'
3472
+ ? {
3473
+ path: data.sentMailPath
3474
+ }
3475
+ : await this.getSpecialUseMailbox('\\Sent');
3476
+
3477
+ if (sentMailbox) {
3478
+ if (raw.buffer) {
3479
+ // convert from a Uint8Array to a Buffer
3480
+ raw = Buffer.from(raw);
3481
+ }
3482
+
3483
+ const connectionClient = await this.getImapConnection(connectionOptions, 'submitMessage');
3484
+
3485
+ // Upload message with Seen flag
3486
+ await connectionClient.append(sentMailbox.path, raw, ['\\Seen']);
3487
+
3488
+ // Return to IDLE if using primary connection
3489
+ if (connectionClient === this.imapClient && this.imapClient.mailbox && !this.imapClient.idling) {
3490
+ // force back to IDLE
3491
+ this.imapClient.idle().catch(err => {
3492
+ this.logger.error({ msg: 'IDLE error', err });
3493
+ });
3494
+ }
3495
+ }
3496
+ } catch (err) {
3497
+ this.logger.error({ msg: 'Failed to upload Sent mail', queueId, messageId, err });
3498
+ }
3499
+ }
3500
+
3501
+ // Add \Answered flag to referenced message if needed
3502
+ if (reference && reference.update) {
3503
+ try {
3504
+ this.checkIMAPConnection(connectionOptions);
3505
+ await this.updateMessage(
3506
+ reference.message,
3507
+ {
3508
+ flags: {
3509
+ add: ['\\Answered'].concat(reference.action === 'forward' ? '$Forwarded' : [])
3510
+ }
3511
+ },
3512
+ connectionOptions
3513
+ );
3514
+ } catch (err) {
3515
+ this.logger.error({ msg: 'Failed to update reference flags', queueId, messageId, reference, err });
3516
+ }
3517
+ }
3518
+
3519
+ // Update feedback key if provided
3520
+ if (data.feedbackKey) {
3521
+ await this.redis
3522
+ .multi()
3523
+ .hset(data.feedbackKey, 'success', 'true')
3524
+ .expire(1 * 60 * 60);
3525
+ }
3526
+
3527
+ // Update gateway usage stats
3528
+ if (gatewayData) {
3529
+ try {
3530
+ await gatewayObject.update({
3531
+ lastError: null,
3532
+ lastUse: new Date(),
3533
+ deliveries: { inc: 1 }
3534
+ });
3535
+ } catch (err) {
3536
+ this.logger.error({ msg: 'Failed to update gateway', queueId, messageId, reference, gateway: gatewayData.gateway, err });
3537
+ }
3538
+ }
3539
+
3540
+ return {
3541
+ response: info.response,
3542
+ messageId: info.messageId
3543
+ };
3544
+ } catch (err) {
3545
+ // Handle permanent failures
3546
+ if (err.responseCode >= 500 && jobData.opts?.attempts <= jobData.attemptsMade) {
3547
+ jobData.nextAttempt = false;
3548
+ }
3549
+
3550
+ // Track SMTP errors for diagnostics
3551
+ let smtpStatus = false;
3552
+ switch (err.code) {
3553
+ case 'ESOCKET':
3554
+ if (err.cert && err.reason) {
3555
+ smtpStatus = {
3556
+ description: `Certificate check for ${smtpSettings.host}:${smtpSettings.port} failed. ${err.reason}`
3557
+ };
3558
+ }
3559
+ break;
3560
+ case 'EMESSAGE':
3561
+ case 'ESTREAM':
3562
+ case 'EENVELOPE':
3563
+ // Ignore. Too generic or message related
3564
+ break;
3565
+ case 'ETIMEDOUT':
3566
+ // firewall?
3567
+ smtpStatus = {
3568
+ description: `Request timed out. Possibly a firewall issue or a wrong hostname/port (${smtpSettings.host}:${smtpSettings.port}).`
3569
+ };
3570
+ break;
3571
+ case 'ETLS':
3572
+ smtpStatus = {
3573
+ description: `EmailEngine failed to set up TLS session with ${smtpSettings.host}:${smtpSettings.port}`
3574
+ };
3575
+ break;
3576
+ case 'EDNS':
3577
+ smtpStatus = {
3578
+ description: `EmailEngine failed to resolve DNS record for ${smtpSettings.host}`
3579
+ };
3580
+ break;
3581
+ case 'ECONNECTION':
3582
+ smtpStatus = {
3583
+ description: `EmailEngine failed to establish TCP connection against ${smtpSettings.host}`
3584
+ };
3585
+ break;
3586
+ case 'EPROTOCOL':
3587
+ smtpStatus = {
3588
+ description: `Unexpected response from ${smtpSettings.host}`
3589
+ };
3590
+ break;
3591
+ case 'EAUTH':
3592
+ smtpStatus = {
3593
+ description: `Authentication failed`
3594
+ };
3595
+ break;
3596
+ }
3597
+
3598
+ if (smtpStatus) {
3599
+ let lastError = Object.assign(
3600
+ {
3601
+ created: Date.now(),
3602
+ status: 'error',
3603
+ response: err.response,
3604
+ responseCode: err.responseCode,
3605
+ code: err.code,
3606
+ command: err.command,
3607
+ networkRouting
3608
+ },
3609
+ smtpStatus
3610
+ );
3611
+
3612
+ // store SMTP error for the account
3613
+ try {
3614
+ await this.redis.hset(this.getAccountKey(), 'smtpStatus', JSON.stringify(lastError));
3615
+ } catch (err) {
3616
+ // ignore?
3617
+ }
3618
+
3619
+ // Update gateway error status
3620
+ if (gatewayData) {
3621
+ try {
3622
+ await gatewayObject.update({
3623
+ lastError,
3624
+ lastUse: new Date()
3625
+ });
3626
+ } catch (err) {
3627
+ // ignore?
3628
+ }
3629
+ }
3630
+ }
3631
+
3632
+ // Update feedback key with failure status
3633
+ if (data.feedbackKey && !jobData.nextAttempt) {
3634
+ await this.redis
3635
+ .multi()
3636
+ .hset(data.feedbackKey, 'success', 'false')
3637
+ .hset(data.feedbackKey, 'error', ((smtpStatus && smtpStatus.description) || '').toString() || 'Failed to send email')
3638
+ .expire(data.feedbackKey, 1 * 60 * 60)
3639
+ .exec();
3640
+ }
3641
+
3642
+ // Send delivery error notification
3643
+ await this.notify(false, EMAIL_DELIVERY_ERROR_NOTIFY, {
3644
+ queueId,
3645
+ envelope,
3646
+
3647
+ messageId: data.messageId,
3648
+
3649
+ error: err.message,
3650
+ errorCode: err.code,
3651
+
3652
+ smtpResponse: err.response,
3653
+ smtpResponseCode: err.responseCode,
3654
+ smtpCommand: err.command,
3655
+
3656
+ networkRouting,
3657
+
3658
+ job: jobData
3659
+ });
3660
+
3661
+ // Enhance error with additional context
3662
+ err.code = err.code || 'SubmitFail';
3663
+ err.statusCode = Number(err.responseCode) || null;
3664
+
3665
+ err.info = { networkRouting };
3666
+
3667
+ throw err;
3668
+ }
3669
+ }
3670
+
3671
+ // stub - to be implemented by subclasses
3672
+ async listSignatures() {
3673
+ return { signatures: [], signaturesSupported: false };
3674
+ }
3675
+ }
3676
+
3677
+ module.exports = { BaseClient, metricsMeta };