@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,571 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
+ import { CustomerEntity, CustomerInteraction } from '../data/entities'
4
+ import { findPeopleByAddresses, normalizeAddresses } from './findPeopleByAddresses'
5
+ import { emitCustomersEvent } from '../events'
6
+
7
+ /**
8
+ * Shared implementation for the link-channel-message subscribers.
9
+ *
10
+ * The auto-discovery scanner requires a single string `event` value on each
11
+ * subscriber file. Because we must handle both `communication_channels.message.received`
12
+ * AND `.sent`, we use TWO thin subscriber files (link-channel-message-received.ts
13
+ * and link-channel-message-sent.ts) that both delegate here.
14
+ */
15
+
16
+ // ── Types ─────────────────────────────────────────────────────────────────
17
+
18
+ /**
19
+ * Payload emitted by `communication_channels.message.received` and
20
+ * `communication_channels.message.sent`.
21
+ *
22
+ * The canonical field is `channelLinkId` (as emitted by the hub). The alias
23
+ * `messageChannelLinkId` is kept for test stubs and legacy compatibility.
24
+ */
25
+ type LinkChannelMessagePayload = {
26
+ eventType?: string
27
+ /** UUID of the MessageChannelLink row (canonical hub field name). */
28
+ channelLinkId?: string
29
+ /** Alias used in some older stubs / test payloads. Prefer channelLinkId. */
30
+ messageChannelLinkId?: string
31
+ channelId?: string | null
32
+ tenantId?: string
33
+ organizationId?: string | null
34
+ providerKey?: string | null
35
+ direction?: 'inbound' | 'outbound' | null
36
+ }
37
+
38
+ type SubscriberContext = {
39
+ resolve: <T = unknown>(name: string) => T
40
+ }
41
+
42
+ // ── Constants ─────────────────────────────────────────────────────────────
43
+
44
+ const POSTGRES_UNIQUE_VIOLATION = '23505'
45
+
46
+ // ── Main handler ──────────────────────────────────────────────────────────
47
+
48
+ export default async function handler(
49
+ payload: LinkChannelMessagePayload,
50
+ ctx: SubscriberContext,
51
+ ): Promise<void> {
52
+ // Resolve the link ID from either field name.
53
+ const linkId = payload?.channelLinkId ?? payload?.messageChannelLinkId
54
+ if (typeof linkId !== 'string' || !linkId) return
55
+
56
+ // Fail-closed when tenantId is missing — unscoped queries are unsafe.
57
+ if (typeof payload.tenantId !== 'string' || !payload.tenantId) return
58
+
59
+ const tenantId = payload.tenantId
60
+ const organizationId = payload.organizationId ?? null
61
+ // CustomerEntity and CustomerInteraction are organization-scoped. Without an
62
+ // organization id, fail closed instead of linking tenant-wide by email/thread.
63
+ if (!organizationId) return
64
+ const dscope = { tenantId, organizationId }
65
+
66
+ // em.fork() gives us an isolated identity map for this event.
67
+ const em = (ctx.resolve('em') as EntityManager).fork()
68
+
69
+ // ── (1) Load the MessageChannelLink row ───────────────────────────────
70
+ //
71
+ // The customers module MUST NOT import MessageChannelLink from the
72
+ // communication_channels module (cross-module ORM boundary rule in AGENTS.md).
73
+ // We use the entity class name as a string so MikroORM's identity map resolves
74
+ // it at runtime — the generated entity registry includes the hub's entities.
75
+ // Read through findOneWithDecryption so any encrypted columns on the hub
76
+ // entity are transparently decrypted (per the Encryption section in AGENTS.md).
77
+ const link = (await findOneWithDecryption(
78
+ em,
79
+ 'MessageChannelLink' as any,
80
+ { id: linkId, tenantId, organizationId } as any,
81
+ undefined,
82
+ dscope,
83
+ )) as Record<string, unknown> | null
84
+
85
+ if (!link) return
86
+
87
+ const metaJson = (link.channelMetadata ?? null) as Record<string, unknown> | null
88
+ const payloadJson = (link.channelPayload ?? null) as Record<string, unknown> | null
89
+
90
+ // ── (2) Resolve the channel to get its owner userId ───────────────────
91
+ //
92
+ // The channel.userId is needed for two purposes:
93
+ // - authorUserId on the CustomerInteraction row
94
+ // - default visibility ('private' for user-scoped, 'shared' for tenant-scoped)
95
+ //
96
+ // We look up the channel only when channelId is provided in the event payload.
97
+ let channelUserId: string | null = null
98
+ if (typeof payload.channelId === 'string' && payload.channelId) {
99
+ const channel = (await findOneWithDecryption(
100
+ em,
101
+ 'CommunicationChannel' as any,
102
+ { id: payload.channelId, tenantId, organizationId } as any,
103
+ undefined,
104
+ dscope,
105
+ )) as { userId?: string | null } | null
106
+ channelUserId = channel?.userId ?? null
107
+ }
108
+
109
+ // ── (3) Collect recipient addresses ───────────────────────────────────
110
+ //
111
+ // The channel metadata shape differs by provider and direction:
112
+ // - Outbound (Gmail): subject/to/cc/bcc/from are in channelMetadata
113
+ // (merged from GmailEmailNativeMetadata by deliver-outbound-message.ts)
114
+ // - Inbound (Gmail): from/to/cc/bcc/subject are in channelPayload
115
+ // (from NormalizedInboundMessage.channelPayload)
116
+ //
117
+ // We check channelMetadata first (works for outbound + any provider that
118
+ // writes addresses there), then fall back to channelPayload for inbound.
119
+ const rawAddresses: unknown[] = []
120
+
121
+ // For inbound IMAP messages we ALWAYS read addresses from `channelPayload`,
122
+ // not `channelMetadata`. The IMAP adapter stores raw provider headers in
123
+ // `channelMetadata` (where `from` is a JSON-stringified string that's
124
+ // useless for address matching), and the structured normalized addresses
125
+ // in `channelPayload.from` / `.to` / `.cc` / `.bcc`. Reading metadata
126
+ // first poisoned `rawAddresses` with stringified-JSON garbage so the
127
+ // payload-fallback never ran.
128
+ //
129
+ // Priority: payloadJson (canonical normalized shape) → metaJson fallback
130
+ // (for legacy/outbound providers that still write addresses there).
131
+ if (payloadJson) {
132
+ collectAddressField(rawAddresses, payloadJson.from)
133
+ collectAddressField(rawAddresses, payloadJson.to)
134
+ collectAddressField(rawAddresses, payloadJson.cc)
135
+ collectAddressField(rawAddresses, payloadJson.bcc)
136
+ }
137
+
138
+ if (rawAddresses.length === 0 && metaJson) {
139
+ collectAddressField(rawAddresses, metaJson.from)
140
+ collectAddressField(rawAddresses, metaJson.to)
141
+ collectAddressField(rawAddresses, metaJson.cc)
142
+ collectAddressField(rawAddresses, metaJson.bcc)
143
+ }
144
+
145
+ const normalized = normalizeAddresses(rawAddresses as string[])
146
+
147
+ // ── (4) Optional explicit crmPersonId hint ────────────────────────────
148
+ //
149
+ // The outbound compose route stores `crmPersonId` in channelMetadata so the
150
+ // subscriber can link to the intended Person even if their address isn't in
151
+ // the recipient list (e.g. typo in To: field).
152
+ const crmPersonIdHint =
153
+ typeof metaJson?.crmPersonId === 'string' ? (metaJson!.crmPersonId as string) : null
154
+
155
+ // Defense-in-depth: the crmPersonId hint is written by the compose route
156
+ // (which already verifies tenant ownership), but the subscriber MUST re-verify
157
+ // the hinted Person belongs to THIS tenant before linking. Otherwise a stale or
158
+ // forged hint could attach an interaction to a Person in another tenant.
159
+ let crmPersonId: string | null = null
160
+ if (crmPersonIdHint) {
161
+ const hintedPerson = await findOneWithDecryption(
162
+ em,
163
+ CustomerEntity,
164
+ { id: crmPersonIdHint, kind: 'person', tenantId, organizationId, deletedAt: null } as any,
165
+ undefined,
166
+ dscope,
167
+ )
168
+ if (hintedPerson) crmPersonId = crmPersonIdHint
169
+ }
170
+
171
+ // Early exit: no addresses AND no hint → nothing to link.
172
+ if (normalized.length === 0 && !crmPersonId) {
173
+ // Before giving up, try threading-inheritance (TC-CRM-EMAIL-005).
174
+ await handleThreadingInheritance(em, link, linkId, tenantId, organizationId, channelUserId, metaJson, payloadJson)
175
+ return
176
+ }
177
+
178
+ // ── (5) Resolve People by address ────────────────────────────────────
179
+ const matched = await findPeopleByAddresses(em, normalized, tenantId, organizationId)
180
+ const personIdSet = new Set<string>(matched.map((m) => m.id))
181
+ if (crmPersonId) personIdSet.add(crmPersonId)
182
+
183
+ if (personIdSet.size === 0) {
184
+ // Try threading-inheritance before giving up.
185
+ await handleThreadingInheritance(em, link, linkId, tenantId, organizationId, channelUserId, metaJson, payloadJson)
186
+ return
187
+ }
188
+
189
+ // ── (6) Determine visibility ──────────────────────────────────────────
190
+ //
191
+ // Priority order:
192
+ // 1. Explicit `crmVisibility` in channelMetadata (set by compose route)
193
+ // 2. Channel owner: 'private' if user-scoped, 'shared' if tenant-scoped
194
+ const linkDirection =
195
+ link.direction === 'inbound' || link.direction === 'outbound' ? link.direction : null
196
+ const visibility: 'private' | 'shared' = resolveVisibility(metaJson, channelUserId, linkDirection)
197
+
198
+ // ── (7) Extract subject / body / timestamps ───────────────────────────
199
+ const subject =
200
+ typeof metaJson?.subject === 'string'
201
+ ? (metaJson!.subject as string)
202
+ : typeof payloadJson?.subject === 'string'
203
+ ? (payloadJson!.subject as string)
204
+ : null
205
+
206
+ const bodyText =
207
+ typeof metaJson?.bodyText === 'string'
208
+ ? (metaJson!.bodyText as string)
209
+ : typeof payloadJson?.text === 'string'
210
+ ? (payloadJson!.text as string)
211
+ : null
212
+
213
+ const occurredAt = link.createdAt instanceof Date ? link.createdAt : new Date()
214
+ const providerKey =
215
+ typeof link.providerKey === 'string' ? (link.providerKey as string) : null
216
+
217
+ // ── (8) INSERT one CustomerInteraction per matched Person ─────────────
218
+ await persistInteractions(
219
+ em,
220
+ personIdSet,
221
+ {
222
+ linkId,
223
+ tenantId,
224
+ organizationId,
225
+ interactionType: 'email',
226
+ title: subject,
227
+ body: bodyText,
228
+ authorUserId: channelUserId,
229
+ occurredAt,
230
+ visibility,
231
+ channelProviderKey: providerKey,
232
+ },
233
+ )
234
+ }
235
+
236
+ // ── Threading-inheritance fallback ────────────────────────────────────────
237
+
238
+ /**
239
+ * When direct address matching finds 0 people AND crmPersonId is absent, look
240
+ * up parent message references from channelMetadata (`inReplyTo` + `references`).
241
+ *
242
+ * For each reference (an RFC2822 Message-ID), find a prior MessageChannelLink
243
+ * in this tenant whose `channelMetadata.messageId` matches. For each such
244
+ * parent link, find any existing email CustomerInteraction rows where
245
+ * `externalMessageId = parent.id` — and link THIS message to the same Persons.
246
+ *
247
+ * This enables TC-CRM-EMAIL-005: a reply from an unknown address is still
248
+ * attached to alice's timeline because the original thread was.
249
+ */
250
+ async function handleThreadingInheritance(
251
+ em: EntityManager,
252
+ _link: Record<string, unknown>,
253
+ linkId: string,
254
+ tenantId: string,
255
+ organizationId: string | null,
256
+ channelUserId: string | null,
257
+ metaJson: Record<string, unknown> | null,
258
+ payloadJson: Record<string, unknown> | null,
259
+ ): Promise<void> {
260
+ // ── Primary: inherit Person(s) from the hub's authoritative thread ──────
261
+ //
262
+ // The hub threads an inbound reply into the same `messages.message.thread_id`
263
+ // as the outbound that started the conversation (via the thread token /
264
+ // subject+participants matcher). That outbound is already linked to the CRM
265
+ // Person (through the `crmPersonId` hint set by the compose route). So a reply
266
+ // inherits the Person of any existing email interaction in the same thread.
267
+ //
268
+ // This is the dependable join where the alternatives are not:
269
+ // - Address matching (`findPeopleByAddresses`) filters the *encrypted*
270
+ // `primary_email` column by a plaintext value, which never matches when
271
+ // tenant data encryption is on (ciphertext != plaintext).
272
+ // - RFC Message-IDs are rewritten by some providers (e.g. Gmail) on send,
273
+ // so the legacy In-Reply-To/References inheritance below also misses.
274
+ // The hub thread id survives both, so we resolve by it first.
275
+ const inboundMessageId = typeof _link.messageId === 'string' ? _link.messageId : null
276
+ if (inboundMessageId && organizationId) {
277
+ const threadPersonRows = (await em.getConnection().execute(
278
+ `SELECT DISTINCT ci.entity_id AS entity_id
279
+ FROM messages inbound_m
280
+ JOIN messages thread_m ON thread_m.thread_id = inbound_m.thread_id
281
+ JOIN message_channel_links mcl ON mcl.message_id = thread_m.id
282
+ JOIN customer_interactions ci ON ci.external_message_id = mcl.id
283
+ WHERE inbound_m.id = ?
284
+ AND inbound_m.tenant_id = ?
285
+ AND inbound_m.organization_id = ?
286
+ AND inbound_m.deleted_at IS NULL
287
+ AND inbound_m.thread_id IS NOT NULL
288
+ AND thread_m.tenant_id = ?
289
+ AND thread_m.organization_id = ?
290
+ AND thread_m.deleted_at IS NULL
291
+ AND mcl.tenant_id = ?
292
+ AND mcl.organization_id = ?
293
+ AND ci.tenant_id = ?
294
+ AND ci.organization_id = ?
295
+ AND ci.interaction_type = 'email'
296
+ AND ci.deleted_at IS NULL
297
+ AND ci.entity_id IS NOT NULL
298
+ LIMIT 200`,
299
+ [
300
+ inboundMessageId,
301
+ tenantId,
302
+ organizationId,
303
+ tenantId,
304
+ organizationId,
305
+ tenantId,
306
+ organizationId,
307
+ tenantId,
308
+ organizationId,
309
+ ],
310
+ )) as Array<{ entity_id: string }>
311
+ const threadPersonIds = new Set<string>(
312
+ threadPersonRows.map((row) => row.entity_id).filter((id): id is string => !!id),
313
+ )
314
+ if (threadPersonIds.size > 0) {
315
+ const subject =
316
+ typeof metaJson?.subject === 'string'
317
+ ? (metaJson.subject as string)
318
+ : typeof payloadJson?.subject === 'string'
319
+ ? (payloadJson.subject as string)
320
+ : null
321
+ const bodyText =
322
+ typeof payloadJson?.text === 'string'
323
+ ? (payloadJson.text as string)
324
+ : typeof metaJson?.bodyText === 'string'
325
+ ? (metaJson.bodyText as string)
326
+ : null
327
+ const occurredAt = _link.createdAt instanceof Date ? (_link.createdAt as Date) : new Date()
328
+ const providerKey = typeof _link.providerKey === 'string' ? (_link.providerKey as string) : null
329
+ await persistInteractions(em, threadPersonIds, {
330
+ linkId,
331
+ tenantId,
332
+ organizationId,
333
+ interactionType: 'email',
334
+ title: subject,
335
+ body: bodyText,
336
+ authorUserId: channelUserId,
337
+ occurredAt,
338
+ visibility: channelUserId ? 'private' : 'shared',
339
+ channelProviderKey: providerKey,
340
+ })
341
+ return
342
+ }
343
+ }
344
+
345
+ // ── Fallback: legacy In-Reply-To / References Message-ID inheritance ─────
346
+ // Collect reference message-ids from inReplyTo + references
347
+ const refIds: string[] = []
348
+ const inReplyTo =
349
+ typeof metaJson?.inReplyTo === 'string'
350
+ ? stripBrackets(metaJson!.inReplyTo as string)
351
+ : typeof payloadJson?.inReplyTo === 'string'
352
+ ? stripBrackets(payloadJson!.inReplyTo as string)
353
+ : null
354
+ if (inReplyTo) refIds.push(inReplyTo)
355
+
356
+ const refs =
357
+ Array.isArray(metaJson?.references)
358
+ ? (metaJson!.references as unknown[])
359
+ : Array.isArray(payloadJson?.references)
360
+ ? (payloadJson!.references as unknown[])
361
+ : []
362
+ for (const r of refs) {
363
+ if (typeof r === 'string') {
364
+ const stripped = stripBrackets(r)
365
+ if (stripped && !refIds.includes(stripped)) refIds.push(stripped)
366
+ }
367
+ }
368
+
369
+ if (refIds.length === 0) return
370
+
371
+ // Find parent MessageChannelLinks whose channelMetadata.messageId is in refIds.
372
+ // Bounded lookup: narrow to the candidate Message-IDs directly in SQL instead
373
+ // of loading every link in the tenant and filtering in JS (which does not scale
374
+ // on a busy mailbox — this path runs for every inbound reply that didn't match a
375
+ // Person by address). channel_metadata is NOT an encrypted column, so a
376
+ // parameterized raw query is safe; we match both bracketed (`<id>`) and
377
+ // unbracketed (`id`) Message-ID storage forms, capped to a sane page.
378
+ const dscope = { tenantId, organizationId }
379
+ const messageIdCandidates = Array.from(
380
+ new Set(refIds.flatMap((ref) => [ref, `<${ref}>`])),
381
+ )
382
+ const placeholders = messageIdCandidates.map(() => '?').join(', ')
383
+ const parentLinkRows = (await em.getConnection().execute(
384
+ `SELECT id FROM message_channel_links
385
+ WHERE tenant_id = ?
386
+ AND organization_id = ?
387
+ AND channel_metadata->>'messageId' IN (${placeholders})
388
+ LIMIT 200`,
389
+ [tenantId, organizationId, ...messageIdCandidates],
390
+ )) as Array<{ id: string }>
391
+
392
+ const matchedParentIds = parentLinkRows.map((parentLink) => parentLink.id)
393
+
394
+ if (matchedParentIds.length === 0) return
395
+
396
+ // Find existing email CustomerInteraction rows for those parent links.
397
+ const parentInteractions = (await findWithDecryption(
398
+ em,
399
+ CustomerInteraction,
400
+ {
401
+ externalMessageId: { $in: matchedParentIds },
402
+ tenantId,
403
+ organizationId,
404
+ interactionType: 'email',
405
+ deletedAt: null,
406
+ } as any,
407
+ undefined,
408
+ dscope,
409
+ )) as Array<{ entity: { id: string } }>
410
+
411
+ const inheritedPersonIdSet = new Set<string>(
412
+ parentInteractions.map((pi) => pi.entity?.id).filter(Boolean) as string[],
413
+ )
414
+
415
+ if (inheritedPersonIdSet.size === 0) return
416
+
417
+ const visibility: 'private' | 'shared' = channelUserId ? 'private' : 'shared'
418
+ const link = _link
419
+ const inheritedMeta = (link.channelMetadata ?? null) as Record<string, unknown> | null
420
+ const subject =
421
+ typeof inheritedMeta?.subject === 'string' ? (inheritedMeta.subject as string) : null
422
+ const occurredAt = link.createdAt instanceof Date ? (link.createdAt as Date) : new Date()
423
+ const providerKey =
424
+ typeof link.providerKey === 'string' ? (link.providerKey as string) : null
425
+
426
+ await persistInteractions(
427
+ em,
428
+ inheritedPersonIdSet,
429
+ {
430
+ linkId,
431
+ tenantId,
432
+ organizationId,
433
+ interactionType: 'email',
434
+ title: subject,
435
+ body: null,
436
+ authorUserId: channelUserId,
437
+ occurredAt,
438
+ visibility,
439
+ channelProviderKey: providerKey,
440
+ },
441
+ )
442
+ }
443
+
444
+ // ── Helpers ───────────────────────────────────────────────────────────────
445
+
446
+ interface InteractionData {
447
+ linkId: string
448
+ tenantId: string
449
+ organizationId: string | null
450
+ interactionType: string
451
+ title: string | null
452
+ body: string | null
453
+ authorUserId: string | null
454
+ occurredAt: Date
455
+ visibility: 'private' | 'shared'
456
+ channelProviderKey: string | null
457
+ }
458
+
459
+ async function persistInteractions(
460
+ em: EntityManager,
461
+ personIdSet: Set<string>,
462
+ data: InteractionData,
463
+ ): Promise<void> {
464
+ for (const personId of personIdSet) {
465
+ // Fork per row so a unique-violation on one row doesn't poison the identity
466
+ // map for subsequent rows. The parent em's connection pool is reused.
467
+ const rowEm = em.fork()
468
+ // Use em.getReference so we satisfy the ManyToOne relation without a
469
+ // redundant SELECT — MikroORM will flush the FK column directly.
470
+ const entityRef = rowEm.getReference(CustomerEntity, personId)
471
+ const interaction = rowEm.create(CustomerInteraction, {
472
+ tenantId: data.tenantId,
473
+ organizationId: data.organizationId,
474
+ entity: entityRef,
475
+ interactionType: data.interactionType,
476
+ title: data.title,
477
+ body: data.body,
478
+ authorUserId: data.authorUserId,
479
+ occurredAt: data.occurredAt,
480
+ // Emails are logged AFTER they're sent/received — they are not
481
+ // scheduled work. Without an explicit status the entity default
482
+ // ('planned') combined with a past `occurredAt` makes the activity
483
+ // timeline render the email as "overdue", which is the wrong UX.
484
+ // The canonical "completed" value is `'done'` per
485
+ // `validators.ts:interactionStatusValues = ['planned', 'done', 'canceled']`
486
+ // — `'completed'` is a legacy spelling that the enricher accepts
487
+ // defensively but the activity timeline `isOverdue` predicate does NOT.
488
+ status: 'done',
489
+ externalMessageId: data.linkId,
490
+ visibility: data.visibility,
491
+ channelProviderKey: data.channelProviderKey,
492
+ } as any)
493
+ try {
494
+ await rowEm.flush()
495
+ } catch (err) {
496
+ // Idempotency: swallow unique-violation on (entity_id, external_message_id).
497
+ // The partial unique index `customer_interactions_email_dedupe_uq` guarantees
498
+ // at-most-once semantics across retries.
499
+ const code = (err as { code?: string }).code
500
+ if (code !== POSTGRES_UNIQUE_VIOLATION) throw err
501
+ // Duplicate (retried delivery) — already linked, skip the refresh signal.
502
+ continue
503
+ }
504
+ // Live-refresh signal for the CRM Person page (clientBroadcast → SSE). The
505
+ // interaction is already persisted, so a failed emit must not abort linking
506
+ // or fail this persistent (retried) subscriber.
507
+ try {
508
+ await emitCustomersEvent('customers.email.linked', {
509
+ personId,
510
+ interactionId: interaction.id,
511
+ tenantId: data.tenantId,
512
+ organizationId: data.organizationId,
513
+ })
514
+ } catch {
515
+ /* swallow — UI refresh signal is non-critical */
516
+ }
517
+ }
518
+ }
519
+
520
+ function resolveVisibility(
521
+ metaJson: Record<string, unknown> | null,
522
+ channelUserId: string | null,
523
+ direction: 'inbound' | 'outbound' | null,
524
+ ): 'private' | 'shared' {
525
+ // The explicit `crmVisibility` override is written ONLY by the outbound compose
526
+ // route. Inbound `channelMetadata` is provider-derived (and therefore attacker-
527
+ // influenceable), so it MUST NOT be able to downgrade a user-owned channel's
528
+ // inbound mail from private → shared. Honor the override on outbound only.
529
+ if (direction === 'outbound') {
530
+ if (metaJson?.crmVisibility === 'shared') return 'shared'
531
+ if (metaJson?.crmVisibility === 'private') return 'private'
532
+ }
533
+ // Tenant-scoped channels (no userId) → shared; user-scoped → private.
534
+ return channelUserId ? 'private' : 'shared'
535
+ }
536
+
537
+ /**
538
+ * Collect email address strings from a field that may be:
539
+ * - a plain string: 'alice@example.com'
540
+ * - an array of strings: ['alice@example.com', 'bob@example.com']
541
+ * - an object with an `address` property: { address: 'alice@example.com', name: 'Alice' }
542
+ * - an array of such objects
543
+ */
544
+ function collectAddressField(out: unknown[], value: unknown): void {
545
+ if (!value) return
546
+ if (typeof value === 'string') {
547
+ out.push(value)
548
+ return
549
+ }
550
+ if (Array.isArray(value)) {
551
+ for (const item of value) {
552
+ if (typeof item === 'string') {
553
+ out.push(item)
554
+ } else if (item && typeof item === 'object' && typeof (item as Record<string, unknown>).address === 'string') {
555
+ out.push((item as Record<string, unknown>).address as string)
556
+ }
557
+ }
558
+ return
559
+ }
560
+ if (typeof value === 'object' && typeof (value as Record<string, unknown>).address === 'string') {
561
+ out.push((value as Record<string, unknown>).address as string)
562
+ }
563
+ }
564
+
565
+ function stripBrackets(value: string): string {
566
+ const trimmed = value.trim()
567
+ if (trimmed.startsWith('<') && trimmed.endsWith('>')) {
568
+ return trimmed.slice(1, -1)
569
+ }
570
+ return trimmed
571
+ }