@aglyn/plugins-outreach 1.0.0-beta.143

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 (282) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +93 -0
  3. package/package.json +52 -0
  4. package/src/index.d.ts +22 -0
  5. package/src/index.js +29 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/compliance-section.d.ts +20 -0
  8. package/src/lib/components/compliance-section.js +291 -0
  9. package/src/lib/components/compliance-section.js.map +1 -0
  10. package/src/lib/components/enroll-dialog.d.ts +36 -0
  11. package/src/lib/components/enroll-dialog.js +656 -0
  12. package/src/lib/components/enroll-dialog.js.map +1 -0
  13. package/src/lib/components/enrollments-table.d.ts +28 -0
  14. package/src/lib/components/enrollments-table.js +375 -0
  15. package/src/lib/components/enrollments-table.js.map +1 -0
  16. package/src/lib/components/mailbox-card.d.ts +46 -0
  17. package/src/lib/components/mailbox-card.js +517 -0
  18. package/src/lib/components/mailbox-card.js.map +1 -0
  19. package/src/lib/components/mailboxes-section.d.ts +24 -0
  20. package/src/lib/components/mailboxes-section.js +258 -0
  21. package/src/lib/components/mailboxes-section.js.map +1 -0
  22. package/src/lib/components/outreach-console-page.d.ts +17 -0
  23. package/src/lib/components/outreach-console-page.js +81 -0
  24. package/src/lib/components/outreach-console-page.js.map +1 -0
  25. package/src/lib/components/outreach-console-sections.d.ts +43 -0
  26. package/src/lib/components/outreach-console-sections.js +55 -0
  27. package/src/lib/components/outreach-console-sections.js.map +1 -0
  28. package/src/lib/components/outreach-ui.d.ts +51 -0
  29. package/src/lib/components/outreach-ui.js +171 -0
  30. package/src/lib/components/outreach-ui.js.map +1 -0
  31. package/src/lib/components/send-window-fields.d.ts +25 -0
  32. package/src/lib/components/send-window-fields.js +178 -0
  33. package/src/lib/components/send-window-fields.js.map +1 -0
  34. package/src/lib/components/sequence-detail.d.ts +54 -0
  35. package/src/lib/components/sequence-detail.js +435 -0
  36. package/src/lib/components/sequence-detail.js.map +1 -0
  37. package/src/lib/components/sequence-editor.d.ts +37 -0
  38. package/src/lib/components/sequence-editor.js +485 -0
  39. package/src/lib/components/sequence-editor.js.map +1 -0
  40. package/src/lib/components/sequence-mailbox-picker.d.ts +44 -0
  41. package/src/lib/components/sequence-mailbox-picker.js +137 -0
  42. package/src/lib/components/sequence-mailbox-picker.js.map +1 -0
  43. package/src/lib/components/sequence-preview.d.ts +27 -0
  44. package/src/lib/components/sequence-preview.js +146 -0
  45. package/src/lib/components/sequence-preview.js.map +1 -0
  46. package/src/lib/components/sequence-step-card.d.ts +31 -0
  47. package/src/lib/components/sequence-step-card.js +326 -0
  48. package/src/lib/components/sequence-step-card.js.map +1 -0
  49. package/src/lib/components/sequences-section.d.ts +40 -0
  50. package/src/lib/components/sequences-section.js +310 -0
  51. package/src/lib/components/sequences-section.js.map +1 -0
  52. package/src/lib/components/use-outreach-api.d.ts +38 -0
  53. package/src/lib/components/use-outreach-api.js +139 -0
  54. package/src/lib/components/use-outreach-api.js.map +1 -0
  55. package/src/lib/components/use-outreach-crm.d.ts +50 -0
  56. package/src/lib/components/use-outreach-crm.js +200 -0
  57. package/src/lib/components/use-outreach-crm.js.map +1 -0
  58. package/src/lib/components/use-outreach-data.d.ts +55 -0
  59. package/src/lib/components/use-outreach-data.js +199 -0
  60. package/src/lib/components/use-outreach-data.js.map +1 -0
  61. package/src/lib/components/use-outreach-mailbox-api.d.ts +30 -0
  62. package/src/lib/components/use-outreach-mailbox-api.js +101 -0
  63. package/src/lib/components/use-outreach-mailbox-api.js.map +1 -0
  64. package/src/lib/components/use-outreach-mailboxes.d.ts +22 -0
  65. package/src/lib/components/use-outreach-mailboxes.js +68 -0
  66. package/src/lib/components/use-outreach-mailboxes.js.map +1 -0
  67. package/src/lib/components/use-outreach-settings.d.ts +17 -0
  68. package/src/lib/components/use-outreach-settings.js +64 -0
  69. package/src/lib/components/use-outreach-settings.js.map +1 -0
  70. package/src/lib/constants/api-routes.d.ts +68 -0
  71. package/src/lib/constants/api-routes.js +57 -0
  72. package/src/lib/constants/api-routes.js.map +1 -0
  73. package/src/lib/constants/bundle-common.d.ts +46 -0
  74. package/src/lib/constants/bundle-common.js +55 -0
  75. package/src/lib/constants/bundle-common.js.map +1 -0
  76. package/src/lib/constants/runtime-jobs.d.ts +24 -0
  77. package/src/lib/constants/runtime-jobs.js +24 -0
  78. package/src/lib/constants/runtime-jobs.js.map +1 -0
  79. package/src/lib/declarations.console-server.d.ts +38 -0
  80. package/src/lib/declarations.console-server.js +99 -0
  81. package/src/lib/declarations.console-server.js.map +1 -0
  82. package/src/lib/engine/compose.d.ts +86 -0
  83. package/src/lib/engine/compose.js +212 -0
  84. package/src/lib/engine/compose.js.map +1 -0
  85. package/src/lib/engine/delivery-status.d.ts +53 -0
  86. package/src/lib/engine/delivery-status.js +184 -0
  87. package/src/lib/engine/delivery-status.js.map +1 -0
  88. package/src/lib/engine/do-not-contact.d.ts +18 -0
  89. package/src/lib/engine/do-not-contact.js +34 -0
  90. package/src/lib/engine/do-not-contact.js.map +1 -0
  91. package/src/lib/engine/enrollment-state.d.ts +156 -0
  92. package/src/lib/engine/enrollment-state.js +303 -0
  93. package/src/lib/engine/enrollment-state.js.map +1 -0
  94. package/src/lib/engine/gates.d.ts +125 -0
  95. package/src/lib/engine/gates.js +232 -0
  96. package/src/lib/engine/gates.js.map +1 -0
  97. package/src/lib/engine/index.d.ts +28 -0
  98. package/src/lib/engine/index.js +33 -0
  99. package/src/lib/engine/index.js.map +1 -0
  100. package/src/lib/engine/mailbox-health.d.ts +49 -0
  101. package/src/lib/engine/mailbox-health.js +83 -0
  102. package/src/lib/engine/mailbox-health.js.map +1 -0
  103. package/src/lib/engine/opt-out-intent.d.ts +41 -0
  104. package/src/lib/engine/opt-out-intent.js +220 -0
  105. package/src/lib/engine/opt-out-intent.js.map +1 -0
  106. package/src/lib/engine/recipient-country.d.ts +41 -0
  107. package/src/lib/engine/recipient-country.js +171 -0
  108. package/src/lib/engine/recipient-country.js.map +1 -0
  109. package/src/lib/engine/schedule.d.ts +70 -0
  110. package/src/lib/engine/schedule.js +115 -0
  111. package/src/lib/engine/schedule.js.map +1 -0
  112. package/src/lib/engine/sending-capacity.d.ts +96 -0
  113. package/src/lib/engine/sending-capacity.js +172 -0
  114. package/src/lib/engine/sending-capacity.js.map +1 -0
  115. package/src/lib/engine/sequence-validation.d.ts +90 -0
  116. package/src/lib/engine/sequence-validation.js +291 -0
  117. package/src/lib/engine/sequence-validation.js.map +1 -0
  118. package/src/lib/engine/thread-classification.d.ts +78 -0
  119. package/src/lib/engine/thread-classification.js +224 -0
  120. package/src/lib/engine/thread-classification.js.map +1 -0
  121. package/src/lib/engine/thread-message.d.ts +80 -0
  122. package/src/lib/engine/thread-message.js +143 -0
  123. package/src/lib/engine/thread-message.js.map +1 -0
  124. package/src/lib/enrollment/enroll-people.d.ts +81 -0
  125. package/src/lib/enrollment/enroll-people.js +250 -0
  126. package/src/lib/enrollment/enroll-people.js.map +1 -0
  127. package/src/lib/enrollment/gate-lookups.d.ts +32 -0
  128. package/src/lib/enrollment/gate-lookups.js +154 -0
  129. package/src/lib/enrollment/gate-lookups.js.map +1 -0
  130. package/src/lib/mailboxes/mailbox-api.d.ts +126 -0
  131. package/src/lib/mailboxes/mailbox-api.js +66 -0
  132. package/src/lib/mailboxes/mailbox-api.js.map +1 -0
  133. package/src/lib/mailboxes/mailbox-credentials.d.ts +95 -0
  134. package/src/lib/mailboxes/mailbox-credentials.js +102 -0
  135. package/src/lib/mailboxes/mailbox-credentials.js.map +1 -0
  136. package/src/lib/mailboxes/mailbox-erasure.d.ts +69 -0
  137. package/src/lib/mailboxes/mailbox-erasure.js +148 -0
  138. package/src/lib/mailboxes/mailbox-erasure.js.map +1 -0
  139. package/src/lib/mailboxes/mailbox-gate.d.ts +75 -0
  140. package/src/lib/mailboxes/mailbox-gate.js +98 -0
  141. package/src/lib/mailboxes/mailbox-gate.js.map +1 -0
  142. package/src/lib/mailboxes/mailbox-revoke.d.ts +59 -0
  143. package/src/lib/mailboxes/mailbox-revoke.js +41 -0
  144. package/src/lib/mailboxes/mailbox-revoke.js.map +1 -0
  145. package/src/lib/mailboxes/mailbox-routes.d.ts +119 -0
  146. package/src/lib/mailboxes/mailbox-routes.js +605 -0
  147. package/src/lib/mailboxes/mailbox-routes.js.map +1 -0
  148. package/src/lib/mailboxes/mailbox-settings.d.ts +110 -0
  149. package/src/lib/mailboxes/mailbox-settings.js +212 -0
  150. package/src/lib/mailboxes/mailbox-settings.js.map +1 -0
  151. package/src/lib/mailboxes/mailbox-transport.d.ts +64 -0
  152. package/src/lib/mailboxes/mailbox-transport.js +94 -0
  153. package/src/lib/mailboxes/mailbox-transport.js.map +1 -0
  154. package/src/lib/mailboxes/oauth-redirect.d.ts +41 -0
  155. package/src/lib/mailboxes/oauth-redirect.js +63 -0
  156. package/src/lib/mailboxes/oauth-redirect.js.map +1 -0
  157. package/src/lib/mailboxes/oauth-state.d.ts +152 -0
  158. package/src/lib/mailboxes/oauth-state.js +212 -0
  159. package/src/lib/mailboxes/oauth-state.js.map +1 -0
  160. package/src/lib/mailboxes/outreach-config.d.ts +64 -0
  161. package/src/lib/mailboxes/outreach-config.js +77 -0
  162. package/src/lib/mailboxes/outreach-config.js.map +1 -0
  163. package/src/lib/mailboxes/register-mailbox-routes.d.ts +47 -0
  164. package/src/lib/mailboxes/register-mailbox-routes.js +135 -0
  165. package/src/lib/mailboxes/register-mailbox-routes.js.map +1 -0
  166. package/src/lib/model/compliance-settings.d.ts +78 -0
  167. package/src/lib/model/compliance-settings.js +155 -0
  168. package/src/lib/model/compliance-settings.js.map +1 -0
  169. package/src/lib/model/outreach-api.d.ts +210 -0
  170. package/src/lib/model/outreach-api.js +28 -0
  171. package/src/lib/model/outreach-api.js.map +1 -0
  172. package/src/lib/model/outreach.types.d.ts +563 -0
  173. package/src/lib/model/outreach.types.js +225 -0
  174. package/src/lib/model/outreach.types.js.map +1 -0
  175. package/src/lib/model/sequence-draft.d.ts +84 -0
  176. package/src/lib/model/sequence-draft.js +239 -0
  177. package/src/lib/model/sequence-draft.js.map +1 -0
  178. package/src/lib/model/step-preview.d.ts +45 -0
  179. package/src/lib/model/step-preview.js +98 -0
  180. package/src/lib/model/step-preview.js.map +1 -0
  181. package/src/lib/model/stored-records.d.ts +23 -0
  182. package/src/lib/model/stored-records.js +84 -0
  183. package/src/lib/model/stored-records.js.map +1 -0
  184. package/src/lib/plugin.d.ts +46 -0
  185. package/src/lib/plugin.js +86 -0
  186. package/src/lib/plugin.js.map +1 -0
  187. package/src/lib/routes/enroll-routes.d.ts +64 -0
  188. package/src/lib/routes/enroll-routes.js +350 -0
  189. package/src/lib/routes/enroll-routes.js.map +1 -0
  190. package/src/lib/routes/enrollment-routes.d.ts +19 -0
  191. package/src/lib/routes/enrollment-routes.js +171 -0
  192. package/src/lib/routes/enrollment-routes.js.map +1 -0
  193. package/src/lib/routes/platform-deps.d.ts +51 -0
  194. package/src/lib/routes/platform-deps.js +66 -0
  195. package/src/lib/routes/platform-deps.js.map +1 -0
  196. package/src/lib/routes/preview-routes.d.ts +33 -0
  197. package/src/lib/routes/preview-routes.js +117 -0
  198. package/src/lib/routes/preview-routes.js.map +1 -0
  199. package/src/lib/routes/register-routes.d.ts +22 -0
  200. package/src/lib/routes/register-routes.js +92 -0
  201. package/src/lib/routes/register-routes.js.map +1 -0
  202. package/src/lib/routes/route-deps.d.ts +49 -0
  203. package/src/lib/routes/route-deps.js +27 -0
  204. package/src/lib/routes/route-deps.js.map +1 -0
  205. package/src/lib/routes/route-gate.d.ts +94 -0
  206. package/src/lib/routes/route-gate.js +87 -0
  207. package/src/lib/routes/route-gate.js.map +1 -0
  208. package/src/lib/routes/route-http.d.ts +32 -0
  209. package/src/lib/routes/route-http.js +62 -0
  210. package/src/lib/routes/route-http.js.map +1 -0
  211. package/src/lib/routes/sequence-routes.d.ts +32 -0
  212. package/src/lib/routes/sequence-routes.js +359 -0
  213. package/src/lib/routes/sequence-routes.js.map +1 -0
  214. package/src/lib/routes/settings-routes.d.ts +30 -0
  215. package/src/lib/routes/settings-routes.js +73 -0
  216. package/src/lib/routes/settings-routes.js.map +1 -0
  217. package/src/lib/runtime/enrollment-events.d.ts +86 -0
  218. package/src/lib/runtime/enrollment-events.js +141 -0
  219. package/src/lib/runtime/enrollment-events.js.map +1 -0
  220. package/src/lib/runtime/fixtures/fake-gmail.d.ts +121 -0
  221. package/src/lib/runtime/fixtures/fake-gmail.js +312 -0
  222. package/src/lib/runtime/fixtures/fake-gmail.js.map +1 -0
  223. package/src/lib/runtime/mailbox-health-store.d.ts +47 -0
  224. package/src/lib/runtime/mailbox-health-store.js +168 -0
  225. package/src/lib/runtime/mailbox-health-store.js.map +1 -0
  226. package/src/lib/runtime/person-erasure.d.ts +39 -0
  227. package/src/lib/runtime/person-erasure.js +57 -0
  228. package/src/lib/runtime/person-erasure.js.map +1 -0
  229. package/src/lib/runtime/platform-runtime-deps.d.ts +29 -0
  230. package/src/lib/runtime/platform-runtime-deps.js +98 -0
  231. package/src/lib/runtime/platform-runtime-deps.js.map +1 -0
  232. package/src/lib/runtime/runtime-deps.d.ts +77 -0
  233. package/src/lib/runtime/runtime-deps.js +18 -0
  234. package/src/lib/runtime/runtime-deps.js.map +1 -0
  235. package/src/lib/runtime/send-job.d.ts +42 -0
  236. package/src/lib/runtime/send-job.js +780 -0
  237. package/src/lib/runtime/send-job.js.map +1 -0
  238. package/src/lib/runtime/sync-job.d.ts +41 -0
  239. package/src/lib/runtime/sync-job.js +480 -0
  240. package/src/lib/runtime/sync-job.js.map +1 -0
  241. package/src/lib/runtime/timeline.d.ts +67 -0
  242. package/src/lib/runtime/timeline.js +93 -0
  243. package/src/lib/runtime/timeline.js.map +1 -0
  244. package/src/lib/runtime/unsubscribe-link.d.ts +75 -0
  245. package/src/lib/runtime/unsubscribe-link.js +121 -0
  246. package/src/lib/runtime/unsubscribe-link.js.map +1 -0
  247. package/src/lib/runtime/unsubscribe-route.d.ts +24 -0
  248. package/src/lib/runtime/unsubscribe-route.js +103 -0
  249. package/src/lib/runtime/unsubscribe-route.js.map +1 -0
  250. package/src/lib/server.d.ts +53 -0
  251. package/src/lib/server.js +94 -0
  252. package/src/lib/server.js.map +1 -0
  253. package/src/lib/storage/compliance-settings-store.d.ts +44 -0
  254. package/src/lib/storage/compliance-settings-store.js +78 -0
  255. package/src/lib/storage/compliance-settings-store.js.map +1 -0
  256. package/src/lib/storage/do-not-contact-store.d.ts +60 -0
  257. package/src/lib/storage/do-not-contact-store.js +168 -0
  258. package/src/lib/storage/do-not-contact-store.js.map +1 -0
  259. package/src/lib/storage/outreach-records.d.ts +33 -0
  260. package/src/lib/storage/outreach-records.js +35 -0
  261. package/src/lib/storage/outreach-records.js.map +1 -0
  262. package/src/lib/subprocessors.d.ts +48 -0
  263. package/src/lib/subprocessors.js +70 -0
  264. package/src/lib/subprocessors.js.map +1 -0
  265. package/src/lib/transport/gmail-client.d.ts +145 -0
  266. package/src/lib/transport/gmail-client.js +229 -0
  267. package/src/lib/transport/gmail-client.js.map +1 -0
  268. package/src/lib/transport/gmail-errors.d.ts +84 -0
  269. package/src/lib/transport/gmail-errors.js +138 -0
  270. package/src/lib/transport/gmail-errors.js.map +1 -0
  271. package/src/lib/transport/google-oauth.d.ts +138 -0
  272. package/src/lib/transport/google-oauth.js +247 -0
  273. package/src/lib/transport/google-oauth.js.map +1 -0
  274. package/src/lib/transport/http.d.ts +53 -0
  275. package/src/lib/transport/http.js +79 -0
  276. package/src/lib/transport/http.js.map +1 -0
  277. package/src/lib/transport/rfc5322.d.ts +116 -0
  278. package/src/lib/transport/rfc5322.js +330 -0
  279. package/src/lib/transport/rfc5322.js.map +1 -0
  280. package/src/lib/transport/send-message.d.ts +69 -0
  281. package/src/lib/transport/send-message.js +77 -0
  282. package/src/lib/transport/send-message.js.map +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/model/sequence-draft.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * A SEQUENCE AS THE EDITOR HOLDS IT (AGL-2980).\n *\n * The editor builds a draft, the save route reads the draft it was sent\n * through the same reader, and the engine's validator judges both — so the\n * issues the editor shows beside a field are the ones the route would\n * refuse the save with. Client-safe.\n *\n * ## Once someone is enrolled, the steps they stand on stay put\n *\n * An enrollment points at its next step by POSITION (`stepIndex`), and it\n * was enrolled into a site's CRM from one mailbox. So after the first\n * enrollment, a save may edit any step's wording and delay and may add\n * steps at the end, but may not remove, reorder or change the kind of a\n * step that already exists, and may not move the sequence to another site\n * or mailbox: each of those would send an enrolled person a step they were\n * never going to get, from an address they never heard from. A new\n * sequence is the way to change them.\n *==========================================*/\n\nimport {\n readOutreachSequenceSettings,\n type OutreachValidationIssue,\n type OutreachValidationCode,\n} from '../engine/sequence-validation'\nimport {\n type OutreachEmailStep,\n type OutreachMailbox,\n type OutreachSendWindow,\n type OutreachSequence,\n type OutreachSequenceSettings,\n type OutreachSequenceStep,\n type OutreachTaskKind,\n type OutreachTaskStep,\n} from './outreach.types'\n\n/** A sequence as the editor holds it and the save route receives it. */\nexport interface OutreachSequenceDraft {\n name: string\n /** The site whose CRM the enrolled people are records of. */\n hostId: string\n /** `''` until a mailbox is chosen. */\n mailboxId: string\n steps: OutreachSequenceStep[]\n settings: OutreachSequenceSettings\n}\n\n/** The most steps a draft is read with; the validator refuses past the real limit. */\nconst STEPS_READ_MAX = 20\n\n/** The delay a new email after the first starts with, in business days. */\nexport const OUTREACH_DEFAULT_FOLLOW_UP_DAYS = 3\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value : '')\n\n/** A whole number, or `NaN` for anything else, so the validator names it. */\nconst wholeNumber = (value: unknown): number => {\n const number = typeof value === 'number' ? value : Number(text(value) || NaN)\n return Number.isInteger(number) ? number : Number.NaN\n}\n\nfunction readWindow(value: unknown): OutreachSendWindow | null {\n if (!value || typeof value !== 'object') return null\n const raw = value as Record<string, unknown>\n return {\n days: Array.isArray(raw['days']) ? raw['days'].map((day) => wholeNumber(day)) : [],\n startMinute: wholeNumber(raw['startMinute']),\n endMinute: wholeNumber(raw['endMinute']),\n }\n}\n\nfunction readStep(value: unknown): OutreachSequenceStep {\n const raw = (value && typeof value === 'object' ? value : {}) as Record<string, unknown>\n const id = text(raw['id']).trim().slice(0, 64)\n const delayBusinessDays = wholeNumber(raw['delayBusinessDays'])\n if (raw['kind'] === 'email') {\n const templateId = text(raw['templateId']).trim()\n const step: OutreachEmailStep = {\n id,\n kind: 'email',\n delayBusinessDays,\n subject: text(raw['subject']),\n replyInThread: raw['replyInThread'] !== false,\n body: text(raw['body']),\n templateId: templateId || null,\n }\n return step\n }\n if (raw['kind'] === 'task') {\n const step: OutreachTaskStep = {\n id,\n kind: 'task',\n taskKind: text(raw['taskKind']) as OutreachTaskKind,\n title: text(raw['title']),\n delayBusinessDays,\n }\n return step\n }\n // Neither kind: kept, so the validator can say \"a step is an email or a\n // task\" beside it rather than the step silently disappearing.\n return { id, kind: text(raw['kind']) } as unknown as OutreachSequenceStep\n}\n\n/**\n * A draft read out of anything — a request body, a stored sequence — with\n * every field the model does not name left behind and every value coerced\n * to its type. Nothing is judged here; that is the validator's.\n */\nexport function readOutreachSequenceDraft(input: unknown): OutreachSequenceDraft {\n const raw = (input && typeof input === 'object' ? input : {}) as Record<string, unknown>\n const settings = readOutreachSequenceSettings(\n (raw['settings'] && typeof raw['settings'] === 'object'\n ? raw['settings']\n : {}) as Record<string, unknown>,\n )\n return {\n name: text(raw['name']).replace(/\\s+/g, ' ').trim(),\n hostId: text(raw['hostId']).trim(),\n mailboxId: text(raw['mailboxId']).trim(),\n steps: (Array.isArray(raw['steps']) ? raw['steps'] : []).slice(0, STEPS_READ_MAX).map(readStep),\n settings: {\n ...settings,\n window: readWindow((raw['settings'] as Record<string, unknown> | undefined)?.['window']),\n },\n }\n}\n\n/** The draft a stored sequence opens as in the editor. */\nexport function outreachSequenceDraftOf(\n sequence: Pick<OutreachSequence, 'name' | 'hostId' | 'mailboxId' | 'steps' | 'settings'>,\n): OutreachSequenceDraft {\n return readOutreachSequenceDraft(sequence)\n}\n\n/** A step id nothing else in the sequence carries. */\nexport function newOutreachStepId(\n steps: readonly Pick<OutreachSequenceStep, 'id'>[] = [],\n random: () => number = Math.random,\n): string {\n const taken = new Set(steps.map((step) => step.id))\n for (let attempt = 0; ; attempt += 1) {\n const drawn = `step-${Math.floor(random() * 36 ** 6).toString(36).padStart(6, '0')}`\n // A source that keeps answering the same number still ends: the attempt\n // count makes the id unique once the draws stop being.\n const id = attempt < 4 ? drawn : `${drawn}-${attempt}`\n if (!taken.has(id)) return id\n }\n}\n\n/**\n * A new email step for the end of `steps`: the first email waits for the\n * next opening; a later one waits a few business days and replies in the\n * thread the first started.\n */\nexport function newOutreachEmailStep(\n steps: readonly OutreachSequenceStep[],\n random?: () => number,\n): OutreachEmailStep {\n const follows = steps.some((step) => step.kind === 'email')\n return {\n id: newOutreachStepId(steps, random),\n kind: 'email',\n delayBusinessDays: follows ? OUTREACH_DEFAULT_FOLLOW_UP_DAYS : 0,\n subject: '',\n replyInThread: true,\n body: '',\n templateId: null,\n }\n}\n\n/** A new task step for the end of `steps`, titled for its kind. */\nexport function newOutreachTaskStep(\n steps: readonly OutreachSequenceStep[],\n taskKind: OutreachTaskKind,\n random?: () => number,\n): OutreachTaskStep {\n const titles: Record<OutreachTaskKind, string> = {\n linkedin: 'Connect on LinkedIn',\n call: 'Call',\n todo: '',\n }\n return {\n id: newOutreachStepId(steps, random),\n kind: 'task',\n taskKind,\n title: titles[taskKind] ?? '',\n delayBusinessDays: 1,\n }\n}\n\n/** A new sequence: one email, for `hostId`, sending to the countries given. */\nexport function emptyOutreachSequenceDraft(input: {\n hostId: string\n mailboxId?: string\n allowedCountries: readonly string[]\n random?: () => number\n}): OutreachSequenceDraft {\n return {\n name: '',\n hostId: input.hostId,\n mailboxId: input.mailboxId ?? '',\n steps: [newOutreachEmailStep([], input.random)],\n settings: {\n window: null,\n allowedCountries: [...input.allowedCountries],\n allowCustomers: false,\n },\n }\n}\n\n/** The issues a save can be refused with beyond the engine's own. */\nexport type OutreachSequenceIssueCode =\n | OutreachValidationCode\n | 'steps_locked'\n | 'host_locked'\n | 'mailbox_locked'\n | 'host_unknown'\n | 'mailbox_unknown'\n | 'mailbox_not_yours'\n | 'mailbox_not_sending'\n | 'country_not_in_org'\n\nexport interface OutreachSequenceIssue extends Omit<OutreachValidationIssue, 'code'> {\n code: OutreachSequenceIssueCode\n}\n\n/**\n * What a save may not change once someone is enrolled — see the module\n * note. `previous` is the stored sequence; `next` the draft being saved.\n */\nexport function outreachEnrolledSequenceIssues(\n previous: Pick<OutreachSequenceDraft, 'hostId' | 'mailboxId' | 'steps'>,\n next: Pick<OutreachSequenceDraft, 'hostId' | 'mailboxId' | 'steps'>,\n): OutreachSequenceIssue[] {\n const issues: OutreachSequenceIssue[] = []\n const locked = (path: string, code: OutreachSequenceIssueCode, message: string) =>\n issues.push({ path, code, message, severity: 'error' })\n const kept = previous.steps.every(\n (step, index) => next.steps[index]?.id === step.id && next.steps[index]?.kind === step.kind,\n )\n if (!kept) {\n locked(\n 'steps',\n 'steps_locked',\n \"People are enrolled in this sequence, so its steps can't be removed, reordered or changed to another kind. Edit their wording, add steps at the end, or start a new sequence.\",\n )\n }\n if (previous.hostId !== next.hostId) {\n locked('hostId', 'host_locked', \"People are enrolled from this site's CRM, so the site can't change.\")\n }\n if (previous.mailboxId !== next.mailboxId) {\n locked(\n 'mailboxId',\n 'mailbox_locked',\n \"People are enrolled from this mailbox, so it can't change. Start a new sequence to send from another.\",\n )\n }\n return issues\n}\n\n/**\n * Whether a sequence's mailbox can send right now, as activation asks it:\n * `none` when no mailbox is chosen, `gone` when it was disconnected or is\n * not this organization's, `paused` or `reconnect_required` while it is\n * connected but not sending, and `sending` when it is connected.\n */\nexport type OutreachSequenceMailboxState =\n | 'none'\n | 'gone'\n | 'paused'\n | 'reconnect_required'\n | 'sending'\n\nexport function outreachSequenceMailboxState(\n mailboxId: string,\n mailbox: Pick<OutreachMailbox, 'status'> | null | undefined,\n): OutreachSequenceMailboxState {\n if (!mailboxId) return 'none'\n if (!mailbox || mailbox.status === 'disconnected') return 'gone'\n if (mailbox.status === 'paused' || mailbox.status === 'reconnect_required') {\n return mailbox.status\n }\n return 'sending'\n}\n\n/** What activation refuses with for each mailbox that cannot send. */\nconst MAILBOX_ACTIVATION_ISSUES: Record<\n Exclude<OutreachSequenceMailboxState, 'sending'>,\n { code: OutreachSequenceIssueCode; message: string }\n> = {\n none: {\n code: 'mailbox_required',\n message: 'Choose the mailbox this sequence sends from before activating it.',\n },\n gone: {\n code: 'mailbox_unknown',\n message: \"This sequence's mailbox is no longer connected. Choose another before activating it.\",\n },\n paused: {\n code: 'mailbox_not_sending',\n message: \"This sequence's mailbox is paused. Resume it in Mailboxes, then activate the sequence.\",\n },\n reconnect_required: {\n code: 'mailbox_not_sending',\n message:\n \"Google stopped accepting this sequence's mailbox. Reconnect it in Mailboxes, then activate the sequence.\",\n },\n}\n\n/**\n * Why a sequence cannot be activated on its mailbox, or `null` when it can\n * (AGL-2980). A sequence is activated only onto a mailbox that is sending:\n * connected, not paused, and not waiting for its member to reconnect it —\n * whether or not its steps include an email, because every step's due time\n * is read in the mailbox's timezone and sending hours.\n *\n * The status route refuses with it, and the sequence page shows it beside\n * a disabled Activate button, so the page says why before anyone clicks.\n */\nexport function outreachMailboxActivationIssue(\n mailboxId: string,\n mailbox: Pick<OutreachMailbox, 'status'> | null | undefined,\n): OutreachSequenceIssue | null {\n const state = outreachSequenceMailboxState(mailboxId, mailbox)\n if (state === 'sending') return null\n const { code, message } = MAILBOX_ACTIVATION_ISSUES[state]\n return { path: 'mailboxId', code, message, severity: 'error' }\n}\n"],"names":["readOutreachSequenceSettings","STEPS_READ_MAX","OUTREACH_DEFAULT_FOLLOW_UP_DAYS","text","value","wholeNumber","number","Number","NaN","isInteger","readWindow","raw","days","Array","isArray","map","day","startMinute","endMinute","readStep","id","trim","slice","delayBusinessDays","templateId","step","kind","subject","replyInThread","body","taskKind","title","readOutreachSequenceDraft","input","settings","name","replace","hostId","mailboxId","steps","window","outreachSequenceDraftOf","sequence","newOutreachStepId","random","Math","taken","Set","attempt","drawn","floor","toString","padStart","has","newOutreachEmailStep","follows","some","newOutreachTaskStep","titles","linkedin","call","todo","emptyOutreachSequenceDraft","allowedCountries","allowCustomers","outreachEnrolledSequenceIssues","previous","next","issues","locked","path","code","message","push","severity","kept","every","index","outreachSequenceMailboxState","mailbox","status","MAILBOX_ACTIVATION_ISSUES","none","gone","paused","reconnect_required","outreachMailboxActivationIssue","state"],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { CrmMergeContext } from '@aglyn/aglyn/app-utils/crm-email-templates';
18
+ import { type OutreachComposeResult } from '../engine/compose';
19
+ import type { OutreachOrgSettings, OutreachSequenceStep } from './outreach.types';
20
+ /** The person a preview is written to when it names no contact. */
21
+ export declare const OUTREACH_SAMPLE_PERSON: {
22
+ readonly name: "Casey Morgan";
23
+ readonly email: "casey.morgan@example.com";
24
+ readonly companyName: "Example Co";
25
+ readonly jobTitle: "Head of Operations";
26
+ readonly personalLine: "I saw Example Co just opened its second location.";
27
+ };
28
+ /** The merge context of the sample person, from the sender and site given. */
29
+ export declare function outreachSampleMergeContext(input: {
30
+ sender?: CrmMergeContext['sender'];
31
+ siteName?: string | null;
32
+ }): CrmMergeContext;
33
+ export interface OutreachStepPreviewInput {
34
+ steps: readonly OutreachSequenceStep[];
35
+ stepIndex: number;
36
+ orgSettings: Pick<OutreachOrgSettings, 'legalName' | 'brandName' | 'postalAddress'> | null;
37
+ merge: CrmMergeContext;
38
+ /** The recipient's address. */
39
+ email: string;
40
+ personalLine: string;
41
+ /** The CRM template's body, when the step names a template. */
42
+ templateBody?: string | null;
43
+ }
44
+ /** The step's email, or why it can't be written — see the module note. */
45
+ export declare function previewOutreachStep(input: OutreachStepPreviewInput): OutreachComposeResult;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /*==========================================
17
+ * ONE STEP'S EMAIL, AS A PERSON WOULD GET IT (AGL-2980).
18
+ *
19
+ * The editor's live preview and the preview route both show an email
20
+ * before anyone is sent it, and both write it with the engine's own
21
+ * composer — the merge, the footer, the threading — so what the rep reads
22
+ * is what the runtime would send. Client-safe.
23
+ *
24
+ * A step later in the thread is sent as `Re:` the thread's subject, and the
25
+ * composer refuses to write one without a thread to answer. A preview has
26
+ * no thread, so it lends the composer one: the subject the thread would
27
+ * have been started with, rendered for the same person, and a placeholder
28
+ * message id that never leaves the preview.
29
+ *==========================================*/ import { resolveCrmMergeFields } from "@aglyn/aglyn/app-utils/crm-email-templates";
30
+ import { composeOutreachEmail } from "../engine/compose.js";
31
+ import { isInThreadEmailStep } from "../engine/sequence-validation.js";
32
+ /** The holder id the sample person's facet is filed under. */ const SAMPLE_GROUP = 'outreach-preview';
33
+ /** The person a preview is written to when it names no contact. */ export const OUTREACH_SAMPLE_PERSON = {
34
+ name: 'Casey Morgan',
35
+ email: 'casey.morgan@example.com',
36
+ companyName: 'Example Co',
37
+ jobTitle: 'Head of Operations',
38
+ personalLine: 'I saw Example Co just opened its second location.'
39
+ };
40
+ /** The merge context of the sample person, from the sender and site given. */ export function outreachSampleMergeContext(input) {
41
+ var _input_sender, _input_siteName;
42
+ return {
43
+ contact: {
44
+ name: OUTREACH_SAMPLE_PERSON.name,
45
+ email: OUTREACH_SAMPLE_PERSON.email,
46
+ facets: {
47
+ [SAMPLE_GROUP]: {
48
+ companyName: OUTREACH_SAMPLE_PERSON.companyName,
49
+ jobTitle: OUTREACH_SAMPLE_PERSON.jobTitle
50
+ }
51
+ }
52
+ },
53
+ contactGroupId: SAMPLE_GROUP,
54
+ sender: (_input_sender = input.sender) != null ? _input_sender : null,
55
+ site: {
56
+ name: (_input_siteName = input.siteName) != null ? _input_siteName : ''
57
+ }
58
+ };
59
+ }
60
+ /** The placeholder a preview lends the composer as the thread's last message. */ const PREVIEW_MESSAGE_ID = '<preview@outreach.invalid>';
61
+ /** The step's email, or why it can't be written — see the module note. */ export function previewOutreachStep(input) {
62
+ var _input_templateBody;
63
+ const steps = [
64
+ ...input.steps
65
+ ];
66
+ let threadSubject = null;
67
+ if (isInThreadEmailStep(steps, input.stepIndex)) {
68
+ // The thread was started by the latest email before this one that did
69
+ // not itself reply in a thread.
70
+ for(let index = input.stepIndex - 1; index >= 0; index -= 1){
71
+ const step = steps[index];
72
+ if ((step == null ? void 0 : step.kind) === 'email' && !isInThreadEmailStep(steps, index)) {
73
+ threadSubject = resolveCrmMergeFields(step.subject, input.merge).text.trim() || null;
74
+ break;
75
+ }
76
+ }
77
+ }
78
+ return composeOutreachEmail({
79
+ sequence: {
80
+ steps
81
+ },
82
+ enrollment: {
83
+ email: input.email,
84
+ stepIndex: input.stepIndex,
85
+ personalLine: input.personalLine,
86
+ threadSubject,
87
+ messageIds: threadSubject ? [
88
+ PREVIEW_MESSAGE_ID
89
+ ] : [],
90
+ gmailThreadId: threadSubject ? 'preview' : null
91
+ },
92
+ orgSettings: input.orgSettings,
93
+ merge: input.merge,
94
+ templateBody: (_input_templateBody = input.templateBody) != null ? _input_templateBody : null
95
+ });
96
+ }
97
+
98
+ //# sourceMappingURL=step-preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/model/step-preview.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * ONE STEP'S EMAIL, AS A PERSON WOULD GET IT (AGL-2980).\n *\n * The editor's live preview and the preview route both show an email\n * before anyone is sent it, and both write it with the engine's own\n * composer — the merge, the footer, the threading — so what the rep reads\n * is what the runtime would send. Client-safe.\n *\n * A step later in the thread is sent as `Re:` the thread's subject, and the\n * composer refuses to write one without a thread to answer. A preview has\n * no thread, so it lends the composer one: the subject the thread would\n * have been started with, rendered for the same person, and a placeholder\n * message id that never leaves the preview.\n *==========================================*/\n\nimport type { CrmMergeContext } from '@aglyn/aglyn/app-utils/crm-email-templates'\nimport { resolveCrmMergeFields } from '@aglyn/aglyn/app-utils/crm-email-templates'\nimport { composeOutreachEmail, type OutreachComposeResult } from '../engine/compose'\nimport { isInThreadEmailStep } from '../engine/sequence-validation'\nimport type { OutreachOrgSettings, OutreachSequenceStep } from './outreach.types'\n\n/** The holder id the sample person's facet is filed under. */\nconst SAMPLE_GROUP = 'outreach-preview'\n\n/** The person a preview is written to when it names no contact. */\nexport const OUTREACH_SAMPLE_PERSON = {\n name: 'Casey Morgan',\n email: 'casey.morgan@example.com',\n companyName: 'Example Co',\n jobTitle: 'Head of Operations',\n personalLine: 'I saw Example Co just opened its second location.',\n} as const\n\n/** The merge context of the sample person, from the sender and site given. */\nexport function outreachSampleMergeContext(input: {\n sender?: CrmMergeContext['sender']\n siteName?: string | null\n}): CrmMergeContext {\n return {\n contact: {\n name: OUTREACH_SAMPLE_PERSON.name,\n email: OUTREACH_SAMPLE_PERSON.email,\n facets: {\n [SAMPLE_GROUP]: {\n companyName: OUTREACH_SAMPLE_PERSON.companyName,\n jobTitle: OUTREACH_SAMPLE_PERSON.jobTitle,\n },\n },\n },\n contactGroupId: SAMPLE_GROUP,\n sender: input.sender ?? null,\n site: { name: input.siteName ?? '' },\n }\n}\n\n/** The placeholder a preview lends the composer as the thread's last message. */\nconst PREVIEW_MESSAGE_ID = '<preview@outreach.invalid>'\n\nexport interface OutreachStepPreviewInput {\n steps: readonly OutreachSequenceStep[]\n stepIndex: number\n orgSettings: Pick<OutreachOrgSettings, 'legalName' | 'brandName' | 'postalAddress'> | null\n merge: CrmMergeContext\n /** The recipient's address. */\n email: string\n personalLine: string\n /** The CRM template's body, when the step names a template. */\n templateBody?: string | null\n}\n\n/** The step's email, or why it can't be written — see the module note. */\nexport function previewOutreachStep(input: OutreachStepPreviewInput): OutreachComposeResult {\n const steps = [...input.steps]\n let threadSubject: string | null = null\n if (isInThreadEmailStep(steps, input.stepIndex)) {\n // The thread was started by the latest email before this one that did\n // not itself reply in a thread.\n for (let index = input.stepIndex - 1; index >= 0; index -= 1) {\n const step = steps[index]\n if (step?.kind === 'email' && !isInThreadEmailStep(steps, index)) {\n threadSubject = resolveCrmMergeFields(step.subject, input.merge).text.trim() || null\n break\n }\n }\n }\n return composeOutreachEmail({\n sequence: { steps },\n enrollment: {\n email: input.email,\n stepIndex: input.stepIndex,\n personalLine: input.personalLine,\n threadSubject,\n messageIds: threadSubject ? [PREVIEW_MESSAGE_ID] : [],\n gmailThreadId: threadSubject ? 'preview' : null,\n },\n orgSettings: input.orgSettings,\n merge: input.merge,\n templateBody: input.templateBody ?? null,\n })\n}\n"],"names":["resolveCrmMergeFields","composeOutreachEmail","isInThreadEmailStep","SAMPLE_GROUP","OUTREACH_SAMPLE_PERSON","name","email","companyName","jobTitle","personalLine","outreachSampleMergeContext","input","contact","facets","contactGroupId","sender","site","siteName","PREVIEW_MESSAGE_ID","previewOutreachStep","steps","threadSubject","stepIndex","index","step","kind","subject","merge","text","trim","sequence","enrollment","messageIds","gmailThreadId","orgSettings","templateBody"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;4CAa4C,GAG5C,SAASA,qBAAqB,QAAQ,6CAA4C;AAClF,SAASC,oBAAoB,QAAoC,uBAAmB;AACpF,SAASC,mBAAmB,QAAQ,mCAA+B;AAGnE,4DAA4D,GAC5D,MAAMC,eAAe;AAErB,iEAAiE,GACjE,OAAO,MAAMC,yBAAyB;IACpCC,MAAM;IACNC,OAAO;IACPC,aAAa;IACbC,UAAU;IACVC,cAAc;AAChB,EAAU;AAEV,4EAA4E,GAC5E,OAAO,SAASC,2BAA2BC,KAG1C;QAaWA,eACMA;IAbhB,OAAO;QACLC,SAAS;YACPP,MAAMD,uBAAuBC,IAAI;YACjCC,OAAOF,uBAAuBE,KAAK;YACnCO,QAAQ;gBACN,CAACV,aAAa,EAAE;oBACdI,aAAaH,uBAAuBG,WAAW;oBAC/CC,UAAUJ,uBAAuBI,QAAQ;gBAC3C;YACF;QACF;QACAM,gBAAgBX;QAChBY,MAAM,GAAEJ,gBAAAA,MAAMI,MAAM,YAAZJ,gBAAgB;QACxBK,MAAM;YAAEX,IAAI,GAAEM,kBAAAA,MAAMM,QAAQ,YAAdN,kBAAkB;QAAG;IACrC;AACF;AAEA,+EAA+E,GAC/E,MAAMO,qBAAqB;AAc3B,wEAAwE,GACxE,OAAO,SAASC,oBAAoBR,KAA+B;QA0BjDA;IAzBhB,MAAMS,QAAQ;WAAIT,MAAMS,KAAK;KAAC;IAC9B,IAAIC,gBAA+B;IACnC,IAAInB,oBAAoBkB,OAAOT,MAAMW,SAAS,GAAG;QAC/C,sEAAsE;QACtE,gCAAgC;QAChC,IAAK,IAAIC,QAAQZ,MAAMW,SAAS,GAAG,GAAGC,SAAS,GAAGA,SAAS,EAAG;YAC5D,MAAMC,OAAOJ,KAAK,CAACG,MAAM;YACzB,IAAIC,CAAAA,wBAAAA,KAAMC,IAAI,MAAK,WAAW,CAACvB,oBAAoBkB,OAAOG,QAAQ;gBAChEF,gBAAgBrB,sBAAsBwB,KAAKE,OAAO,EAAEf,MAAMgB,KAAK,EAAEC,IAAI,CAACC,IAAI,MAAM;gBAChF;YACF;QACF;IACF;IACA,OAAO5B,qBAAqB;QAC1B6B,UAAU;YAAEV;QAAM;QAClBW,YAAY;YACVzB,OAAOK,MAAML,KAAK;YAClBgB,WAAWX,MAAMW,SAAS;YAC1Bb,cAAcE,MAAMF,YAAY;YAChCY;YACAW,YAAYX,gBAAgB;gBAACH;aAAmB,GAAG,EAAE;YACrDe,eAAeZ,gBAAgB,YAAY;QAC7C;QACAa,aAAavB,MAAMuB,WAAW;QAC9BP,OAAOhB,MAAMgB,KAAK;QAClBQ,YAAY,GAAExB,sBAAAA,MAAMwB,YAAY,YAAlBxB,sBAAsB;IACtC;AACF"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type OutreachEnrollment, type OutreachMailbox, type OutreachSequence } from './outreach.types';
18
+ /** A stored sequence in its model shape, or `null` for no document. */
19
+ export declare function readStoredOutreachSequence(id: string, data: Record<string, unknown> | undefined): OutreachSequence | null;
20
+ /** A stored enrollment in its model shape, or `null` for no document. */
21
+ export declare function readStoredOutreachEnrollment(id: string, data: Record<string, unknown> | undefined): OutreachEnrollment | null;
22
+ /** A stored mailbox with its id, or `null` for no document. */
23
+ export declare function readStoredOutreachMailbox(id: string, data: Record<string, unknown> | undefined): OutreachMailbox | null;
@@ -0,0 +1,84 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ /**
18
+ * A stored Outreach document read back into its model shape (AGL-2980),
19
+ * for the server's routes and the console's listeners alike. Client-safe.
20
+ *
21
+ * Read DEFENSIVELY, a field at a time, because the readers act on what they
22
+ * read: a sequence's steps are what gets sent, and an enrollment's status is
23
+ * what decides whether it is sent at all.
24
+ */ import { readOutreachSequenceDraft } from "./sequence-draft.js";
25
+ import { OUTREACH_ENROLLMENT_STATUSES, OUTREACH_SEQUENCE_STATUSES, OUTREACH_STOP_REASONS } from "./outreach.types.js";
26
+ const text = (value)=>typeof value === 'string' ? value : '';
27
+ const ms = (value)=>typeof value === 'number' && Number.isFinite(value) ? value : null;
28
+ const texts = (value)=>Array.isArray(value) ? value.filter((entry)=>typeof entry === 'string') : [];
29
+ /** A stored sequence in its model shape, or `null` for no document. */ export function readStoredOutreachSequence(id, data) {
30
+ var _ms, _ms1;
31
+ if (!data) return null;
32
+ const draft = readOutreachSequenceDraft(data);
33
+ const status = data['status'];
34
+ return _extends({
35
+ id
36
+ }, draft, {
37
+ status: OUTREACH_SEQUENCE_STATUSES.includes(status) ? status : 'draft',
38
+ createdAtMs: (_ms = ms(data['createdAtMs'])) != null ? _ms : 0,
39
+ updatedAtMs: (_ms1 = ms(data['updatedAtMs'])) != null ? _ms1 : 0
40
+ });
41
+ }
42
+ /** A stored enrollment in its model shape, or `null` for no document. */ export function readStoredOutreachEnrollment(id, data) {
43
+ var _ms, _ms1;
44
+ if (!data) return null;
45
+ const status = data['status'];
46
+ const stopReason = data['stopReason'];
47
+ const stepIndex = Number(data['stepIndex']);
48
+ return _extends({}, data, {
49
+ id,
50
+ sequenceId: text(data['sequenceId']),
51
+ contactId: text(data['contactId']),
52
+ contactName: text(data['contactName']),
53
+ email: text(data['email']),
54
+ hostId: text(data['hostId']),
55
+ mailboxId: text(data['mailboxId']),
56
+ stepIndex: Number.isInteger(stepIndex) && stepIndex >= 0 ? stepIndex : 0,
57
+ nextDueAtMs: ms(data['nextDueAtMs']),
58
+ // A status the model does not name is read as stopped: nothing sends
59
+ // from a document nobody can say the state of.
60
+ status: OUTREACH_ENROLLMENT_STATUSES.includes(status) ? status : 'stopped',
61
+ stopReason: OUTREACH_STOP_REASONS.includes(stopReason) ? stopReason : null,
62
+ stopDetail: typeof data['stopDetail'] === 'string' ? data['stopDetail'] : null,
63
+ stoppedAtMs: ms(data['stoppedAtMs']),
64
+ stoppedByUid: typeof data['stoppedByUid'] === 'string' ? data['stoppedByUid'] : null,
65
+ personalLine: text(data['personalLine']),
66
+ cold: data['cold'] === true,
67
+ enrolledByUid: text(data['enrolledByUid']),
68
+ gmailThreadId: typeof data['gmailThreadId'] === 'string' ? data['gmailThreadId'] : null,
69
+ gmailThreadIds: texts(data['gmailThreadIds']),
70
+ threadSubject: typeof data['threadSubject'] === 'string' ? data['threadSubject'] : null,
71
+ messageIds: texts(data['messageIds']),
72
+ lastSentAtMs: ms(data['lastSentAtMs']),
73
+ createdAtMs: (_ms = ms(data['createdAtMs'])) != null ? _ms : 0,
74
+ updatedAtMs: (_ms1 = ms(data['updatedAtMs'])) != null ? _ms1 : 0
75
+ });
76
+ }
77
+ /** A stored mailbox with its id, or `null` for no document. */ export function readStoredOutreachMailbox(id, data) {
78
+ if (!data) return null;
79
+ return _extends({}, data, {
80
+ id
81
+ });
82
+ }
83
+
84
+ //# sourceMappingURL=stored-records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/model/stored-records.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A stored Outreach document read back into its model shape (AGL-2980),\n * for the server's routes and the console's listeners alike. Client-safe.\n *\n * Read DEFENSIVELY, a field at a time, because the readers act on what they\n * read: a sequence's steps are what gets sent, and an enrollment's status is\n * what decides whether it is sent at all.\n */\n\nimport { readOutreachSequenceDraft } from './sequence-draft'\nimport {\n OUTREACH_ENROLLMENT_STATUSES,\n OUTREACH_SEQUENCE_STATUSES,\n OUTREACH_STOP_REASONS,\n type OutreachEnrollment,\n type OutreachEnrollmentStatus,\n type OutreachMailbox,\n type OutreachSequence,\n type OutreachSequenceStatus,\n type OutreachStopReason,\n} from './outreach.types'\n\nconst text = (value: unknown): string =>\n typeof value === 'string' ? value : ''\nconst ms = (value: unknown): number | null =>\n typeof value === 'number' && Number.isFinite(value) ? value : null\nconst texts = (value: unknown): string[] =>\n Array.isArray(value)\n ? value.filter((entry): entry is string => typeof entry === 'string')\n : []\n\n/** A stored sequence in its model shape, or `null` for no document. */\nexport function readStoredOutreachSequence(\n id: string,\n data: Record<string, unknown> | undefined,\n): OutreachSequence | null {\n if (!data) return null\n const draft = readOutreachSequenceDraft(data)\n const status = data['status']\n return {\n id,\n ...draft,\n status: (OUTREACH_SEQUENCE_STATUSES as readonly unknown[]).includes(status)\n ? (status as OutreachSequenceStatus)\n : 'draft',\n createdAtMs: ms(data['createdAtMs']) ?? 0,\n updatedAtMs: ms(data['updatedAtMs']) ?? 0,\n }\n}\n\n/** A stored enrollment in its model shape, or `null` for no document. */\nexport function readStoredOutreachEnrollment(\n id: string,\n data: Record<string, unknown> | undefined,\n): OutreachEnrollment | null {\n if (!data) return null\n const status = data['status']\n const stopReason = data['stopReason']\n const stepIndex = Number(data['stepIndex'])\n return {\n ...(data as unknown as OutreachEnrollment),\n id,\n sequenceId: text(data['sequenceId']),\n contactId: text(data['contactId']),\n contactName: text(data['contactName']),\n email: text(data['email']),\n hostId: text(data['hostId']),\n mailboxId: text(data['mailboxId']),\n stepIndex: Number.isInteger(stepIndex) && stepIndex >= 0 ? stepIndex : 0,\n nextDueAtMs: ms(data['nextDueAtMs']),\n // A status the model does not name is read as stopped: nothing sends\n // from a document nobody can say the state of.\n status: (OUTREACH_ENROLLMENT_STATUSES as readonly unknown[]).includes(\n status,\n )\n ? (status as OutreachEnrollmentStatus)\n : 'stopped',\n stopReason: (OUTREACH_STOP_REASONS as readonly unknown[]).includes(\n stopReason,\n )\n ? (stopReason as OutreachStopReason)\n : null,\n stopDetail:\n typeof data['stopDetail'] === 'string' ? data['stopDetail'] : null,\n stoppedAtMs: ms(data['stoppedAtMs']),\n stoppedByUid:\n typeof data['stoppedByUid'] === 'string' ? data['stoppedByUid'] : null,\n personalLine: text(data['personalLine']),\n cold: data['cold'] === true,\n enrolledByUid: text(data['enrolledByUid']),\n gmailThreadId:\n typeof data['gmailThreadId'] === 'string' ? data['gmailThreadId'] : null,\n gmailThreadIds: texts(data['gmailThreadIds']),\n threadSubject:\n typeof data['threadSubject'] === 'string' ? data['threadSubject'] : null,\n messageIds: texts(data['messageIds']),\n lastSentAtMs: ms(data['lastSentAtMs']),\n createdAtMs: ms(data['createdAtMs']) ?? 0,\n updatedAtMs: ms(data['updatedAtMs']) ?? 0,\n }\n}\n\n/** A stored mailbox with its id, or `null` for no document. */\nexport function readStoredOutreachMailbox(\n id: string,\n data: Record<string, unknown> | undefined,\n): OutreachMailbox | null {\n if (!data) return null\n return { ...(data as unknown as OutreachMailbox), id }\n}\n"],"names":["readOutreachSequenceDraft","OUTREACH_ENROLLMENT_STATUSES","OUTREACH_SEQUENCE_STATUSES","OUTREACH_STOP_REASONS","text","value","ms","Number","isFinite","texts","Array","isArray","filter","entry","readStoredOutreachSequence","id","data","draft","status","includes","createdAtMs","updatedAtMs","readStoredOutreachEnrollment","stopReason","stepIndex","sequenceId","contactId","contactName","email","hostId","mailboxId","isInteger","nextDueAtMs","stopDetail","stoppedAtMs","stoppedByUid","personalLine","cold","enrolledByUid","gmailThreadId","gmailThreadIds","threadSubject","messageIds","lastSentAtMs","readStoredOutreachMailbox"],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * The console half of this plugin (AGL-2974), named in `plugins.config.json`
19
+ * as `console`. Console and console API only, like the CRM: a sequence has no
20
+ * canvas element, so there is no site bundle and no tenant half.
21
+ *
22
+ * THE NAME PEOPLE READ IS "SEQUENCES" (AGL-3199); the plugin id, the lib, the
23
+ * package, the flag, the entitlement, the permission, the collections and the
24
+ * API prefix all stay `outreach`. Renaming those would move stored data and
25
+ * break the unsubscribe links already sent, and no reader sees them.
26
+ *
27
+ * ONE ORGANIZATION-LEVEL SURFACE. Sequences are a rep's work across every site
28
+ * the organization sells for, sent from that rep's own mailbox, so they live
29
+ * beside the organization's other tabs rather than under a site. It is
30
+ * declared in `orgNavItems`, which the console serves at
31
+ * `/[orgSlug]/outreach/<section>` through its generic org route.
32
+ *
33
+ * THREE GATES, each answered by the shell, never by this page:
34
+ *
35
+ * - the RELEASE flag `release_outreach`, reached through the nav item's
36
+ * `navTabId` — off by default, so only staff preview it;
37
+ * - the ENTITLEMENT `features.outreach`, which no plan carries, so an
38
+ * organization has it only through its per-org override;
39
+ * - the PERMISSION `outreach.use`, which owners and admins hold by default.
40
+ *
41
+ * The org tab strip hides the tab unless all three hold. A deep link is
42
+ * answered by the route with the refusal that applies, and the words for the
43
+ * entitlement one are this extension's own: the shell's derived sentence
44
+ * calls a feature no plan carries "a paid add-on", which this is not.
45
+ */
46
+ export declare function registerOutreachConsole(): void;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { registerConsoleExtension, registerPluginPermissions } from "@aglyn/aglyn";
17
+ import { mdiEmailFastOutline } from "@aglyn/shared-data-mdi";
18
+ import { lazy } from "react";
19
+ import { OUTREACH_CONSOLE_SECTIONS } from "./components/outreach-console-sections.js";
20
+ import { OUTREACH_PERMISSIONS, OUTREACH_PLUGIN_ID, OUTREACH_USE_PERMISSION } from "./constants/bundle-common.js";
21
+ /** Code-split: the hub only loads when opened. */ const OutreachConsolePage = lazy(()=>import("./components/outreach-console-page.js"));
22
+ /**
23
+ * The console half of this plugin (AGL-2974), named in `plugins.config.json`
24
+ * as `console`. Console and console API only, like the CRM: a sequence has no
25
+ * canvas element, so there is no site bundle and no tenant half.
26
+ *
27
+ * THE NAME PEOPLE READ IS "SEQUENCES" (AGL-3199); the plugin id, the lib, the
28
+ * package, the flag, the entitlement, the permission, the collections and the
29
+ * API prefix all stay `outreach`. Renaming those would move stored data and
30
+ * break the unsubscribe links already sent, and no reader sees them.
31
+ *
32
+ * ONE ORGANIZATION-LEVEL SURFACE. Sequences are a rep's work across every site
33
+ * the organization sells for, sent from that rep's own mailbox, so they live
34
+ * beside the organization's other tabs rather than under a site. It is
35
+ * declared in `orgNavItems`, which the console serves at
36
+ * `/[orgSlug]/outreach/<section>` through its generic org route.
37
+ *
38
+ * THREE GATES, each answered by the shell, never by this page:
39
+ *
40
+ * - the RELEASE flag `release_outreach`, reached through the nav item's
41
+ * `navTabId` — off by default, so only staff preview it;
42
+ * - the ENTITLEMENT `features.outreach`, which no plan carries, so an
43
+ * organization has it only through its per-org override;
44
+ * - the PERMISSION `outreach.use`, which owners and admins hold by default.
45
+ *
46
+ * The org tab strip hides the tab unless all three hold. A deep link is
47
+ * answered by the route with the refusal that applies, and the words for the
48
+ * entitlement one are this extension's own: the shell's derived sentence
49
+ * calls a feature no plan carries "a paid add-on", which this is not.
50
+ */ export function registerOutreachConsole() {
51
+ registerPluginPermissions(OUTREACH_PERMISSIONS);
52
+ registerConsoleExtension({
53
+ pluginId: OUTREACH_PLUGIN_ID,
54
+ displayName: 'Sequences',
55
+ permission: OUTREACH_USE_PERMISSION,
56
+ featureFlag: 'outreach',
57
+ upgradeNotice: {
58
+ message: "Sequences isn't available to this workspace yet."
59
+ },
60
+ orgNavItems: [
61
+ {
62
+ label: 'Sequences',
63
+ // The URL slug stays `outreach`: it is the plugin id the console
64
+ // resolves the surface by, and `PLUGIN_TITLES` carries the name.
65
+ href: '/outreach',
66
+ sections: OUTREACH_CONSOLE_SECTIONS,
67
+ // The release flag's tab id: `release_outreach` names it, and the org
68
+ // strip hides the tab from customers while the flag is off.
69
+ navTabId: 'nav-tab-org-outreach',
70
+ icon: {
71
+ path: mdiEmailFastOutline.path
72
+ },
73
+ header: {
74
+ title: 'Sequences',
75
+ icon: {
76
+ path: mdiEmailFastOutline.path
77
+ },
78
+ docsTopic: 'sequences'
79
+ },
80
+ Component: OutreachConsolePage
81
+ }
82
+ ]
83
+ });
84
+ }
85
+
86
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/plugins/outreach/src/lib/plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n registerConsoleExtension,\n registerPluginPermissions,\n} from '@aglyn/aglyn'\nimport { mdiEmailFastOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport { OUTREACH_CONSOLE_SECTIONS } from './components/outreach-console-sections'\nimport {\n OUTREACH_PERMISSIONS,\n OUTREACH_PLUGIN_ID,\n OUTREACH_USE_PERMISSION,\n} from './constants/bundle-common'\n\n/** Code-split: the hub only loads when opened. */\nconst OutreachConsolePage = lazy(\n () => import('./components/outreach-console-page'),\n)\n\n/**\n * The console half of this plugin (AGL-2974), named in `plugins.config.json`\n * as `console`. Console and console API only, like the CRM: a sequence has no\n * canvas element, so there is no site bundle and no tenant half.\n *\n * THE NAME PEOPLE READ IS \"SEQUENCES\" (AGL-3199); the plugin id, the lib, the\n * package, the flag, the entitlement, the permission, the collections and the\n * API prefix all stay `outreach`. Renaming those would move stored data and\n * break the unsubscribe links already sent, and no reader sees them.\n *\n * ONE ORGANIZATION-LEVEL SURFACE. Sequences are a rep's work across every site\n * the organization sells for, sent from that rep's own mailbox, so they live\n * beside the organization's other tabs rather than under a site. It is\n * declared in `orgNavItems`, which the console serves at\n * `/[orgSlug]/outreach/<section>` through its generic org route.\n *\n * THREE GATES, each answered by the shell, never by this page:\n *\n * - the RELEASE flag `release_outreach`, reached through the nav item's\n * `navTabId` — off by default, so only staff preview it;\n * - the ENTITLEMENT `features.outreach`, which no plan carries, so an\n * organization has it only through its per-org override;\n * - the PERMISSION `outreach.use`, which owners and admins hold by default.\n *\n * The org tab strip hides the tab unless all three hold. A deep link is\n * answered by the route with the refusal that applies, and the words for the\n * entitlement one are this extension's own: the shell's derived sentence\n * calls a feature no plan carries \"a paid add-on\", which this is not.\n */\nexport function registerOutreachConsole(): void {\n registerPluginPermissions(OUTREACH_PERMISSIONS)\n registerConsoleExtension({\n pluginId: OUTREACH_PLUGIN_ID,\n displayName: 'Sequences',\n permission: OUTREACH_USE_PERMISSION,\n featureFlag: 'outreach',\n upgradeNotice: {\n message: \"Sequences isn't available to this workspace yet.\",\n },\n orgNavItems: [\n {\n label: 'Sequences',\n // The URL slug stays `outreach`: it is the plugin id the console\n // resolves the surface by, and `PLUGIN_TITLES` carries the name.\n href: '/outreach',\n sections: OUTREACH_CONSOLE_SECTIONS,\n // The release flag's tab id: `release_outreach` names it, and the org\n // strip hides the tab from customers while the flag is off.\n navTabId: 'nav-tab-org-outreach',\n icon: { path: mdiEmailFastOutline.path },\n header: {\n title: 'Sequences',\n icon: { path: mdiEmailFastOutline.path },\n docsTopic: 'sequences',\n },\n Component: OutreachConsolePage,\n },\n ],\n })\n}\n"],"names":["registerConsoleExtension","registerPluginPermissions","mdiEmailFastOutline","lazy","OUTREACH_CONSOLE_SECTIONS","OUTREACH_PERMISSIONS","OUTREACH_PLUGIN_ID","OUTREACH_USE_PERMISSION","OutreachConsolePage","registerOutreachConsole","pluginId","displayName","permission","featureFlag","upgradeNotice","message","orgNavItems","label","href","sections","navTabId","icon","path","header","title","docsTopic","Component"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,wBAAwB,EACxBC,yBAAyB,QACpB,eAAc;AACrB,SAASC,mBAAmB,QAAQ,yBAAwB;AAC5D,SAASC,IAAI,QAAQ,QAAO;AAC5B,SAASC,yBAAyB,QAAQ,4CAAwC;AAClF,SACEC,oBAAoB,EACpBC,kBAAkB,EAClBC,uBAAuB,QAClB,+BAA2B;AAElC,gDAAgD,GAChD,MAAMC,sBAAsBL,KAC1B,IAAM,MAAM,CAAC;AAGf;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,SAASM;IACdR,0BAA0BI;IAC1BL,yBAAyB;QACvBU,UAAUJ;QACVK,aAAa;QACbC,YAAYL;QACZM,aAAa;QACbC,eAAe;YACbC,SAAS;QACX;QACAC,aAAa;YACX;gBACEC,OAAO;gBACP,iEAAiE;gBACjE,iEAAiE;gBACjEC,MAAM;gBACNC,UAAUf;gBACV,sEAAsE;gBACtE,4DAA4D;gBAC5DgB,UAAU;gBACVC,MAAM;oBAAEC,MAAMpB,oBAAoBoB,IAAI;gBAAC;gBACvCC,QAAQ;oBACNC,OAAO;oBACPH,MAAM;wBAAEC,MAAMpB,oBAAoBoB,IAAI;oBAAC;oBACvCG,WAAW;gBACb;gBACAC,WAAWlB;YACb;SACD;IACH;AACF"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { PluginWebApiHandler } from '@aglyn/aglyn/server';
18
+ import { type OutreachRouteDeps } from './route-deps';
19
+ import { type OutreachRouteExtraPermission } from './route-gate';
20
+ /**
21
+ * ENROLLING PEOPLE IN A SEQUENCE (AGL-2980): `outreach/enroll/preview` and
22
+ * `outreach/enroll`.
23
+ *
24
+ * The preview reads the people a saved Contacts view or a search named —
25
+ * at most {@link OUTREACH_ENROLL_BATCH_MAX} — and says where each stands:
26
+ * eligible, blocked with the gate's own sentence, or waiting on the rep for
27
+ * a personal line and the attestations only they can make. Confirm reads
28
+ * everything again and asks the gates again with what the rep supplied,
29
+ * because a preview is a picture of a moment: someone can unsubscribe
30
+ * between the two, and a request can claim anything. Nothing the preview
31
+ * answered is trusted by the enroll.
32
+ *
33
+ * Both read the CRM's people, so both ask for `data.manage` beside
34
+ * `outreach.use` — the permission the contacts' own rules read with, so
35
+ * Outreach is no side door into the address book.
36
+ *
37
+ * Only an ACTIVE sequence takes people: enrolling schedules the first step
38
+ * in the mailbox's hours, and a draft's steps and a paused sequence's timing
39
+ * are not yet what will be sent.
40
+ */
41
+ /** The permission enrolling reads the CRM with. */
42
+ export declare const OUTREACH_READS_PEOPLE: OutreachRouteExtraPermission;
43
+ /** Everything enrolling reaches beyond the routes' own dependencies. */
44
+ export interface OutreachEnrollRouteDeps extends OutreachRouteDeps {
45
+ /**
46
+ * The addresses a saved Contacts view selects among one site's contacts,
47
+ * read the way the dynamic-list sweep reads one, and whether the read
48
+ * reached the whole view before its budget.
49
+ */
50
+ crmViewEmails(input: {
51
+ hostId: string;
52
+ viewId: string;
53
+ }): Promise<{
54
+ emails: string[];
55
+ complete: boolean;
56
+ }>;
57
+ }
58
+ export interface OutreachEnrollRoutes {
59
+ preview: PluginWebApiHandler;
60
+ confirm: PluginWebApiHandler;
61
+ }
62
+ /** The activity line an enroll writes. */
63
+ export declare function outreachEnrolledActivity(count: number): string;
64
+ export declare function createOutreachEnrollRoutes(deps: OutreachEnrollRouteDeps): OutreachEnrollRoutes;