@open-mercato/core 0.6.5-develop.4384.1.ce2ec6eaaa → 0.6.5-develop.4393.1.de282b5dfd

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 (533) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/channel_ingest_dead_letter/index.js +25 -0
  3. package/dist/generated/entities/channel_ingest_dead_letter/index.js.map +7 -0
  4. package/dist/generated/entities/channel_thread_mapping/index.js +25 -0
  5. package/dist/generated/entities/channel_thread_mapping/index.js.map +7 -0
  6. package/dist/generated/entities/channel_thread_token/index.js +17 -0
  7. package/dist/generated/entities/channel_thread_token/index.js.map +7 -0
  8. package/dist/generated/entities/communication_channel/index.js +43 -0
  9. package/dist/generated/entities/communication_channel/index.js.map +7 -0
  10. package/dist/generated/entities/customer_interaction/index.js +4 -0
  11. package/dist/generated/entities/customer_interaction/index.js.map +2 -2
  12. package/dist/generated/entities/external_conversation/index.js +25 -0
  13. package/dist/generated/entities/external_conversation/index.js.map +7 -0
  14. package/dist/generated/entities/external_message/index.js +25 -0
  15. package/dist/generated/entities/external_message/index.js.map +7 -0
  16. package/dist/generated/entities/integration_credentials/index.js +3 -1
  17. package/dist/generated/entities/integration_credentials/index.js.map +2 -2
  18. package/dist/generated/entities/message/index.js +2 -0
  19. package/dist/generated/entities/message/index.js.map +2 -2
  20. package/dist/generated/entities/message_channel_link/index.js +33 -0
  21. package/dist/generated/entities/message_channel_link/index.js.map +7 -0
  22. package/dist/generated/entities/message_reaction/index.js +25 -0
  23. package/dist/generated/entities/message_reaction/index.js.map +7 -0
  24. package/dist/generated/entities.ids.generated.js +11 -0
  25. package/dist/generated/entities.ids.generated.js.map +2 -2
  26. package/dist/generated/entity-fields-registry.js +117 -0
  27. package/dist/generated/entity-fields-registry.js.map +2 -2
  28. package/dist/helpers/integration/authFixtures.js +2 -1
  29. package/dist/helpers/integration/authFixtures.js.map +2 -2
  30. package/dist/helpers/integration/communicationChannelsFixtures.js +58 -0
  31. package/dist/helpers/integration/communicationChannelsFixtures.js.map +7 -0
  32. package/dist/modules/communication_channels/acl.js +47 -0
  33. package/dist/modules/communication_channels/acl.js.map +7 -0
  34. package/dist/modules/communication_channels/api/delete/channels/[id]/route.js +133 -0
  35. package/dist/modules/communication_channels/api/delete/channels/[id]/route.js.map +7 -0
  36. package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js +113 -0
  37. package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js.map +7 -0
  38. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +138 -0
  39. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +7 -0
  40. package/dist/modules/communication_channels/api/get/channels/[id]/route.js +93 -0
  41. package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +7 -0
  42. package/dist/modules/communication_channels/api/get/channels/route.js +96 -0
  43. package/dist/modules/communication_channels/api/get/channels/route.js.map +7 -0
  44. package/dist/modules/communication_channels/api/get/me/channels/route.js +82 -0
  45. package/dist/modules/communication_channels/api/get/me/channels/route.js.map +7 -0
  46. package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +274 -0
  47. package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js.map +7 -0
  48. package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js +168 -0
  49. package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js.map +7 -0
  50. package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js +143 -0
  51. package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js.map +7 -0
  52. package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js +127 -0
  53. package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js.map +7 -0
  54. package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js +99 -0
  55. package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js.map +7 -0
  56. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +197 -0
  57. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +7 -0
  58. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +124 -0
  59. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +7 -0
  60. package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js +120 -0
  61. package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js.map +7 -0
  62. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +157 -0
  63. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +7 -0
  64. package/dist/modules/communication_channels/api/post/send-as-user/route.js +115 -0
  65. package/dist/modules/communication_channels/api/post/send-as-user/route.js.map +7 -0
  66. package/dist/modules/communication_channels/api/post/test-seed/route.js +217 -0
  67. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +7 -0
  68. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +175 -0
  69. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +7 -0
  70. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +123 -0
  71. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +7 -0
  72. package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js +117 -0
  73. package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js.map +7 -0
  74. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +180 -0
  75. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +7 -0
  76. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js +36 -0
  77. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js.map +7 -0
  78. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +107 -0
  79. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +7 -0
  80. package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js +38 -0
  81. package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js.map +7 -0
  82. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +727 -0
  83. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +7 -0
  84. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js +38 -0
  85. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js.map +7 -0
  86. package/dist/modules/communication_channels/commands/connect-credential-channel.js +154 -0
  87. package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +7 -0
  88. package/dist/modules/communication_channels/commands/delete-channel.js +137 -0
  89. package/dist/modules/communication_channels/commands/delete-channel.js.map +7 -0
  90. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +400 -0
  91. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +7 -0
  92. package/dist/modules/communication_channels/commands/disconnect-channel.js +163 -0
  93. package/dist/modules/communication_channels/commands/disconnect-channel.js.map +7 -0
  94. package/dist/modules/communication_channels/commands/ingest-inbound-message.js +413 -0
  95. package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +7 -0
  96. package/dist/modules/communication_channels/commands/interceptors.js +68 -0
  97. package/dist/modules/communication_channels/commands/interceptors.js.map +7 -0
  98. package/dist/modules/communication_channels/commands/process-inbound-reaction.js +198 -0
  99. package/dist/modules/communication_channels/commands/process-inbound-reaction.js.map +7 -0
  100. package/dist/modules/communication_channels/commands/push-register.js +146 -0
  101. package/dist/modules/communication_channels/commands/push-register.js.map +7 -0
  102. package/dist/modules/communication_channels/commands/push-renew.js +23 -0
  103. package/dist/modules/communication_channels/commands/push-renew.js.map +7 -0
  104. package/dist/modules/communication_channels/commands/push-unregister.js +108 -0
  105. package/dist/modules/communication_channels/commands/push-unregister.js.map +7 -0
  106. package/dist/modules/communication_channels/commands/queue-import-history.js +113 -0
  107. package/dist/modules/communication_channels/commands/queue-import-history.js.map +7 -0
  108. package/dist/modules/communication_channels/commands/reassign-conversation.js +193 -0
  109. package/dist/modules/communication_channels/commands/reassign-conversation.js.map +7 -0
  110. package/dist/modules/communication_channels/commands/set-primary-channel.js +114 -0
  111. package/dist/modules/communication_channels/commands/set-primary-channel.js.map +7 -0
  112. package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js +260 -0
  113. package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js.map +7 -0
  114. package/dist/modules/communication_channels/data/enrichers.js +286 -0
  115. package/dist/modules/communication_channels/data/enrichers.js.map +7 -0
  116. package/dist/modules/communication_channels/data/entities.js +447 -0
  117. package/dist/modules/communication_channels/data/entities.js.map +7 -0
  118. package/dist/modules/communication_channels/data/extensions.js +67 -0
  119. package/dist/modules/communication_channels/data/extensions.js.map +7 -0
  120. package/dist/modules/communication_channels/data/validators.js +123 -0
  121. package/dist/modules/communication_channels/data/validators.js.map +7 -0
  122. package/dist/modules/communication_channels/di.js +35 -0
  123. package/dist/modules/communication_channels/di.js.map +7 -0
  124. package/dist/modules/communication_channels/encryption.js +12 -0
  125. package/dist/modules/communication_channels/encryption.js.map +7 -0
  126. package/dist/modules/communication_channels/events.js +124 -0
  127. package/dist/modules/communication_channels/events.js.map +7 -0
  128. package/dist/modules/communication_channels/index.js +20 -0
  129. package/dist/modules/communication_channels/index.js.map +7 -0
  130. package/dist/modules/communication_channels/lib/access-control.js +43 -0
  131. package/dist/modules/communication_channels/lib/access-control.js.map +7 -0
  132. package/dist/modules/communication_channels/lib/adapter-compat.js +36 -0
  133. package/dist/modules/communication_channels/lib/adapter-compat.js.map +7 -0
  134. package/dist/modules/communication_channels/lib/adapter-registry-singleton.js +22 -0
  135. package/dist/modules/communication_channels/lib/adapter-registry-singleton.js.map +7 -0
  136. package/dist/modules/communication_channels/lib/adapter.js +1 -0
  137. package/dist/modules/communication_channels/lib/adapter.js.map +7 -0
  138. package/dist/modules/communication_channels/lib/connect-channel.js +95 -0
  139. package/dist/modules/communication_channels/lib/connect-channel.js.map +7 -0
  140. package/dist/modules/communication_channels/lib/contact-resolver.js +79 -0
  141. package/dist/modules/communication_channels/lib/contact-resolver.js.map +7 -0
  142. package/dist/modules/communication_channels/lib/credential-refresh.js +97 -0
  143. package/dist/modules/communication_channels/lib/credential-refresh.js.map +7 -0
  144. package/dist/modules/communication_channels/lib/dead-letter.js +62 -0
  145. package/dist/modules/communication_channels/lib/dead-letter.js.map +7 -0
  146. package/dist/modules/communication_channels/lib/email-capabilities.js +47 -0
  147. package/dist/modules/communication_channels/lib/email-capabilities.js.map +7 -0
  148. package/dist/modules/communication_channels/lib/email-contact.js +14 -0
  149. package/dist/modules/communication_channels/lib/email-contact.js.map +7 -0
  150. package/dist/modules/communication_channels/lib/email-mime.js +259 -0
  151. package/dist/modules/communication_channels/lib/email-mime.js.map +7 -0
  152. package/dist/modules/communication_channels/lib/error-classification.js +101 -0
  153. package/dist/modules/communication_channels/lib/error-classification.js.map +7 -0
  154. package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js +185 -0
  155. package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js.map +7 -0
  156. package/dist/modules/communication_channels/lib/mutation-guards.js +114 -0
  157. package/dist/modules/communication_channels/lib/mutation-guards.js.map +7 -0
  158. package/dist/modules/communication_channels/lib/oauth-client-config.js +32 -0
  159. package/dist/modules/communication_channels/lib/oauth-client-config.js.map +7 -0
  160. package/dist/modules/communication_channels/lib/oauth-state.js +128 -0
  161. package/dist/modules/communication_channels/lib/oauth-state.js.map +7 -0
  162. package/dist/modules/communication_channels/lib/oauth-token.js +45 -0
  163. package/dist/modules/communication_channels/lib/oauth-token.js.map +7 -0
  164. package/dist/modules/communication_channels/lib/pg-errors.js +11 -0
  165. package/dist/modules/communication_channels/lib/pg-errors.js.map +7 -0
  166. package/dist/modules/communication_channels/lib/provider-health.js +24 -0
  167. package/dist/modules/communication_channels/lib/provider-health.js.map +7 -0
  168. package/dist/modules/communication_channels/lib/push-state.js +19 -0
  169. package/dist/modules/communication_channels/lib/push-state.js.map +7 -0
  170. package/dist/modules/communication_channels/lib/queue.js +54 -0
  171. package/dist/modules/communication_channels/lib/queue.js.map +7 -0
  172. package/dist/modules/communication_channels/lib/reaction-processor-types.js +5 -0
  173. package/dist/modules/communication_channels/lib/reaction-processor-types.js.map +7 -0
  174. package/dist/modules/communication_channels/lib/reaction-semantics.js +11 -0
  175. package/dist/modules/communication_channels/lib/reaction-semantics.js.map +7 -0
  176. package/dist/modules/communication_channels/lib/registry.js +67 -0
  177. package/dist/modules/communication_channels/lib/registry.js.map +7 -0
  178. package/dist/modules/communication_channels/lib/route-mutation-guard.js +43 -0
  179. package/dist/modules/communication_channels/lib/route-mutation-guard.js.map +7 -0
  180. package/dist/modules/communication_channels/lib/sanitize-channel-html.js +96 -0
  181. package/dist/modules/communication_channels/lib/sanitize-channel-html.js.map +7 -0
  182. package/dist/modules/communication_channels/lib/send-as-user.js +194 -0
  183. package/dist/modules/communication_channels/lib/send-as-user.js.map +7 -0
  184. package/dist/modules/communication_channels/lib/system-user.js +22 -0
  185. package/dist/modules/communication_channels/lib/system-user.js.map +7 -0
  186. package/dist/modules/communication_channels/lib/test-seed.js +68 -0
  187. package/dist/modules/communication_channels/lib/test-seed.js.map +7 -0
  188. package/dist/modules/communication_channels/lib/thread-matcher.js +263 -0
  189. package/dist/modules/communication_channels/lib/thread-matcher.js.map +7 -0
  190. package/dist/modules/communication_channels/lib/thread-token.js +219 -0
  191. package/dist/modules/communication_channels/lib/thread-token.js.map +7 -0
  192. package/dist/modules/communication_channels/lib/use-connect-channel.js +61 -0
  193. package/dist/modules/communication_channels/lib/use-connect-channel.js.map +7 -0
  194. package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js +50 -0
  195. package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js.map +7 -0
  196. package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js +19 -0
  197. package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js.map +7 -0
  198. package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js +13 -0
  199. package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js.map +7 -0
  200. package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js +17 -0
  201. package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js.map +7 -0
  202. package/dist/modules/communication_channels/notifications.client.js +51 -0
  203. package/dist/modules/communication_channels/notifications.client.js.map +7 -0
  204. package/dist/modules/communication_channels/notifications.handlers.js +53 -0
  205. package/dist/modules/communication_channels/notifications.handlers.js.map +7 -0
  206. package/dist/modules/communication_channels/notifications.js +56 -0
  207. package/dist/modules/communication_channels/notifications.js.map +7 -0
  208. package/dist/modules/communication_channels/setup.js +105 -0
  209. package/dist/modules/communication_channels/setup.js.map +7 -0
  210. package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js +71 -0
  211. package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js.map +7 -0
  212. package/dist/modules/communication_channels/subscribers/outbound-bridge.js +103 -0
  213. package/dist/modules/communication_channels/subscribers/outbound-bridge.js.map +7 -0
  214. package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js +51 -0
  215. package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js.map +7 -0
  216. package/dist/modules/communication_channels/widgets/components.js +7 -0
  217. package/dist/modules/communication_channels/widgets/components.js.map +7 -0
  218. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js +18 -0
  219. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js.map +7 -0
  220. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js +30 -0
  221. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js.map +7 -0
  222. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js +185 -0
  223. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js.map +7 -0
  224. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js +17 -0
  225. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js.map +7 -0
  226. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +44 -0
  227. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +7 -0
  228. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js +17 -0
  229. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js.map +7 -0
  230. package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js +23 -0
  231. package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js.map +7 -0
  232. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js +141 -0
  233. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js.map +7 -0
  234. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js +17 -0
  235. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js.map +7 -0
  236. package/dist/modules/communication_channels/widgets/injection-table.js +38 -0
  237. package/dist/modules/communication_channels/widgets/injection-table.js.map +7 -0
  238. package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js +25 -0
  239. package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js.map +7 -0
  240. package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js +19 -0
  241. package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js.map +7 -0
  242. package/dist/modules/communication_channels/widgets/notifications/index.js +7 -0
  243. package/dist/modules/communication_channels/widgets/notifications/index.js.map +7 -0
  244. package/dist/modules/communication_channels/workers/channel-import-history.js +185 -0
  245. package/dist/modules/communication_channels/workers/channel-import-history.js.map +7 -0
  246. package/dist/modules/communication_channels/workers/gmail-history-sync.js +154 -0
  247. package/dist/modules/communication_channels/workers/gmail-history-sync.js.map +7 -0
  248. package/dist/modules/communication_channels/workers/gmail-renew-watch.js +95 -0
  249. package/dist/modules/communication_channels/workers/gmail-renew-watch.js.map +7 -0
  250. package/dist/modules/communication_channels/workers/inbound-processor.js +56 -0
  251. package/dist/modules/communication_channels/workers/inbound-processor.js.map +7 -0
  252. package/dist/modules/communication_channels/workers/outbound-delivery.js +85 -0
  253. package/dist/modules/communication_channels/workers/outbound-delivery.js.map +7 -0
  254. package/dist/modules/communication_channels/workers/poll-channel.js +240 -0
  255. package/dist/modules/communication_channels/workers/poll-channel.js.map +7 -0
  256. package/dist/modules/communication_channels/workers/poll-tick.js +132 -0
  257. package/dist/modules/communication_channels/workers/poll-tick.js.map +7 -0
  258. package/dist/modules/communication_channels/workers/reaction-processor.js +192 -0
  259. package/dist/modules/communication_channels/workers/reaction-processor.js.map +7 -0
  260. package/dist/modules/customers/acl.js +18 -0
  261. package/dist/modules/customers/acl.js.map +2 -2
  262. package/dist/modules/customers/api/activities/route.js +9 -0
  263. package/dist/modules/customers/api/activities/route.js.map +2 -2
  264. package/dist/modules/customers/api/companies/[id]/route.js +18 -7
  265. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  266. package/dist/modules/customers/api/interactions/[id]/visibility/route.js +151 -0
  267. package/dist/modules/customers/api/interactions/[id]/visibility/route.js.map +7 -0
  268. package/dist/modules/customers/api/interactions/counts/route.js +6 -0
  269. package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
  270. package/dist/modules/customers/api/interactions/route.js +26 -7
  271. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  272. package/dist/modules/customers/api/people/[id]/email-threads/route.js +82 -0
  273. package/dist/modules/customers/api/people/[id]/email-threads/route.js.map +7 -0
  274. package/dist/modules/customers/api/people/[id]/emails/route.js +157 -0
  275. package/dist/modules/customers/api/people/[id]/emails/route.js.map +7 -0
  276. package/dist/modules/customers/api/people/[id]/route.js +12 -4
  277. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  278. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +10 -0
  279. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  280. package/dist/modules/customers/commands/deals.js +46 -5
  281. package/dist/modules/customers/commands/deals.js.map +2 -2
  282. package/dist/modules/customers/commands/interactions.js +16 -0
  283. package/dist/modules/customers/commands/interactions.js.map +2 -2
  284. package/dist/modules/customers/components/detail/ActivityCard.js +32 -0
  285. package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
  286. package/dist/modules/customers/components/detail/ComposeEmailDialog.js +242 -0
  287. package/dist/modules/customers/components/detail/ComposeEmailDialog.js.map +7 -0
  288. package/dist/modules/customers/components/detail/DealForm.js +2 -1
  289. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  290. package/dist/modules/customers/components/detail/DealsSection.js +10 -0
  291. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  292. package/dist/modules/customers/components/detail/EmailCardActions.js +179 -0
  293. package/dist/modules/customers/components/detail/EmailCardActions.js.map +7 -0
  294. package/dist/modules/customers/components/detail/EmailReplyForwardActions.js +52 -0
  295. package/dist/modules/customers/components/detail/EmailReplyForwardActions.js.map +7 -0
  296. package/dist/modules/customers/components/detail/PersonDetailTabs.js +7 -1
  297. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  298. package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js +366 -0
  299. package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js.map +7 -0
  300. package/dist/modules/customers/data/enrichers.js +133 -2
  301. package/dist/modules/customers/data/enrichers.js.map +2 -2
  302. package/dist/modules/customers/data/entities.js +18 -0
  303. package/dist/modules/customers/data/entities.js.map +2 -2
  304. package/dist/modules/customers/data/extensions.js +16 -0
  305. package/dist/modules/customers/data/extensions.js.map +7 -0
  306. package/dist/modules/customers/encryption.js +11 -0
  307. package/dist/modules/customers/encryption.js.map +2 -2
  308. package/dist/modules/customers/events.js +4 -1
  309. package/dist/modules/customers/events.js.map +2 -2
  310. package/dist/modules/customers/lib/findPeopleByAddresses.js +64 -0
  311. package/dist/modules/customers/lib/findPeopleByAddresses.js.map +7 -0
  312. package/dist/modules/customers/lib/kysely.js.map +2 -2
  313. package/dist/modules/customers/lib/link-channel-message-handler.js +303 -0
  314. package/dist/modules/customers/lib/link-channel-message-handler.js.map +7 -0
  315. package/dist/modules/customers/lib/personEmailThreads.js +205 -0
  316. package/dist/modules/customers/lib/personEmailThreads.js.map +7 -0
  317. package/dist/modules/customers/lib/visibilityFilter.js +51 -0
  318. package/dist/modules/customers/lib/visibilityFilter.js.map +7 -0
  319. package/dist/modules/customers/migrations/Migration20260527012240_customers.js +20 -0
  320. package/dist/modules/customers/migrations/Migration20260527012240_customers.js.map +7 -0
  321. package/dist/modules/customers/setup.js +2 -1
  322. package/dist/modules/customers/setup.js.map +2 -2
  323. package/dist/modules/customers/subscribers/link-channel-message-received.js +12 -0
  324. package/dist/modules/customers/subscribers/link-channel-message-received.js.map +7 -0
  325. package/dist/modules/customers/subscribers/link-channel-message-sent.js +12 -0
  326. package/dist/modules/customers/subscribers/link-channel-message-sent.js.map +7 -0
  327. package/dist/modules/integrations/data/entities.js +8 -1
  328. package/dist/modules/integrations/data/entities.js.map +2 -2
  329. package/dist/modules/integrations/lib/credentials-service.js +29 -14
  330. package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
  331. package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js +15 -0
  332. package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js.map +7 -0
  333. package/dist/modules/messages/commands/messages.js +70 -8
  334. package/dist/modules/messages/commands/messages.js.map +2 -2
  335. package/dist/modules/messages/components/ComposeMessagePageClient.js +24 -13
  336. package/dist/modules/messages/components/ComposeMessagePageClient.js.map +2 -2
  337. package/dist/modules/messages/components/MessageDetailPageClient.js +39 -2
  338. package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
  339. package/dist/modules/messages/components/MessagesInboxPageClient.js +1 -0
  340. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
  341. package/dist/modules/messages/data/entities.js +8 -1
  342. package/dist/modules/messages/data/entities.js.map +2 -2
  343. package/dist/modules/messages/migrations/Migration20260531130000.js +15 -0
  344. package/dist/modules/messages/migrations/Migration20260531130000.js.map +7 -0
  345. package/dist/modules/messages/widgets/injection-table.js +7 -0
  346. package/dist/modules/messages/widgets/injection-table.js.map +7 -0
  347. package/generated/entities/channel_ingest_dead_letter/index.ts +11 -0
  348. package/generated/entities/channel_thread_mapping/index.ts +11 -0
  349. package/generated/entities/channel_thread_token/index.ts +7 -0
  350. package/generated/entities/communication_channel/index.ts +20 -0
  351. package/generated/entities/customer_interaction/index.ts +2 -0
  352. package/generated/entities/external_conversation/index.ts +11 -0
  353. package/generated/entities/external_message/index.ts +11 -0
  354. package/generated/entities/integration_credentials/index.ts +1 -0
  355. package/generated/entities/message/index.ts +1 -0
  356. package/generated/entities/message_channel_link/index.ts +15 -0
  357. package/generated/entities/message_reaction/index.ts +11 -0
  358. package/generated/entities.ids.generated.ts +11 -0
  359. package/generated/entity-fields-registry.ts +117 -0
  360. package/package.json +9 -7
  361. package/src/helpers/integration/authFixtures.ts +4 -1
  362. package/src/helpers/integration/communicationChannelsFixtures.ts +124 -0
  363. package/src/modules/communication_channels/acl.ts +43 -0
  364. package/src/modules/communication_channels/api/delete/channels/[id]/route.ts +163 -0
  365. package/src/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.ts +143 -0
  366. package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +173 -0
  367. package/src/modules/communication_channels/api/get/channels/[id]/route.ts +111 -0
  368. package/src/modules/communication_channels/api/get/channels/route.ts +109 -0
  369. package/src/modules/communication_channels/api/get/me/channels/route.ts +100 -0
  370. package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +355 -0
  371. package/src/modules/communication_channels/api/post/channels/[id]/import-history/route.ts +206 -0
  372. package/src/modules/communication_channels/api/post/channels/[id]/poll-now/route.ts +174 -0
  373. package/src/modules/communication_channels/api/post/channels/[id]/push/register/route.ts +158 -0
  374. package/src/modules/communication_channels/api/post/channels/[id]/set-primary/route.ts +114 -0
  375. package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +241 -0
  376. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +134 -0
  377. package/src/modules/communication_channels/api/post/messages/[messageId]/reactions/route.ts +143 -0
  378. package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +192 -0
  379. package/src/modules/communication_channels/api/post/send-as-user/route.ts +125 -0
  380. package/src/modules/communication_channels/api/post/test-seed/route.ts +267 -0
  381. package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +227 -0
  382. package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +161 -0
  383. package/src/modules/communication_channels/api/put/threads/[threadId]/assign/route.ts +132 -0
  384. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.ts +34 -0
  385. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +250 -0
  386. package/src/modules/communication_channels/backend/communication_channels/channels/page.meta.ts +36 -0
  387. package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +137 -0
  388. package/src/modules/communication_channels/backend/profile/communication-channels/page.meta.ts +36 -0
  389. package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +907 -0
  390. package/src/modules/communication_channels/commands/connect-credential-channel.ts +243 -0
  391. package/src/modules/communication_channels/commands/delete-channel.ts +193 -0
  392. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +579 -0
  393. package/src/modules/communication_channels/commands/disconnect-channel.ts +241 -0
  394. package/src/modules/communication_channels/commands/ingest-inbound-message.ts +602 -0
  395. package/src/modules/communication_channels/commands/interceptors.ts +104 -0
  396. package/src/modules/communication_channels/commands/process-inbound-reaction.ts +265 -0
  397. package/src/modules/communication_channels/commands/push-register.ts +203 -0
  398. package/src/modules/communication_channels/commands/push-renew.ts +49 -0
  399. package/src/modules/communication_channels/commands/push-unregister.ts +168 -0
  400. package/src/modules/communication_channels/commands/queue-import-history.ts +180 -0
  401. package/src/modules/communication_channels/commands/reassign-conversation.ts +273 -0
  402. package/src/modules/communication_channels/commands/set-primary-channel.ts +154 -0
  403. package/src/modules/communication_channels/commands/toggle-outbound-reaction.ts +347 -0
  404. package/src/modules/communication_channels/data/enrichers.ts +413 -0
  405. package/src/modules/communication_channels/data/entities.ts +546 -0
  406. package/src/modules/communication_channels/data/extensions.ts +76 -0
  407. package/src/modules/communication_channels/data/validators.ts +138 -0
  408. package/src/modules/communication_channels/di.ts +40 -0
  409. package/src/modules/communication_channels/encryption.ts +44 -0
  410. package/src/modules/communication_channels/events.ts +122 -0
  411. package/src/modules/communication_channels/i18n/de.json +138 -0
  412. package/src/modules/communication_channels/i18n/en.json +138 -0
  413. package/src/modules/communication_channels/i18n/es.json +138 -0
  414. package/src/modules/communication_channels/i18n/pl.json +138 -0
  415. package/src/modules/communication_channels/index.ts +19 -0
  416. package/src/modules/communication_channels/lib/access-control.ts +110 -0
  417. package/src/modules/communication_channels/lib/adapter-compat.ts +57 -0
  418. package/src/modules/communication_channels/lib/adapter-registry-singleton.ts +35 -0
  419. package/src/modules/communication_channels/lib/adapter.ts +605 -0
  420. package/src/modules/communication_channels/lib/connect-channel.ts +163 -0
  421. package/src/modules/communication_channels/lib/contact-resolver.ts +162 -0
  422. package/src/modules/communication_channels/lib/credential-refresh.ts +197 -0
  423. package/src/modules/communication_channels/lib/dead-letter.ts +87 -0
  424. package/src/modules/communication_channels/lib/email-capabilities.ts +60 -0
  425. package/src/modules/communication_channels/lib/email-contact.ts +17 -0
  426. package/src/modules/communication_channels/lib/email-mime.ts +425 -0
  427. package/src/modules/communication_channels/lib/error-classification.ts +144 -0
  428. package/src/modules/communication_channels/lib/gmail-pubsub-jwt.ts +278 -0
  429. package/src/modules/communication_channels/lib/mutation-guards.ts +215 -0
  430. package/src/modules/communication_channels/lib/oauth-client-config.ts +79 -0
  431. package/src/modules/communication_channels/lib/oauth-state.ts +228 -0
  432. package/src/modules/communication_channels/lib/oauth-token.ts +81 -0
  433. package/src/modules/communication_channels/lib/pg-errors.ts +12 -0
  434. package/src/modules/communication_channels/lib/provider-health.ts +47 -0
  435. package/src/modules/communication_channels/lib/push-state.ts +38 -0
  436. package/src/modules/communication_channels/lib/queue.ts +66 -0
  437. package/src/modules/communication_channels/lib/reaction-processor-types.ts +51 -0
  438. package/src/modules/communication_channels/lib/reaction-semantics.ts +48 -0
  439. package/src/modules/communication_channels/lib/registry.ts +99 -0
  440. package/src/modules/communication_channels/lib/route-mutation-guard.ts +68 -0
  441. package/src/modules/communication_channels/lib/sanitize-channel-html.ts +129 -0
  442. package/src/modules/communication_channels/lib/send-as-user.ts +284 -0
  443. package/src/modules/communication_channels/lib/system-user.ts +74 -0
  444. package/src/modules/communication_channels/lib/test-seed.ts +140 -0
  445. package/src/modules/communication_channels/lib/thread-matcher.ts +430 -0
  446. package/src/modules/communication_channels/lib/thread-token.ts +355 -0
  447. package/src/modules/communication_channels/lib/use-connect-channel.ts +73 -0
  448. package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +2142 -0
  449. package/src/modules/communication_channels/migrations/Migration20260526134719_communication_channels.ts +55 -0
  450. package/src/modules/communication_channels/migrations/Migration20260527195446_communication_channels.ts +20 -0
  451. package/src/modules/communication_channels/migrations/Migration20260529231848_communication_channels.ts +13 -0
  452. package/src/modules/communication_channels/migrations/Migration20260531120000_communication_channels.ts +24 -0
  453. package/src/modules/communication_channels/notifications.client.ts +50 -0
  454. package/src/modules/communication_channels/notifications.handlers.ts +86 -0
  455. package/src/modules/communication_channels/notifications.ts +52 -0
  456. package/src/modules/communication_channels/setup.ts +158 -0
  457. package/src/modules/communication_channels/subscribers/channel-requires-reauth-notification.ts +118 -0
  458. package/src/modules/communication_channels/subscribers/outbound-bridge.ts +175 -0
  459. package/src/modules/communication_channels/subscribers/user-deleted-cascade.ts +100 -0
  460. package/src/modules/communication_channels/widgets/components.ts +36 -0
  461. package/src/modules/communication_channels/widgets/injection/channel-badge/widget.client.tsx +38 -0
  462. package/src/modules/communication_channels/widgets/injection/channel-badge/widget.ts +51 -0
  463. package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.tsx +278 -0
  464. package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.ts +24 -0
  465. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +63 -0
  466. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts +29 -0
  467. package/src/modules/communication_channels/widgets/injection/profile-channels-menu/widget.ts +34 -0
  468. package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.client.tsx +177 -0
  469. package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.ts +26 -0
  470. package/src/modules/communication_channels/widgets/injection-table.ts +47 -0
  471. package/src/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.tsx +48 -0
  472. package/src/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.tsx +45 -0
  473. package/src/modules/communication_channels/widgets/notifications/index.ts +2 -0
  474. package/src/modules/communication_channels/workers/channel-import-history.ts +252 -0
  475. package/src/modules/communication_channels/workers/gmail-history-sync.ts +223 -0
  476. package/src/modules/communication_channels/workers/gmail-renew-watch.ts +141 -0
  477. package/src/modules/communication_channels/workers/inbound-processor.ts +114 -0
  478. package/src/modules/communication_channels/workers/outbound-delivery.ts +155 -0
  479. package/src/modules/communication_channels/workers/poll-channel.ts +391 -0
  480. package/src/modules/communication_channels/workers/poll-tick.ts +210 -0
  481. package/src/modules/communication_channels/workers/reaction-processor.ts +264 -0
  482. package/src/modules/customers/acl.ts +18 -0
  483. package/src/modules/customers/api/activities/route.ts +13 -0
  484. package/src/modules/customers/api/companies/[id]/route.ts +21 -1
  485. package/src/modules/customers/api/interactions/[id]/visibility/route.ts +179 -0
  486. package/src/modules/customers/api/interactions/counts/route.ts +10 -0
  487. package/src/modules/customers/api/interactions/route.ts +51 -5
  488. package/src/modules/customers/api/people/[id]/email-threads/route.ts +92 -0
  489. package/src/modules/customers/api/people/[id]/emails/route.ts +184 -0
  490. package/src/modules/customers/api/people/[id]/route.ts +17 -2
  491. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +11 -1
  492. package/src/modules/customers/commands/deals.ts +65 -6
  493. package/src/modules/customers/commands/interactions.ts +30 -0
  494. package/src/modules/customers/components/detail/ActivityCard.tsx +48 -0
  495. package/src/modules/customers/components/detail/ComposeEmailDialog.tsx +329 -0
  496. package/src/modules/customers/components/detail/DealForm.tsx +2 -1
  497. package/src/modules/customers/components/detail/DealsSection.tsx +26 -0
  498. package/src/modules/customers/components/detail/EmailCardActions.tsx +258 -0
  499. package/src/modules/customers/components/detail/EmailReplyForwardActions.tsx +53 -0
  500. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +8 -1
  501. package/src/modules/customers/components/detail/PersonEmailThreadsTab.tsx +448 -0
  502. package/src/modules/customers/data/enrichers.ts +252 -1
  503. package/src/modules/customers/data/entities.ts +46 -1
  504. package/src/modules/customers/data/extensions.ts +26 -0
  505. package/src/modules/customers/encryption.ts +11 -0
  506. package/src/modules/customers/events.ts +4 -0
  507. package/src/modules/customers/i18n/de.json +41 -0
  508. package/src/modules/customers/i18n/en.json +41 -0
  509. package/src/modules/customers/i18n/es.json +41 -0
  510. package/src/modules/customers/i18n/pl.json +41 -0
  511. package/src/modules/customers/lib/findPeopleByAddresses.ts +107 -0
  512. package/src/modules/customers/lib/kysely.ts +16 -0
  513. package/src/modules/customers/lib/link-channel-message-handler.ts +571 -0
  514. package/src/modules/customers/lib/personEmailThreads.ts +325 -0
  515. package/src/modules/customers/lib/visibilityFilter.ts +152 -0
  516. package/src/modules/customers/migrations/.snapshot-open-mercato.json +61 -0
  517. package/src/modules/customers/migrations/Migration20260527012240_customers.ts +23 -0
  518. package/src/modules/customers/setup.ts +1 -0
  519. package/src/modules/customers/subscribers/link-channel-message-received.ts +21 -0
  520. package/src/modules/customers/subscribers/link-channel-message-sent.ts +21 -0
  521. package/src/modules/integrations/AGENTS.md +9 -0
  522. package/src/modules/integrations/data/entities.ts +21 -1
  523. package/src/modules/integrations/lib/credentials-service.ts +49 -13
  524. package/src/modules/integrations/migrations/.snapshot-open-mercato.json +26 -1
  525. package/src/modules/integrations/migrations/Migration20260526154136_integrations.ts +15 -0
  526. package/src/modules/messages/commands/messages.ts +101 -8
  527. package/src/modules/messages/components/ComposeMessagePageClient.tsx +17 -0
  528. package/src/modules/messages/components/MessageDetailPageClient.tsx +43 -0
  529. package/src/modules/messages/components/MessagesInboxPageClient.tsx +4 -0
  530. package/src/modules/messages/data/entities.ts +11 -0
  531. package/src/modules/messages/migrations/.snapshot-open-mercato.json +18 -0
  532. package/src/modules/messages/migrations/Migration20260531130000.ts +15 -0
  533. package/src/modules/messages/widgets/injection-table.ts +29 -0
@@ -0,0 +1,579 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import { z } from 'zod'
3
+ import type { EntityManager } from '@mikro-orm/postgresql'
4
+ import type { CommandHandler } from '@open-mercato/shared/lib/commands'
5
+ import { registerCommand } from '@open-mercato/shared/lib/commands'
6
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
7
+ import { emitCommunicationChannelsEvent } from '../events'
8
+ import { refreshCredentialsIfNeeded } from '../lib/credential-refresh'
9
+ import { classifyOutboundError, isReauthError } from '../lib/error-classification'
10
+ import {
11
+ buildBodyFooter,
12
+ buildReferencesId,
13
+ getOrCreateThreadToken,
14
+ } from '../lib/thread-token'
15
+ import { stringOrUndefined, stripBrackets } from '../lib/email-mime'
16
+ import type { ChannelAdapterRegistry } from '../lib/registry'
17
+ import { isUniqueViolation } from '../lib/pg-errors'
18
+ import { Message } from '../../messages/data/entities'
19
+ import {
20
+ ChannelThreadMapping,
21
+ CommunicationChannel,
22
+ ExternalMessage,
23
+ MessageChannelLink,
24
+ } from '../data/entities'
25
+
26
+ /**
27
+ * Sentinel — `Message.threadId` of an internal-only (no channel link) message
28
+ * has no matching `ChannelThreadMapping`. In that case outbound delivery is a no-op.
29
+ */
30
+ const NO_THREAD_MAPPING_RESULT = { status: 'no_channel_link' as const }
31
+
32
+ const deliverInputSchema = z.object({
33
+ messageId: z.string().uuid(),
34
+ scope: z.object({
35
+ tenantId: z.string().uuid(),
36
+ organizationId: z.string().uuid().nullable(),
37
+ }),
38
+ /**
39
+ * If true, force a credential refresh before sending — used by retry attempts
40
+ * after a 401 from the provider.
41
+ */
42
+ forceCredentialRefresh: z.boolean().optional(),
43
+ })
44
+
45
+ export type DeliverOutboundMessageInput = z.infer<typeof deliverInputSchema>
46
+
47
+ export type DeliverOutboundMessageResult =
48
+ | { status: 'no_channel_link' }
49
+ | { status: 'already_delivered'; messageId: string; channelLinkId: string }
50
+ | {
51
+ status: 'delivered'
52
+ messageId: string
53
+ channelLinkId: string
54
+ externalMessageId: string
55
+ providerKey: string
56
+ }
57
+ | {
58
+ status: 'failed'
59
+ messageId: string
60
+ channelLinkId: string
61
+ providerKey: string
62
+ error: string
63
+ transient: boolean
64
+ /**
65
+ * True when the failure was a 401 / invalid_grant — the channel was
66
+ * flipped to `requires_reauth`. The worker uses this to attempt one
67
+ * forced credential refresh before giving up.
68
+ */
69
+ requiresReauth: boolean
70
+ }
71
+
72
+ export const COMMUNICATION_CHANNELS_DELIVER_OUTBOUND_COMMAND_ID =
73
+ 'communication_channels.message.deliver_outbound'
74
+
75
+ type CredentialsServiceLike = {
76
+ resolve: (
77
+ integrationId: string,
78
+ scope: { organizationId: string; tenantId: string; userId?: string | null },
79
+ ) => Promise<Record<string, unknown> | null>
80
+ save?: (
81
+ integrationId: string,
82
+ credentials: Record<string, unknown>,
83
+ scope: { organizationId: string; tenantId: string; userId?: string | null },
84
+ ) => Promise<void>
85
+ }
86
+
87
+ type IntegrationLogLike = {
88
+ log?: (entry: Record<string, unknown>) => Promise<void> | void
89
+ warn?: (entry: Record<string, unknown>) => Promise<void> | void
90
+ error?: (entry: Record<string, unknown>) => Promise<void> | void
91
+ }
92
+
93
+ /**
94
+ * Outbound delivery command. Called from the outbound worker.
95
+ *
96
+ * Steps (SPEC-045d §7):
97
+ * 1. Re-fetch the Message by ID. Bail if internal-only (no ChannelThreadMapping).
98
+ * 2. Resolve channel + adapter + credentials.
99
+ * 3. Idempotently upsert a 'pending' MessageChannelLink (unique on `messageId`).
100
+ * Skip if a 'sent'/'delivered' link already exists.
101
+ * 4. Refresh credentials when OAuth + near expiry (or when caller forces it).
102
+ * 5. Call `adapter.convertOutbound(...)` → channel-native content.
103
+ * 6. Call `adapter.sendMessage(...)`.
104
+ * 7. On success: persist ExternalMessage + flip link to 'sent', emit `.message.sent`.
105
+ * 8. On failure: flip link to 'failed' + classify error, log to integrationLog,
106
+ * emit `.delivery_failed`. The worker decides whether to retry based on
107
+ * `result.transient`.
108
+ *
109
+ * Idempotency: the unique constraint on `message_channel_links.message_id`
110
+ * prevents the same Message being sent twice through the channel even if the
111
+ * subscriber fires repeatedly. Combined with the link's lifecycle state
112
+ * (pending → sent | failed), we get safe retries.
113
+ */
114
+ const deliverOutboundMessageCommand: CommandHandler<
115
+ DeliverOutboundMessageInput,
116
+ DeliverOutboundMessageResult
117
+ > = {
118
+ id: COMMUNICATION_CHANNELS_DELIVER_OUTBOUND_COMMAND_ID,
119
+ async execute(rawInput, ctx) {
120
+ const input = deliverInputSchema.parse(rawInput) as DeliverOutboundMessageInput
121
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
122
+ const dscope = {
123
+ tenantId: input.scope.tenantId,
124
+ organizationId: input.scope.organizationId ?? null,
125
+ }
126
+
127
+ // (1) Re-fetch Message by ID — never trust the event payload shape.
128
+ const message = await findOneWithDecryption(
129
+ em,
130
+ Message,
131
+ {
132
+ id: input.messageId,
133
+ tenantId: input.scope.tenantId,
134
+ organizationId: input.scope.organizationId ?? null,
135
+ deletedAt: null,
136
+ },
137
+ undefined,
138
+ dscope,
139
+ )
140
+ if (!message) {
141
+ // Message was deleted before we got to deliver. Treat as no-op.
142
+ return NO_THREAD_MAPPING_RESULT
143
+ }
144
+ if (!message.threadId) {
145
+ // Message has no thread → no channel routing.
146
+ return NO_THREAD_MAPPING_RESULT
147
+ }
148
+
149
+ // (1 cont.) Look up the channel link via ChannelThreadMapping.threadId.
150
+ const mapping = await findOneWithDecryption(
151
+ em,
152
+ ChannelThreadMapping,
153
+ {
154
+ messageThreadId: message.threadId,
155
+ tenantId: input.scope.tenantId,
156
+ organizationId: input.scope.organizationId ?? null,
157
+ },
158
+ undefined,
159
+ dscope,
160
+ )
161
+ if (!mapping) {
162
+ // Internal-only message — no channel delivery needed.
163
+ return NO_THREAD_MAPPING_RESULT
164
+ }
165
+
166
+ // (2) Channel + adapter.
167
+ const channel = await findOneWithDecryption(
168
+ em,
169
+ CommunicationChannel,
170
+ {
171
+ id: mapping.channelId,
172
+ tenantId: input.scope.tenantId,
173
+ organizationId: input.scope.organizationId ?? null,
174
+ deletedAt: null,
175
+ },
176
+ undefined,
177
+ dscope,
178
+ )
179
+ if (!channel) {
180
+ throw new Error(
181
+ `[internal] Channel ${mapping.channelId} not found for tenant ${input.scope.tenantId} (or has been deleted)`,
182
+ )
183
+ }
184
+ if (!channel.isActive) {
185
+ throw new Error(`[internal] Channel ${mapping.channelId} is inactive; refusing to deliver outbound`)
186
+ }
187
+
188
+ const adapterRegistry = ctx.container.resolve('channelAdapterRegistry') as ChannelAdapterRegistry
189
+ const adapter = adapterRegistry.get(channel.providerKey)
190
+ if (!adapter) {
191
+ throw new Error(
192
+ `[internal] No ChannelAdapter registered for providerKey '${channel.providerKey}'. ` +
193
+ 'Check that the provider package is enabled in modules.ts.',
194
+ )
195
+ }
196
+
197
+ // (3) Idempotently upsert a 'pending' MessageChannelLink.
198
+ let link = await findOneWithDecryption(
199
+ em,
200
+ MessageChannelLink,
201
+ {
202
+ messageId: message.id,
203
+ tenantId: input.scope.tenantId,
204
+ organizationId: input.scope.organizationId ?? null,
205
+ },
206
+ undefined,
207
+ dscope,
208
+ )
209
+ if (
210
+ link &&
211
+ (link.deliveryStatus === 'queued' ||
212
+ link.deliveryStatus === 'sent' ||
213
+ link.deliveryStatus === 'delivered' ||
214
+ link.deliveryStatus === 'read')
215
+ ) {
216
+ // Already sent (or accepted by the provider as 'queued') — short-circuit
217
+ // so a retried job does not re-invoke the adapter and double-send.
218
+ return {
219
+ status: 'already_delivered',
220
+ messageId: message.id,
221
+ channelLinkId: link.id,
222
+ }
223
+ }
224
+ if (!link) {
225
+ link = em.create(MessageChannelLink, {
226
+ messageId: message.id,
227
+ externalConversationId: mapping.externalConversationId,
228
+ providerKey: channel.providerKey,
229
+ channelType: channel.channelType,
230
+ direction: 'outbound',
231
+ deliveryStatus: 'pending',
232
+ tenantId: input.scope.tenantId,
233
+ organizationId: input.scope.organizationId ?? null,
234
+ })
235
+ em.persist(link)
236
+ try {
237
+ await em.flush()
238
+ } catch (flushErr) {
239
+ // Concurrency guard: the link lookup above is not atomic with this
240
+ // insert, so two deliveries of the same message (a replayed
241
+ // `messages.message.sent`, or an overlapping worker retry) can both
242
+ // reach here. The `message_channel_links_message_uq` index rejects the
243
+ // loser with a 23505. Defer to the winning job — re-read its link on a
244
+ // fresh fork and report `already_delivered` — instead of re-invoking the
245
+ // adapter (double send) or letting the raw error dead-letter the job.
246
+ if (isUniqueViolation(flushErr)) {
247
+ const winner = await findOneWithDecryption(
248
+ em.fork(),
249
+ MessageChannelLink,
250
+ {
251
+ messageId: message.id,
252
+ tenantId: input.scope.tenantId,
253
+ organizationId: input.scope.organizationId ?? null,
254
+ },
255
+ undefined,
256
+ dscope,
257
+ )
258
+ if (winner) {
259
+ return {
260
+ status: 'already_delivered',
261
+ messageId: message.id,
262
+ channelLinkId: winner.id,
263
+ }
264
+ }
265
+ }
266
+ throw flushErr
267
+ }
268
+ }
269
+
270
+ // (2 cont.) Decrypted credentials via the integrations module (if available).
271
+ let credentialsService: CredentialsServiceLike | null = null
272
+ try {
273
+ credentialsService = ctx.container.resolve(
274
+ 'integrationCredentialsService',
275
+ ) as CredentialsServiceLike
276
+ } catch {
277
+ credentialsService = null
278
+ }
279
+ // Per-user credentials scope: pass `channel.userId` so the credentials
280
+ // service returns this user's row, not whoever connected last. See
281
+ // review R2-C1 / N1 (2026-05-26).
282
+ const credentialsScope = {
283
+ tenantId: input.scope.tenantId,
284
+ organizationId: input.scope.organizationId ?? input.scope.tenantId,
285
+ userId: channel.userId ?? null,
286
+ }
287
+ let credentials: Record<string, unknown> = {}
288
+ if (channel.credentialsRef && credentialsService) {
289
+ try {
290
+ credentials =
291
+ (await credentialsService.resolve(`channel_${channel.providerKey}`, credentialsScope)) ?? {}
292
+ } catch {
293
+ credentials = {}
294
+ }
295
+ }
296
+
297
+ // (4) Credential refresh if OAuth + near expiry, or forced by retry.
298
+ let integrationLog: IntegrationLogLike | null = null
299
+ try {
300
+ integrationLog = ctx.container.resolve('integrationLogService') as IntegrationLogLike
301
+ } catch {
302
+ integrationLog = null
303
+ }
304
+ const refreshResult = await refreshCredentialsIfNeeded(
305
+ {
306
+ adapter,
307
+ channelId: channel.id,
308
+ credentials,
309
+ scope: credentialsScope,
310
+ force: Boolean(input.forceCredentialRefresh),
311
+ },
312
+ {
313
+ credentialsService,
314
+ logger: (...args) => console.warn(...args),
315
+ },
316
+ )
317
+ credentials = refreshResult.credentials
318
+
319
+ // (4b) Spec B — get-or-create the per-thread crypto token and inject it
320
+ // into the outbound payload BEFORE the adapter converts it. The token
321
+ // travels as both a `References` header (via channelMetadata.references)
322
+ // and a hidden body marker so inbound replies can be threaded back to
323
+ // this conversation even when the recipient's MUA strips RFC 5322
324
+ // headers. Idempotent on retry — same token reused per thread.
325
+ let threadToken: string | null = null
326
+ try {
327
+ const { token } = await getOrCreateThreadToken(em, {
328
+ tenantId: input.scope.tenantId,
329
+ organizationId: input.scope.organizationId ?? null,
330
+ messageThreadId: mapping.messageThreadId,
331
+ })
332
+ threadToken = token
333
+ } catch (tokenErr) {
334
+ // Token creation should never block a send — if it fails, the message
335
+ // still goes out, just without our thread-token attachment point.
336
+ // Threading falls back to the JWZ strategy via Message-Id headers.
337
+ console.warn(
338
+ '[communication_channels:deliver-outbound] thread token unavailable, proceeding without it:',
339
+ tokenErr instanceof Error ? tokenErr.message : tokenErr,
340
+ )
341
+ }
342
+
343
+ // (5) + (6) Convert + send.
344
+ try {
345
+ const outboundPayload = (link.channelPayload as Record<string, unknown> | null) ?? {}
346
+ const outboundHtml = typeof outboundPayload.html === 'string' ? outboundPayload.html : null
347
+ const outboundText = typeof outboundPayload.text === 'string' ? outboundPayload.text : null
348
+ let outboundBody = outboundHtml ?? outboundText ?? message.body ?? ''
349
+ const outboundBodyFormat = outboundHtml
350
+ ? 'html'
351
+ : ((message.bodyFormat as 'text' | 'markdown' | 'html') ?? 'text')
352
+
353
+ // Pre-existing channelMetadata.references (string[]) so we can extend
354
+ // it with the synthetic thread-token id without disturbing other refs
355
+ // (e.g. the recipient's own reply chain).
356
+ const baseMetadata = (link.channelMetadata as Record<string, unknown> | undefined) ?? {}
357
+ const existingRefs = Array.isArray(baseMetadata.references)
358
+ ? (baseMetadata.references as unknown[]).filter(
359
+ (value): value is string => typeof value === 'string',
360
+ )
361
+ : []
362
+ let mergedReferences = existingRefs
363
+ if (threadToken && !outboundBody.includes(`[OM:${threadToken}]`)) {
364
+ // Append the body footer for the corresponding format. The hidden
365
+ // HTML span is `display:none`; the plain-text trailer is a small
366
+ // bracketed marker on its own line. Both survive most reply clients.
367
+ const footer = buildBodyFooter(threadToken)
368
+ if (outboundBodyFormat === 'html') {
369
+ const closingBody = outboundBody.lastIndexOf('</body>')
370
+ outboundBody =
371
+ closingBody >= 0
372
+ ? `${outboundBody.slice(0, closingBody)}${footer.html}${outboundBody.slice(closingBody)}`
373
+ : `${outboundBody}${footer.html}`
374
+ } else {
375
+ outboundBody = `${outboundBody}${footer.plain}`
376
+ }
377
+ const refId = buildReferencesId(threadToken)
378
+ if (!mergedReferences.includes(refId)) {
379
+ mergedReferences = [...mergedReferences, refId]
380
+ }
381
+ }
382
+ const converted = await adapter.convertOutbound({
383
+ body: outboundBody,
384
+ bodyFormat: outboundBodyFormat,
385
+ channelMetadata: {
386
+ thread_id: mapping.externalThreadRef,
387
+ ...baseMetadata,
388
+ references: mergedReferences,
389
+ ...(threadToken ? { omThreadToken: threadToken } : {}),
390
+ },
391
+ })
392
+
393
+ // NOTE — at-least-once delivery (accepted v1 semantics). This provider
394
+ // send is a non-transactional external side effect. The terminal-status
395
+ // short-circuit above and the `message_channel_links_message_uq` index
396
+ // prevent duplicate link records and re-sends after a *completed*
397
+ // delivery, but if the process crashes in the narrow window between this
398
+ // call returning and the success flush below, the link stays `pending`
399
+ // and a worker retry re-invokes the adapter — the recipient may receive a
400
+ // duplicate. This is deliberate: email providers (Gmail/SMTP) expose no
401
+ // idempotent-send key nor a reliable "did message X send?" query, so
402
+ // re-sending is preferred over risking a dropped message.
403
+ const sendResult = await adapter.sendMessage({
404
+ conversationId: mapping.externalThreadRef,
405
+ content: converted.content,
406
+ credentials,
407
+ scope: {
408
+ tenantId: input.scope.tenantId,
409
+ organizationId: input.scope.organizationId ?? input.scope.tenantId,
410
+ },
411
+ metadata: converted.metadata,
412
+ })
413
+
414
+ if (sendResult.status === 'failed') {
415
+ throw new Error(sendResult.error ?? `Adapter '${adapter.providerKey}' reported send failure`)
416
+ }
417
+
418
+ // (7) Persist success records.
419
+ //
420
+ // Pre-generate the ExternalMessage PK client-side. The PK uses
421
+ // `defaultRaw: 'gen_random_uuid()'`, so `externalMessage.id` is undefined
422
+ // until after the INSERT returns — writing `link.externalMessageId =
423
+ // externalMessage.id` before the flush would silently persist NULL on the
424
+ // link's FK. Mirrors the inbound ingest path (ingest-inbound-message.ts).
425
+ const externalMessageRowId = randomUUID()
426
+ const externalMessage = em.create(ExternalMessage, {
427
+ id: externalMessageRowId,
428
+ channelId: channel.id,
429
+ conversationId: mapping.externalConversationId,
430
+ externalMessageId: sendResult.externalMessageId,
431
+ direction: 'outbound',
432
+ senderIdentifier: null,
433
+ senderDisplayName: null,
434
+ providerTimestamp: new Date(),
435
+ tenantId: input.scope.tenantId,
436
+ organizationId: input.scope.organizationId ?? null,
437
+ })
438
+ em.persist(externalMessage)
439
+
440
+ // A successful send proves the credentials are valid — clear any prior
441
+ // `requires_reauth` / `error` state so a recovered channel doesn't keep
442
+ // showing a stale reconnect banner (e.g. after a forced-refresh retry).
443
+ if (channel.status === 'requires_reauth' || channel.status === 'error') {
444
+ channel.status = 'connected'
445
+ }
446
+
447
+ link.deliveryStatus = sendResult.status === 'sent' ? 'sent' : 'queued'
448
+ link.externalMessageId = externalMessageRowId
449
+ link.channelMetadata = {
450
+ ...((link.channelMetadata as Record<string, unknown> | undefined) ?? {}),
451
+ ...(converted.metadata ?? {}),
452
+ externalMessageId: sendResult.externalMessageId,
453
+ // Always persist the RFC2822 Message-ID so inbound reply matching (JWZ
454
+ // strategy in lib/thread-matcher) and sent-folder dedup can resolve this
455
+ // outbound message. Adapters that build the message themselves (Gmail)
456
+ // return it in `converted.metadata.messageId`; IMAP/SMTP lets
457
+ // the transport mint it, surfacing it only as `sendResult.externalMessageId`
458
+ // (the RFC2822 id the recipient replies to) — fall back to that.
459
+ // Store it bracket-stripped to match the inbound convention
460
+ // (`normalizeMimeInbound` strips), so the JWZ matcher and sent-folder dedup —
461
+ // which compare against stripped ids — resolve it. `assembleRfc2822`
462
+ // re-applies brackets when this id is later used to build reply headers.
463
+ messageId: stripBrackets(
464
+ stringOrUndefined((converted.metadata as Record<string, unknown> | undefined)?.messageId) ??
465
+ sendResult.externalMessageId,
466
+ ),
467
+ }
468
+ await em.flush()
469
+
470
+ await emitCommunicationChannelsEvent(
471
+ 'communication_channels.message.sent',
472
+ {
473
+ messageId: message.id,
474
+ externalMessageId: externalMessage.id,
475
+ channelLinkId: link.id,
476
+ conversationId: mapping.externalConversationId,
477
+ channelId: channel.id,
478
+ providerKey: channel.providerKey,
479
+ channelType: channel.channelType,
480
+ direction: 'outbound',
481
+ tenantId: input.scope.tenantId,
482
+ organizationId: input.scope.organizationId ?? null,
483
+ },
484
+ { persistent: true },
485
+ )
486
+
487
+ return {
488
+ status: 'delivered',
489
+ messageId: message.id,
490
+ channelLinkId: link.id,
491
+ externalMessageId: externalMessage.id,
492
+ providerKey: channel.providerKey,
493
+ }
494
+ } catch (sendErr) {
495
+ // (8) Failure path — classify, persist, emit, return.
496
+ const classification = classifyOutboundError(sendErr)
497
+ const requiresReauth = isReauthError(classification)
498
+ link.deliveryStatus = 'failed'
499
+ link.channelMetadata = {
500
+ ...((link.channelMetadata as Record<string, unknown> | undefined) ?? {}),
501
+ lastError: classification.message,
502
+ lastErrorAt: new Date().toISOString(),
503
+ transient: classification.transient,
504
+ requiresReauth,
505
+ }
506
+ // A 401 / invalid_grant means the stored credentials are dead and no
507
+ // retry will help — flip the channel to `requires_reauth` (mirrors the
508
+ // inbound poll path) so the operator gets a reconnect signal instead of
509
+ // silently-failing sends. A later successful send self-heals back to
510
+ // `connected` (see the success path above).
511
+ if (requiresReauth) {
512
+ channel.status = 'requires_reauth'
513
+ }
514
+ await em.flush()
515
+
516
+ try {
517
+ await integrationLog?.error?.({
518
+ integrationId: `channel_${channel.providerKey}`,
519
+ tenantId: input.scope.tenantId,
520
+ organizationId: input.scope.organizationId ?? null,
521
+ channelId: channel.id,
522
+ messageId: message.id,
523
+ status: classification.status ?? null,
524
+ transient: classification.transient,
525
+ message: classification.message,
526
+ })
527
+ } catch {
528
+ // best-effort logging
529
+ }
530
+
531
+ if (requiresReauth) {
532
+ await emitCommunicationChannelsEvent(
533
+ 'communication_channels.channel.requires_reauth',
534
+ {
535
+ channelId: channel.id,
536
+ providerKey: channel.providerKey,
537
+ channelType: channel.channelType,
538
+ reason: classification.message,
539
+ tenantId: input.scope.tenantId,
540
+ organizationId: input.scope.organizationId ?? null,
541
+ },
542
+ { persistent: true },
543
+ )
544
+ }
545
+
546
+ await emitCommunicationChannelsEvent(
547
+ 'communication_channels.message.delivery_failed',
548
+ {
549
+ messageId: message.id,
550
+ channelLinkId: link.id,
551
+ conversationId: mapping.externalConversationId,
552
+ channelId: channel.id,
553
+ providerKey: channel.providerKey,
554
+ channelType: channel.channelType,
555
+ transient: classification.transient,
556
+ error: classification.message,
557
+ status: classification.status ?? null,
558
+ tenantId: input.scope.tenantId,
559
+ organizationId: input.scope.organizationId ?? null,
560
+ },
561
+ { persistent: true },
562
+ )
563
+
564
+ return {
565
+ status: 'failed',
566
+ messageId: message.id,
567
+ channelLinkId: link.id,
568
+ providerKey: channel.providerKey,
569
+ error: classification.message,
570
+ transient: classification.transient,
571
+ requiresReauth,
572
+ }
573
+ }
574
+ },
575
+ }
576
+
577
+ registerCommand(deliverOutboundMessageCommand)
578
+
579
+ export default deliverOutboundMessageCommand