@graph8/sdk 0.308.1 → 0.357.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (654) hide show
  1. package/README.md +81 -0
  2. package/blocks/README.md +32 -0
  3. package/blocks/_foundation/README.md +78 -0
  4. package/blocks/_foundation/block.json +79 -0
  5. package/blocks/_foundation/components/api-error.tsx +62 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +142 -0
  7. package/blocks/_foundation/components/empty-state.tsx +24 -0
  8. package/blocks/_foundation/hooks/use-g8-mutation.ts +88 -0
  9. package/blocks/_foundation/hooks/use-g8-query.ts +87 -0
  10. package/blocks/_foundation/hooks/use-job-poll.ts +130 -0
  11. package/blocks/_foundation/hooks/use-paginated.ts +105 -0
  12. package/blocks/_foundation/lib/g8-client.ts +128 -0
  13. package/blocks/_foundation/recipes/verify-proxy-key.ts +18 -0
  14. package/blocks/_foundation/server/proxy.ts +233 -0
  15. package/blocks/_foundation/server/route.ts +30 -0
  16. package/blocks/agents/README.md +133 -0
  17. package/blocks/agents/block.json +223 -0
  18. package/blocks/agents/components/agent-builder.tsx +199 -0
  19. package/blocks/agents/components/agent-knowledge-panel.tsx +144 -0
  20. package/blocks/agents/components/agent-runs.tsx +175 -0
  21. package/blocks/agents/components/agent-skills-panel.tsx +126 -0
  22. package/blocks/agents/components/agent-test-console.tsx +175 -0
  23. package/blocks/agents/lib/agent-types.ts +114 -0
  24. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +50 -0
  25. package/blocks/agents/recipes/run-and-follow-agent-run.ts +46 -0
  26. package/blocks/agents/recipes/test-agent-before-activating.ts +36 -0
  27. package/blocks/analytics/README.md +73 -0
  28. package/blocks/analytics/block.json +178 -0
  29. package/blocks/analytics/components/funnel-chart.tsx +79 -0
  30. package/blocks/analytics/components/kpi-tiles.tsx +74 -0
  31. package/blocks/analytics/components/leaderboard.tsx +86 -0
  32. package/blocks/analytics/components/report-builder.tsx +200 -0
  33. package/blocks/analytics/hooks/use-report.ts +26 -0
  34. package/blocks/analytics/lib/analytics-types.ts +108 -0
  35. package/blocks/analytics/lib/use-debounced.ts +16 -0
  36. package/blocks/analytics/recipes/01-weekly-outbound-report.ts +32 -0
  37. package/blocks/analytics/recipes/02-revenue-snapshot.ts +22 -0
  38. package/blocks/analytics/recipes/03-saved-report.ts +30 -0
  39. package/blocks/appointments/README.md +80 -0
  40. package/blocks/appointments/block.json +218 -0
  41. package/blocks/appointments/components/availability-editor.tsx +192 -0
  42. package/blocks/appointments/components/booking-page.tsx +154 -0
  43. package/blocks/appointments/components/event-type-editor.tsx +214 -0
  44. package/blocks/appointments/components/meeting-detail.tsx +147 -0
  45. package/blocks/appointments/components/meeting-list.tsx +148 -0
  46. package/blocks/appointments/hooks/use-available-slots.ts +29 -0
  47. package/blocks/appointments/hooks/use-schedules.ts +18 -0
  48. package/blocks/appointments/lib/appointments-types.ts +159 -0
  49. package/blocks/appointments/lib/idempotency.ts +30 -0
  50. package/blocks/appointments/recipes/01-create-event-type-and-hours.ts +27 -0
  51. package/blocks/appointments/recipes/02-find-slots-and-book.ts +34 -0
  52. package/blocks/appointments/recipes/03-bookings-digest.ts +22 -0
  53. package/blocks/appointments/recipes/04-calendar-invite.ts +21 -0
  54. package/blocks/block.schema.json +130 -0
  55. package/blocks/block.types.ts +109 -0
  56. package/blocks/campaigns/README.md +118 -0
  57. package/blocks/campaigns/block.json +361 -0
  58. package/blocks/campaigns/components/campaign-assets.tsx +180 -0
  59. package/blocks/campaigns/components/campaign-audience-step.tsx +91 -0
  60. package/blocks/campaigns/components/campaign-launch-step.tsx +147 -0
  61. package/blocks/campaigns/components/campaign-list.tsx +120 -0
  62. package/blocks/campaigns/components/campaign-metrics.tsx +93 -0
  63. package/blocks/campaigns/components/campaign-select.tsx +52 -0
  64. package/blocks/campaigns/components/campaign-sequence-step.tsx +158 -0
  65. package/blocks/campaigns/components/campaign-wizard.tsx +238 -0
  66. package/blocks/campaigns/components/form-field.tsx +29 -0
  67. package/blocks/campaigns/components/icp-editor.tsx +230 -0
  68. package/blocks/campaigns/components/persona-editor.tsx +210 -0
  69. package/blocks/campaigns/hooks/use-company-profile.ts +34 -0
  70. package/blocks/campaigns/lib/campaigns-utils.ts +155 -0
  71. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +33 -0
  72. package/blocks/campaigns/recipes/campaign-report.ts +22 -0
  73. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +51 -0
  74. package/blocks/campaigns/recipes/set-up-icp-and-personas.ts +37 -0
  75. package/blocks/coach/README.md +93 -0
  76. package/blocks/coach/block.json +125 -0
  77. package/blocks/coach/components/call-scorecard.tsx +126 -0
  78. package/blocks/coach/components/coaching-feed.tsx +140 -0
  79. package/blocks/coach/lib/coach-types.ts +116 -0
  80. package/blocks/coach/recipes/read-a-scorecard.ts +27 -0
  81. package/blocks/coach/recipes/weekly-coaching-digest.ts +53 -0
  82. package/blocks/copilot/README.md +99 -0
  83. package/blocks/copilot/block.json +151 -0
  84. package/blocks/copilot/components/copilot-memory.tsx +87 -0
  85. package/blocks/copilot/components/copilot-panel.tsx +189 -0
  86. package/blocks/copilot/components/copilot-team-chat.tsx +111 -0
  87. package/blocks/copilot/lib/copilot-types.ts +111 -0
  88. package/blocks/copilot/recipes/ask-the-copilot.ts +35 -0
  89. package/blocks/copilot/recipes/post-in-a-work-channel.ts +33 -0
  90. package/blocks/copilot/recipes/remember-and-recall.ts +19 -0
  91. package/blocks/crm/README.md +122 -0
  92. package/blocks/crm/block.json +378 -0
  93. package/blocks/crm/components/activity-timeline.tsx +99 -0
  94. package/blocks/crm/components/company-detail.tsx +118 -0
  95. package/blocks/crm/components/company-table.tsx +173 -0
  96. package/blocks/crm/components/contact-detail.tsx +135 -0
  97. package/blocks/crm/components/contact-table.tsx +217 -0
  98. package/blocks/crm/components/dedupe-review.tsx +164 -0
  99. package/blocks/crm/components/import-wizard.tsx +193 -0
  100. package/blocks/crm/components/notes-panel.tsx +112 -0
  101. package/blocks/crm/components/record-form.tsx +191 -0
  102. package/blocks/crm/components/task-list.tsx +107 -0
  103. package/blocks/crm/hooks/use-custom-fields.ts +18 -0
  104. package/blocks/crm/hooks/use-debounced.ts +13 -0
  105. package/blocks/crm/hooks/use-page-query.ts +52 -0
  106. package/blocks/crm/lib/crm-types.ts +136 -0
  107. package/blocks/crm/recipes/custom-fields-and-values.ts +28 -0
  108. package/blocks/crm/recipes/dedupe-and-merge.ts +30 -0
  109. package/blocks/crm/recipes/notes-tasks-and-activity.ts +23 -0
  110. package/blocks/crm/recipes/upsert-contact-with-company.ts +33 -0
  111. package/blocks/deals/README.md +78 -0
  112. package/blocks/deals/block.json +198 -0
  113. package/blocks/deals/components/deal-detail.tsx +134 -0
  114. package/blocks/deals/components/forecast-view.tsx +112 -0
  115. package/blocks/deals/components/pipeline-board.tsx +142 -0
  116. package/blocks/deals/components/stage-editor.tsx +96 -0
  117. package/blocks/deals/hooks/use-pipeline-deals.ts +67 -0
  118. package/blocks/deals/hooks/use-pipelines.ts +19 -0
  119. package/blocks/deals/lib/deals-types.ts +128 -0
  120. package/blocks/deals/recipes/01-create-deal-and-advance.ts +32 -0
  121. package/blocks/deals/recipes/02-pipeline-report.ts +35 -0
  122. package/blocks/deals/recipes/03-stale-deals-sweep.ts +20 -0
  123. package/blocks/dialer/README.md +114 -0
  124. package/blocks/dialer/block.json +260 -0
  125. package/blocks/dialer/components/call-log.tsx +78 -0
  126. package/blocks/dialer/components/call-queue.tsx +188 -0
  127. package/blocks/dialer/components/dialer-workspace.tsx +47 -0
  128. package/blocks/dialer/components/disposition-panel.tsx +75 -0
  129. package/blocks/dialer/components/live-calls-table.tsx +107 -0
  130. package/blocks/dialer/components/session-start-form.tsx +111 -0
  131. package/blocks/dialer/components/softphone.tsx +122 -0
  132. package/blocks/dialer/components/transcript-viewer.tsx +82 -0
  133. package/blocks/dialer/hooks/use-dialer-numbers.ts +19 -0
  134. package/blocks/dialer/hooks/use-dialer-session.ts +68 -0
  135. package/blocks/dialer/hooks/use-dispositions.ts +29 -0
  136. package/blocks/dialer/lib/dialer-types.ts +158 -0
  137. package/blocks/dialer/lib/softphone-transport.ts +67 -0
  138. package/blocks/dialer/lib/twilio-transport.ts +74 -0
  139. package/blocks/dialer/recipes/01-create-session-and-dial.ts +39 -0
  140. package/blocks/dialer/recipes/02-disposition-calls.ts +26 -0
  141. package/blocks/dialer/recipes/03-review-a-call.ts +23 -0
  142. package/blocks/enrichment/README.md +105 -0
  143. package/blocks/enrichment/block.json +204 -0
  144. package/blocks/enrichment/components/bulk-enrich-panel.tsx +205 -0
  145. package/blocks/enrichment/components/email-verify-badge.tsx +82 -0
  146. package/blocks/enrichment/components/enrich-button.tsx +136 -0
  147. package/blocks/enrichment/components/waterfall-config.tsx +127 -0
  148. package/blocks/enrichment/hooks/use-providers.ts +18 -0
  149. package/blocks/enrichment/lib/enrichment-types.ts +104 -0
  150. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +41 -0
  151. package/blocks/enrichment/recipes/lookup-and-verify.ts +25 -0
  152. package/blocks/enrichment/recipes/waterfall-pipeline.ts +26 -0
  153. package/blocks/forms/README.md +112 -0
  154. package/blocks/forms/block.json +200 -0
  155. package/blocks/forms/components/form-builder.tsx +150 -0
  156. package/blocks/forms/components/form-embed.tsx +103 -0
  157. package/blocks/forms/components/form-mapping-editor.tsx +125 -0
  158. package/blocks/forms/components/site-audit-panel.tsx +73 -0
  159. package/blocks/forms/components/submissions-table.tsx +158 -0
  160. package/blocks/forms/lib/form-types.ts +127 -0
  161. package/blocks/forms/recipes/install-snippet-and-form.ts +21 -0
  162. package/blocks/forms/recipes/retry-failed-deliveries.ts +26 -0
  163. package/blocks/forms/recipes/route-form-to-list.ts +44 -0
  164. package/blocks/inbox/README.md +72 -0
  165. package/blocks/inbox/block.json +194 -0
  166. package/blocks/inbox/components/inbox-list.tsx +156 -0
  167. package/blocks/inbox/components/inbox-screen.tsx +49 -0
  168. package/blocks/inbox/components/reply-composer.tsx +183 -0
  169. package/blocks/inbox/components/tag-and-assign.tsx +189 -0
  170. package/blocks/inbox/components/thread-badge.tsx +17 -0
  171. package/blocks/inbox/components/thread-view.tsx +108 -0
  172. package/blocks/inbox/hooks/use-inbox-thread.ts +24 -0
  173. package/blocks/inbox/hooks/use-inbox-threads.ts +40 -0
  174. package/blocks/inbox/lib/idempotency.ts +30 -0
  175. package/blocks/inbox/lib/inbox-types.ts +100 -0
  176. package/blocks/inbox/recipes/01-triage-and-reply.ts +43 -0
  177. package/blocks/inbox/recipes/02-tag-and-assign.ts +31 -0
  178. package/blocks/inbox/recipes/03-hot-contacts-digest.ts +16 -0
  179. package/blocks/integrations/README.md +75 -0
  180. package/blocks/integrations/block.json +172 -0
  181. package/blocks/integrations/components/integration-list.tsx +165 -0
  182. package/blocks/integrations/components/sync-status.tsx +223 -0
  183. package/blocks/integrations/components/webhook-manager.tsx +300 -0
  184. package/blocks/integrations/lib/integrations-utils.ts +62 -0
  185. package/blocks/integrations/recipes/crm-sync-health.ts +19 -0
  186. package/blocks/integrations/recipes/push-contacts-to-crm.ts +20 -0
  187. package/blocks/integrations/recipes/register-webhook.ts +18 -0
  188. package/blocks/integrations/recipes/verify-webhook-route.ts +33 -0
  189. package/blocks/intent/README.md +57 -0
  190. package/blocks/intent/block.json +138 -0
  191. package/blocks/intent/components/account-intent-score.tsx +91 -0
  192. package/blocks/intent/components/intent-feed.tsx +215 -0
  193. package/blocks/intent/components/intent-keyword-manager.tsx +331 -0
  194. package/blocks/intent/hooks/use-account-intent.ts +46 -0
  195. package/blocks/intent/lib/intent-utils.ts +95 -0
  196. package/blocks/intent/recipes/rank-intent-accounts.ts +36 -0
  197. package/blocks/intent/recipes/track-competitor-domain.ts +23 -0
  198. package/blocks/intent/recipes/who-visited-url.ts +18 -0
  199. package/blocks/lifecycle/README.md +79 -0
  200. package/blocks/lifecycle/block.json +198 -0
  201. package/blocks/lifecycle/components/churn-list.tsx +92 -0
  202. package/blocks/lifecycle/components/customer-table.tsx +119 -0
  203. package/blocks/lifecycle/components/lead-queue.tsx +127 -0
  204. package/blocks/lifecycle/components/lifecycle-board.tsx +102 -0
  205. package/blocks/lifecycle/components/stage-move-dialog.tsx +75 -0
  206. package/blocks/lifecycle/hooks/use-debounced.ts +13 -0
  207. package/blocks/lifecycle/hooks/use-lifecycle-stages.ts +27 -0
  208. package/blocks/lifecycle/lib/lifecycle.ts +99 -0
  209. package/blocks/lifecycle/recipes/customer-brief.ts +23 -0
  210. package/blocks/lifecycle/recipes/move-company-stage.ts +17 -0
  211. package/blocks/lifecycle/recipes/read-lifecycle-board.ts +21 -0
  212. package/blocks/lifecycle/recipes/unlock-stage-contacts.ts +25 -0
  213. package/blocks/lists/README.md +96 -0
  214. package/blocks/lists/block.json +166 -0
  215. package/blocks/lists/components/add-to-list-button.tsx +111 -0
  216. package/blocks/lists/components/list-picker.tsx +94 -0
  217. package/blocks/lists/components/list-table.tsx +160 -0
  218. package/blocks/lists/components/suppression-manager.tsx +153 -0
  219. package/blocks/lists/recipes/create-list-and-add-contacts.ts +35 -0
  220. package/blocks/lists/recipes/refresh-and-check-lists.ts +21 -0
  221. package/blocks/lists/recipes/suppress-and-reinstate.ts +30 -0
  222. package/blocks/mailboxes/README.md +71 -0
  223. package/blocks/mailboxes/block.json +187 -0
  224. package/blocks/mailboxes/components/connect-mailbox.tsx +284 -0
  225. package/blocks/mailboxes/components/domain-purchase.tsx +271 -0
  226. package/blocks/mailboxes/components/mailbox-list.tsx +185 -0
  227. package/blocks/mailboxes/components/warmup-health.tsx +232 -0
  228. package/blocks/mailboxes/lib/mailboxes-utils.ts +69 -0
  229. package/blocks/mailboxes/recipes/buy-domain-and-mailboxes.ts +64 -0
  230. package/blocks/mailboxes/recipes/connect-imap-mailbox.ts +38 -0
  231. package/blocks/mailboxes/recipes/is-it-safe-to-send.ts +17 -0
  232. package/blocks/mailboxes/recipes/warmup-report.ts +31 -0
  233. package/blocks/newsletter/README.md +122 -0
  234. package/blocks/newsletter/block.json +224 -0
  235. package/blocks/newsletter/components/issue-form.tsx +124 -0
  236. package/blocks/newsletter/components/issue-scheduler.tsx +152 -0
  237. package/blocks/newsletter/components/newsletter-editor.tsx +131 -0
  238. package/blocks/newsletter/components/newsletter-stats.tsx +93 -0
  239. package/blocks/newsletter/components/subscriber-table.tsx +173 -0
  240. package/blocks/newsletter/lib/newsletter-types.ts +97 -0
  241. package/blocks/newsletter/recipes/create-newsletter-and-draft-issue.ts +27 -0
  242. package/blocks/newsletter/recipes/import-audience-and-send.ts +43 -0
  243. package/blocks/newsletter/recipes/issue-performance-report.ts +26 -0
  244. package/blocks/nurture/README.md +110 -0
  245. package/blocks/nurture/block.json +191 -0
  246. package/blocks/nurture/components/nurture-builder.tsx +114 -0
  247. package/blocks/nurture/components/nurture-enroll.tsx +107 -0
  248. package/blocks/nurture/components/nurture-form.tsx +120 -0
  249. package/blocks/nurture/components/nurture-stats.tsx +81 -0
  250. package/blocks/nurture/lib/nurture-types.ts +109 -0
  251. package/blocks/nurture/recipes/create-a-nurture.ts +34 -0
  252. package/blocks/nurture/recipes/enroll-contacts-and-launch.ts +24 -0
  253. package/blocks/nurture/recipes/nurture-health-check.ts +28 -0
  254. package/blocks/objects/README.md +120 -0
  255. package/blocks/objects/block.json +243 -0
  256. package/blocks/objects/components/attribute-field-input.tsx +82 -0
  257. package/blocks/objects/components/attribute-form-dialog.tsx +159 -0
  258. package/blocks/objects/components/object-not-found.tsx +34 -0
  259. package/blocks/objects/components/object-record-detail.tsx +143 -0
  260. package/blocks/objects/components/object-schema-editor.tsx +197 -0
  261. package/blocks/objects/components/object-table.tsx +130 -0
  262. package/blocks/objects/components/record-form-dialog.tsx +112 -0
  263. package/blocks/objects/components/record-roles-panel.tsx +106 -0
  264. package/blocks/objects/hooks/use-object-schema.ts +36 -0
  265. package/blocks/objects/lib/object-types.ts +214 -0
  266. package/blocks/objects/recipes/define-object-and-add-records.ts +24 -0
  267. package/blocks/objects/recipes/update-audit-and-archive-records.ts +39 -0
  268. package/blocks/objects/recipes/upsert-records-from-your-system.ts +30 -0
  269. package/blocks/pages/README.md +104 -0
  270. package/blocks/pages/block.json +223 -0
  271. package/blocks/pages/components/brand-kit-editor.tsx +152 -0
  272. package/blocks/pages/components/content-library.tsx +163 -0
  273. package/blocks/pages/components/page-editor.tsx +253 -0
  274. package/blocks/pages/components/page-list.tsx +159 -0
  275. package/blocks/pages/components/seo-report.tsx +159 -0
  276. package/blocks/pages/lib/studio.ts +77 -0
  277. package/blocks/pages/recipes/audit-site-seo.ts +28 -0
  278. package/blocks/pages/recipes/create-and-publish-page.ts +26 -0
  279. package/blocks/pages/recipes/edit-page-section.ts +25 -0
  280. package/blocks/pages/recipes/refresh-brand-kit.ts +22 -0
  281. package/blocks/quotes/README.md +79 -0
  282. package/blocks/quotes/block.json +183 -0
  283. package/blocks/quotes/components/product-picker.tsx +80 -0
  284. package/blocks/quotes/components/quote-builder.tsx +195 -0
  285. package/blocks/quotes/components/quote-list.tsx +79 -0
  286. package/blocks/quotes/components/quote-viewer.tsx +135 -0
  287. package/blocks/quotes/components/signature-status.tsx +77 -0
  288. package/blocks/quotes/hooks/use-debounced.ts +13 -0
  289. package/blocks/quotes/lib/quotes.ts +142 -0
  290. package/blocks/quotes/recipes/create-and-send-quote.ts +44 -0
  291. package/blocks/quotes/recipes/revise-sent-quote.ts +18 -0
  292. package/blocks/quotes/recipes/track-quote-signature.ts +22 -0
  293. package/blocks/radar/README.md +106 -0
  294. package/blocks/radar/block.json +189 -0
  295. package/blocks/radar/components/competitor-battle-card.tsx +53 -0
  296. package/blocks/radar/components/competitor-tracker.tsx +158 -0
  297. package/blocks/radar/components/radar-overview.tsx +67 -0
  298. package/blocks/radar/components/radar-report.tsx +86 -0
  299. package/blocks/radar/hooks/use-competitors.ts +23 -0
  300. package/blocks/radar/lib/radar-types.ts +124 -0
  301. package/blocks/radar/recipes/battle-card.ts +28 -0
  302. package/blocks/radar/recipes/deep-dive-report.ts +32 -0
  303. package/blocks/radar/recipes/marketing-snapshot.ts +18 -0
  304. package/blocks/radar/recipes/track-and-scan.ts +39 -0
  305. package/blocks/search/README.md +129 -0
  306. package/blocks/search/block.json +185 -0
  307. package/blocks/search/components/filter-builder.tsx +149 -0
  308. package/blocks/search/components/prospect-results.tsx +175 -0
  309. package/blocks/search/components/save-to-list-dialog.tsx +135 -0
  310. package/blocks/search/components/saved-searches.tsx +95 -0
  311. package/blocks/search/hooks/use-filter-options.ts +41 -0
  312. package/blocks/search/lib/search-types.ts +149 -0
  313. package/blocks/search/recipes/explore-filter-values.ts +27 -0
  314. package/blocks/search/recipes/saved-searches.ts +34 -0
  315. package/blocks/search/recipes/search-and-save-to-list.ts +37 -0
  316. package/blocks/sequencer/README.md +87 -0
  317. package/blocks/sequencer/block.json +214 -0
  318. package/blocks/sequencer/components/enroll-dialog.tsx +93 -0
  319. package/blocks/sequencer/components/sequence-builder.tsx +306 -0
  320. package/blocks/sequencer/components/sequence-list.tsx +200 -0
  321. package/blocks/sequencer/components/sequence-stats.tsx +256 -0
  322. package/blocks/sequencer/components/step-editor.tsx +213 -0
  323. package/blocks/sequencer/lib/sequencer-utils.ts +168 -0
  324. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +51 -0
  325. package/blocks/sequencer/recipes/launch-and-enroll.ts +44 -0
  326. package/blocks/sequencer/recipes/pause-when-unsafe.ts +30 -0
  327. package/blocks/sequencer/recipes/sequence-report.ts +22 -0
  328. package/blocks/social-ads/README.md +128 -0
  329. package/blocks/social-ads/block.json +256 -0
  330. package/blocks/social-ads/components/ad-campaign-list.tsx +136 -0
  331. package/blocks/social-ads/components/audience-sync.tsx +150 -0
  332. package/blocks/social-ads/components/creative-gallery.tsx +121 -0
  333. package/blocks/social-ads/components/post-composer.tsx +122 -0
  334. package/blocks/social-ads/components/social-post-list.tsx +116 -0
  335. package/blocks/social-ads/hooks/use-social-accounts.ts +16 -0
  336. package/blocks/social-ads/lib/social-ads-types.ts +167 -0
  337. package/blocks/social-ads/recipes/creative-to-gallery.ts +41 -0
  338. package/blocks/social-ads/recipes/schedule-post.ts +51 -0
  339. package/blocks/social-ads/recipes/sync-audience-and-launch.ts +41 -0
  340. package/blocks/visitors/README.md +56 -0
  341. package/blocks/visitors/block.json +139 -0
  342. package/blocks/visitors/components/snippet-install.tsx +169 -0
  343. package/blocks/visitors/components/traffic-chart.tsx +156 -0
  344. package/blocks/visitors/components/visitor-company-card.tsx +92 -0
  345. package/blocks/visitors/components/visitor-feed.tsx +157 -0
  346. package/blocks/visitors/lib/visitors-utils.ts +78 -0
  347. package/blocks/visitors/recipes/check-snippet-installed.ts +24 -0
  348. package/blocks/visitors/recipes/recent-company-visits.ts +27 -0
  349. package/blocks/visitors/recipes/traffic-summary.ts +24 -0
  350. package/blocks/webchat/README.md +87 -0
  351. package/blocks/webchat/block.json +134 -0
  352. package/blocks/webchat/components/chat-widget-config.tsx +160 -0
  353. package/blocks/webchat/components/conversation-list.tsx +74 -0
  354. package/blocks/webchat/lib/webchat-types.ts +83 -0
  355. package/blocks/webchat/recipes/create-bot-and-style-widget.ts +24 -0
  356. package/blocks/webchat/recipes/list-chat-visitors.ts +24 -0
  357. package/blocks/workbench/README.md +135 -0
  358. package/blocks/workbench/block.json +259 -0
  359. package/blocks/workbench/components/column-recipe-editor.tsx +183 -0
  360. package/blocks/workbench/components/run-progress.tsx +132 -0
  361. package/blocks/workbench/components/workbench-add-rows.tsx +100 -0
  362. package/blocks/workbench/components/workbench-grid.tsx +159 -0
  363. package/blocks/workbench/components/workbench-row-actions.tsx +102 -0
  364. package/blocks/workbench/components/workbench-table-list.tsx +105 -0
  365. package/blocks/workbench/hooks/use-workbench-table.ts +30 -0
  366. package/blocks/workbench/lib/waterfall-presets.ts +119 -0
  367. package/blocks/workbench/lib/workbench-types.ts +191 -0
  368. package/blocks/workbench/recipes/build-enriched-table.ts +65 -0
  369. package/blocks/workbench/recipes/import-from-search.ts +42 -0
  370. package/blocks/workbench/recipes/scrape-into-table.ts +36 -0
  371. package/blocks/workbench/recipes/waterfall-column.ts +55 -0
  372. package/blocks/workflows/README.md +100 -0
  373. package/blocks/workflows/block.json +188 -0
  374. package/blocks/workflows/components/run-history.tsx +136 -0
  375. package/blocks/workflows/components/trigger-config.tsx +134 -0
  376. package/blocks/workflows/components/workflow-editor.tsx +142 -0
  377. package/blocks/workflows/components/workflow-list.tsx +153 -0
  378. package/blocks/workflows/hooks/use-workflow.ts +51 -0
  379. package/blocks/workflows/lib/graph.ts +187 -0
  380. package/blocks/workflows/recipes/create-workflow.ts +35 -0
  381. package/blocks/workflows/recipes/find-failed-runs.ts +15 -0
  382. package/blocks/workflows/recipes/run-workflow-and-wait.ts +32 -0
  383. package/blocks/workflows/recipes/set-trigger.ts +22 -0
  384. package/dist/cli.js +5330 -29
  385. package/dist/index.browser.js +7740 -0
  386. package/dist/index.browser.js.map +1 -0
  387. package/dist/index.browser.mjs +7717 -0
  388. package/dist/index.browser.mjs.map +1 -0
  389. package/dist/index.d.mts +1440 -21
  390. package/dist/index.d.ts +1440 -21
  391. package/dist/index.js +42 -3
  392. package/dist/index.js.map +1 -1
  393. package/dist/index.mjs +42 -3
  394. package/dist/index.mjs.map +1 -1
  395. package/dist/react.browser.js +7598 -0
  396. package/dist/react.browser.js.map +1 -0
  397. package/dist/react.browser.mjs +7568 -0
  398. package/dist/react.browser.mjs.map +1 -0
  399. package/dist/react.d.mts +1340 -18
  400. package/dist/react.d.ts +1340 -18
  401. package/dist/react.js +41 -2
  402. package/dist/react.js.map +1 -1
  403. package/dist/react.mjs +41 -2
  404. package/dist/react.mjs.map +1 -1
  405. package/dist/types/agentSetup.d.ts +6 -6
  406. package/dist/types/blocks/add.d.ts +53 -0
  407. package/dist/types/blocks/create.d.ts +46 -0
  408. package/dist/types/blocks/doctor.d.ts +26 -0
  409. package/dist/types/blocks/registry.d.ts +134 -0
  410. package/dist/types/cli.d.ts +16 -0
  411. package/dist/types/core.d.ts +1 -1
  412. package/dist/types/crypto-browser.d.ts +2 -0
  413. package/dist/types/crypto-node.d.ts +6 -0
  414. package/dist/types/react.d.ts +1 -1
  415. package/dist/types/version.d.ts +2 -2
  416. package/dist/types/webhooks.d.ts +2 -2
  417. package/dist/ui/index.d.mts +54 -0
  418. package/dist/ui/index.d.ts +54 -0
  419. package/dist/ui/index.js +105 -0
  420. package/dist/ui/index.js.map +1 -0
  421. package/dist/ui/index.mjs +44 -0
  422. package/dist/ui/index.mjs.map +1 -0
  423. package/dist/ui/lib/compose-refs.d.mts +19 -0
  424. package/dist/ui/lib/compose-refs.d.ts +19 -0
  425. package/dist/ui/lib/compose-refs.js +76 -0
  426. package/dist/ui/lib/compose-refs.js.map +1 -0
  427. package/dist/ui/lib/compose-refs.mjs +41 -0
  428. package/dist/ui/lib/compose-refs.mjs.map +1 -0
  429. package/dist/ui/lib/use-mobile.d.mts +4 -0
  430. package/dist/ui/lib/use-mobile.d.ts +4 -0
  431. package/dist/ui/lib/use-mobile.js +57 -0
  432. package/dist/ui/lib/use-mobile.js.map +1 -0
  433. package/dist/ui/lib/use-mobile.mjs +22 -0
  434. package/dist/ui/lib/use-mobile.mjs.map +1 -0
  435. package/dist/ui/lib/utils.d.mts +10 -0
  436. package/dist/ui/lib/utils.d.ts +10 -0
  437. package/dist/ui/lib/utils.js +33 -0
  438. package/dist/ui/lib/utils.js.map +1 -0
  439. package/dist/ui/lib/utils.mjs +9 -0
  440. package/dist/ui/lib/utils.mjs.map +1 -0
  441. package/dist/ui/primitives/alert-dialog.d.mts +22 -0
  442. package/dist/ui/primitives/alert-dialog.d.ts +22 -0
  443. package/dist/ui/primitives/alert-dialog.js +163 -0
  444. package/dist/ui/primitives/alert-dialog.js.map +1 -0
  445. package/dist/ui/primitives/alert-dialog.mjs +119 -0
  446. package/dist/ui/primitives/alert-dialog.mjs.map +1 -0
  447. package/dist/ui/primitives/avatar.d.mts +8 -0
  448. package/dist/ui/primitives/avatar.d.ts +8 -0
  449. package/dist/ui/primitives/avatar.js +91 -0
  450. package/dist/ui/primitives/avatar.js.map +1 -0
  451. package/dist/ui/primitives/avatar.mjs +55 -0
  452. package/dist/ui/primitives/avatar.mjs.map +1 -0
  453. package/dist/ui/primitives/breadcrumb.d.mts +13 -0
  454. package/dist/ui/primitives/breadcrumb.d.ts +13 -0
  455. package/dist/ui/primitives/breadcrumb.js +127 -0
  456. package/dist/ui/primitives/breadcrumb.js.map +1 -0
  457. package/dist/ui/primitives/breadcrumb.mjs +97 -0
  458. package/dist/ui/primitives/breadcrumb.mjs.map +1 -0
  459. package/dist/ui/primitives/button.d.mts +15 -0
  460. package/dist/ui/primitives/button.d.ts +15 -0
  461. package/dist/ui/primitives/button.js +91 -0
  462. package/dist/ui/primitives/button.js.map +1 -0
  463. package/dist/ui/primitives/button.mjs +56 -0
  464. package/dist/ui/primitives/button.mjs.map +1 -0
  465. package/dist/ui/primitives/card.d.mts +10 -0
  466. package/dist/ui/primitives/card.d.ts +10 -0
  467. package/dist/ui/primitives/card.js +101 -0
  468. package/dist/ui/primitives/card.js.map +1 -0
  469. package/dist/ui/primitives/card.mjs +62 -0
  470. package/dist/ui/primitives/card.mjs.map +1 -0
  471. package/dist/ui/primitives/carousel.d.mts +45 -0
  472. package/dist/ui/primitives/carousel.d.ts +45 -0
  473. package/dist/ui/primitives/carousel.js +300 -0
  474. package/dist/ui/primitives/carousel.js.map +1 -0
  475. package/dist/ui/primitives/carousel.mjs +261 -0
  476. package/dist/ui/primitives/carousel.mjs.map +1 -0
  477. package/dist/ui/primitives/chart.d.mts +68 -0
  478. package/dist/ui/primitives/chart.d.ts +68 -0
  479. package/dist/ui/primitives/chart.js +280 -0
  480. package/dist/ui/primitives/chart.js.map +1 -0
  481. package/dist/ui/primitives/chart.mjs +241 -0
  482. package/dist/ui/primitives/chart.mjs.map +1 -0
  483. package/dist/ui/primitives/collapsible.d.mts +8 -0
  484. package/dist/ui/primitives/collapsible.d.ts +8 -0
  485. package/dist/ui/primitives/collapsible.js +72 -0
  486. package/dist/ui/primitives/collapsible.js.map +1 -0
  487. package/dist/ui/primitives/collapsible.mjs +36 -0
  488. package/dist/ui/primitives/collapsible.mjs.map +1 -0
  489. package/dist/ui/primitives/context-menu.d.mts +29 -0
  490. package/dist/ui/primitives/context-menu.d.ts +29 -0
  491. package/dist/ui/primitives/context-menu.js +203 -0
  492. package/dist/ui/primitives/context-menu.js.map +1 -0
  493. package/dist/ui/primitives/context-menu.mjs +155 -0
  494. package/dist/ui/primitives/context-menu.mjs.map +1 -0
  495. package/dist/ui/primitives/dialog.d.mts +26 -0
  496. package/dist/ui/primitives/dialog.d.ts +26 -0
  497. package/dist/ui/primitives/dialog.js +192 -0
  498. package/dist/ui/primitives/dialog.js.map +1 -0
  499. package/dist/ui/primitives/dialog.mjs +148 -0
  500. package/dist/ui/primitives/dialog.mjs.map +1 -0
  501. package/dist/ui/primitives/drawer.d.mts +26 -0
  502. package/dist/ui/primitives/drawer.d.ts +26 -0
  503. package/dist/ui/primitives/drawer.js +146 -0
  504. package/dist/ui/primitives/drawer.js.map +1 -0
  505. package/dist/ui/primitives/drawer.mjs +103 -0
  506. package/dist/ui/primitives/drawer.mjs.map +1 -0
  507. package/dist/ui/primitives/input.d.mts +5 -0
  508. package/dist/ui/primitives/input.d.ts +5 -0
  509. package/dist/ui/primitives/input.js +46 -0
  510. package/dist/ui/primitives/input.js.map +1 -0
  511. package/dist/ui/primitives/input.mjs +22 -0
  512. package/dist/ui/primitives/input.mjs.map +1 -0
  513. package/dist/ui/primitives/item.d.mts +27 -0
  514. package/dist/ui/primitives/item.d.ts +27 -0
  515. package/dist/ui/primitives/item.js +221 -0
  516. package/dist/ui/primitives/item.js.map +1 -0
  517. package/dist/ui/primitives/item.mjs +188 -0
  518. package/dist/ui/primitives/item.mjs.map +1 -0
  519. package/dist/ui/primitives/kbd.d.mts +15 -0
  520. package/dist/ui/primitives/kbd.d.ts +15 -0
  521. package/dist/ui/primitives/kbd.js +45 -0
  522. package/dist/ui/primitives/kbd.js.map +1 -0
  523. package/dist/ui/primitives/kbd.mjs +21 -0
  524. package/dist/ui/primitives/kbd.mjs.map +1 -0
  525. package/dist/ui/primitives/label.d.mts +6 -0
  526. package/dist/ui/primitives/label.d.ts +6 -0
  527. package/dist/ui/primitives/label.js +55 -0
  528. package/dist/ui/primitives/label.js.map +1 -0
  529. package/dist/ui/primitives/label.mjs +21 -0
  530. package/dist/ui/primitives/label.mjs.map +1 -0
  531. package/dist/ui/primitives/resizable.d.mts +10 -0
  532. package/dist/ui/primitives/resizable.d.ts +10 -0
  533. package/dist/ui/primitives/resizable.js +68 -0
  534. package/dist/ui/primitives/resizable.js.map +1 -0
  535. package/dist/ui/primitives/resizable.mjs +42 -0
  536. package/dist/ui/primitives/resizable.mjs.map +1 -0
  537. package/dist/ui/primitives/save-status.d.mts +31 -0
  538. package/dist/ui/primitives/save-status.d.ts +31 -0
  539. package/dist/ui/primitives/save-status.js +150 -0
  540. package/dist/ui/primitives/save-status.js.map +1 -0
  541. package/dist/ui/primitives/save-status.mjs +126 -0
  542. package/dist/ui/primitives/save-status.mjs.map +1 -0
  543. package/dist/ui/primitives/separator.d.mts +6 -0
  544. package/dist/ui/primitives/separator.d.ts +6 -0
  545. package/dist/ui/primitives/separator.js +62 -0
  546. package/dist/ui/primitives/separator.js.map +1 -0
  547. package/dist/ui/primitives/separator.mjs +28 -0
  548. package/dist/ui/primitives/separator.mjs.map +1 -0
  549. package/dist/ui/primitives/sheet.d.mts +16 -0
  550. package/dist/ui/primitives/sheet.d.ts +16 -0
  551. package/dist/ui/primitives/sheet.js +170 -0
  552. package/dist/ui/primitives/sheet.js.map +1 -0
  553. package/dist/ui/primitives/sheet.mjs +129 -0
  554. package/dist/ui/primitives/sheet.mjs.map +1 -0
  555. package/dist/ui/primitives/sidebar.d.mts +74 -0
  556. package/dist/ui/primitives/sidebar.d.ts +74 -0
  557. package/dist/ui/primitives/sidebar.js +697 -0
  558. package/dist/ui/primitives/sidebar.js.map +1 -0
  559. package/dist/ui/primitives/sidebar.mjs +651 -0
  560. package/dist/ui/primitives/sidebar.mjs.map +1 -0
  561. package/dist/ui/primitives/skeleton.d.mts +5 -0
  562. package/dist/ui/primitives/skeleton.d.ts +5 -0
  563. package/dist/ui/primitives/skeleton.js +40 -0
  564. package/dist/ui/primitives/skeleton.js.map +1 -0
  565. package/dist/ui/primitives/skeleton.mjs +16 -0
  566. package/dist/ui/primitives/skeleton.mjs.map +1 -0
  567. package/dist/ui/primitives/slider.d.mts +20 -0
  568. package/dist/ui/primitives/slider.d.ts +20 -0
  569. package/dist/ui/primitives/slider.js +112 -0
  570. package/dist/ui/primitives/slider.js.map +1 -0
  571. package/dist/ui/primitives/slider.mjs +78 -0
  572. package/dist/ui/primitives/slider.mjs.map +1 -0
  573. package/dist/ui/primitives/sortable.d.mts +50 -0
  574. package/dist/ui/primitives/sortable.d.ts +50 -0
  575. package/dist/ui/primitives/sortable.js +456 -0
  576. package/dist/ui/primitives/sortable.js.map +1 -0
  577. package/dist/ui/primitives/sortable.mjs +435 -0
  578. package/dist/ui/primitives/sortable.mjs.map +1 -0
  579. package/dist/ui/primitives/switch.d.mts +6 -0
  580. package/dist/ui/primitives/switch.d.ts +6 -0
  581. package/dist/ui/primitives/switch.js +67 -0
  582. package/dist/ui/primitives/switch.js.map +1 -0
  583. package/dist/ui/primitives/switch.mjs +33 -0
  584. package/dist/ui/primitives/switch.mjs.map +1 -0
  585. package/dist/ui/primitives/table.d.mts +12 -0
  586. package/dist/ui/primitives/table.d.ts +12 -0
  587. package/dist/ui/primitives/table.js +130 -0
  588. package/dist/ui/primitives/table.js.map +1 -0
  589. package/dist/ui/primitives/table.mjs +89 -0
  590. package/dist/ui/primitives/table.mjs.map +1 -0
  591. package/dist/ui/primitives/tabs.d.mts +9 -0
  592. package/dist/ui/primitives/tabs.d.ts +9 -0
  593. package/dist/ui/primitives/tabs.js +86 -0
  594. package/dist/ui/primitives/tabs.js.map +1 -0
  595. package/dist/ui/primitives/tabs.mjs +49 -0
  596. package/dist/ui/primitives/tabs.mjs.map +1 -0
  597. package/dist/ui/primitives/textarea.d.mts +5 -0
  598. package/dist/ui/primitives/textarea.d.ts +5 -0
  599. package/dist/ui/primitives/textarea.js +43 -0
  600. package/dist/ui/primitives/textarea.js.map +1 -0
  601. package/dist/ui/primitives/textarea.mjs +19 -0
  602. package/dist/ui/primitives/textarea.mjs.map +1 -0
  603. package/dist/ui/primitives/tooltip.d.mts +9 -0
  604. package/dist/ui/primitives/tooltip.d.ts +9 -0
  605. package/dist/ui/primitives/tooltip.js +94 -0
  606. package/dist/ui/primitives/tooltip.js.map +1 -0
  607. package/dist/ui/primitives/tooltip.mjs +57 -0
  608. package/dist/ui/primitives/tooltip.mjs.map +1 -0
  609. package/docs/agents.md +72 -29
  610. package/docs/api/README.md +6 -3
  611. package/docs/api/appObjects.d.ts +1 -1
  612. package/docs/api/appObjects.md +1 -1
  613. package/docs/api/by-path.md +29 -0
  614. package/docs/api/objects.d.ts +72 -5
  615. package/docs/api/objects.md +84 -7
  616. package/docs/api/processLists.d.ts +247 -0
  617. package/docs/api/processLists.md +409 -0
  618. package/docs/api/reports.d.ts +476 -0
  619. package/docs/api/reports.md +539 -0
  620. package/docs/api/scheduleBuy.d.ts +12 -8
  621. package/docs/api/scheduleBuy.md +13 -10
  622. package/docs/api/seo.d.ts +406 -3
  623. package/docs/api/seo.md +505 -1
  624. package/docs/api/workspaceBackup.d.ts +324 -0
  625. package/docs/api/workspaceBackup.md +427 -0
  626. package/package.json +174 -5
  627. package/stacks/6sense.json +46 -0
  628. package/stacks/README.md +20 -0
  629. package/stacks/apollo.json +128 -0
  630. package/stacks/clay.json +59 -0
  631. package/stacks/gong.json +72 -0
  632. package/stacks/hubspot.json +162 -0
  633. package/stacks/intercom.json +53 -0
  634. package/stacks/mailchimp.json +71 -0
  635. package/stacks/outreach.json +86 -0
  636. package/stacks/salesforce.json +94 -0
  637. package/stacks/stack.schema.json +32 -0
  638. package/stacks/starter.json +22 -0
  639. package/styles/styles.css +2 -0
  640. package/styles/theme.css +181 -0
  641. package/styles/tokens.css +284 -0
  642. package/templates/next-app/README.md.tmpl +35 -0
  643. package/templates/next-app/_pages/redirect-page.tsx.tmpl +5 -0
  644. package/templates/next-app/_pages/route-page.tsx.tmpl +9 -0
  645. package/templates/next-app/app/globals.css.tmpl +8 -0
  646. package/templates/next-app/app/layout.tsx.tmpl +19 -0
  647. package/templates/next-app/components/app-shell.tsx.tmpl +34 -0
  648. package/templates/next-app/dot-env.local.example.tmpl +6 -0
  649. package/templates/next-app/dot-gitignore.tmpl +7 -0
  650. package/templates/next-app/lib/nav.ts.tmpl +2 -0
  651. package/templates/next-app/next.config.mjs.tmpl +4 -0
  652. package/templates/next-app/package.json.tmpl +25 -0
  653. package/templates/next-app/postcss.config.mjs.tmpl +4 -0
  654. package/templates/next-app/tsconfig.json.tmpl +21 -0
@@ -0,0 +1,185 @@
1
+ "use client";
2
+
3
+ /**
4
+ * MailboxList: the sending mailboxes in the workspace, with connection health and per-mailbox actions.
5
+ *
6
+ * SDK operations, in order:
7
+ * 1. listMailboxes GET /mailboxes (read; id, status, provider, warmup)
8
+ * 2. testMailboxConnection POST /mailboxes/{mailbox_id}/test-connection (write; checks SMTP and IMAP, no spend)
9
+ * 3. archiveMailbox POST /mailboxes/{mailbox_id}/archive (destructive: confirmed)
10
+ * 4. deleteMailbox DELETE /mailboxes/{mailbox_id} (destructive: confirmed)
11
+ *
12
+ * Tiers: 3 and 4 are destructive, so each opens ConfirmSpendDialog first. Sequences that send from a
13
+ * deleted mailbox stop being able to send. Archiving only hides the mailbox and is reversible (restore with
14
+ * `is_archived: false`); it does not cancel a purchased mailbox's billing.
15
+ */
16
+
17
+ import { useState } from "react";
18
+ import { Button, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@graph8/sdk/ui";
19
+ import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
20
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
21
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
22
+ import { ApiError } from "@/g8/components/api-error";
23
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
24
+ import { EmptyState } from "@/g8/components/empty-state";
25
+ import { listOf, objectOf, statusClass } from "../lib/mailboxes-utils";
26
+ import type { MailboxRow } from "../lib/mailboxes-utils";
27
+
28
+ export interface MailboxListProps {
29
+ includeArchived?: boolean;
30
+ /** Called when a row is opened (for example to show WarmupHealth for it). */
31
+ onSelect?: (mailbox: MailboxRow) => void;
32
+ /** Rendered when there are no mailboxes: usually a ConnectMailbox trigger. */
33
+ emptyAction?: React.ReactNode;
34
+ className?: string;
35
+ }
36
+
37
+ type Pending = { kind: "archive" | "delete"; mailbox: MailboxRow };
38
+
39
+ interface TestResult {
40
+ success?: boolean;
41
+ smtp?: { success?: boolean; error?: string };
42
+ imap?: { success?: boolean; error?: string };
43
+ }
44
+
45
+ export function MailboxList({ includeArchived = false, onSelect, emptyAction, className }: MailboxListProps) {
46
+ const [pending, setPending] = useState<Pending | null>(null);
47
+ const [tests, setTests] = useState<Record<string, string>>({});
48
+ const [testing, setTesting] = useState<ReadonlySet<string>>(new Set());
49
+ const list = useG8Query(
50
+ (signal) => g8Get("/mailboxes", { limit: 200, include_archived: includeArchived }, { signal }),
51
+ [includeArchived],
52
+ );
53
+
54
+ const test = useG8Mutation(async (m: MailboxRow) => {
55
+ setTesting((t) => new Set(t).add(m.id));
56
+ try {
57
+ const res = await g8Post(`/mailboxes/${m.id}/test-connection`, {});
58
+ const r = objectOf(res) as TestResult;
59
+ const msg = r.success
60
+ ? "SMTP and IMAP both work"
61
+ : [
62
+ r.smtp?.success === false && `SMTP: ${r.smtp.error ?? "failed"}`,
63
+ r.imap?.success === false && `IMAP: ${r.imap.error ?? "failed"}`,
64
+ ]
65
+ .filter(Boolean)
66
+ .join("; ") || "Connection failed";
67
+ setTests((t) => ({ ...t, [m.id]: msg }));
68
+ await list.refetch();
69
+ } finally {
70
+ setTesting((t) => {
71
+ const next = new Set(t);
72
+ next.delete(m.id);
73
+ return next;
74
+ });
75
+ }
76
+ });
77
+ const act = useG8Mutation(async (p: Pending) => {
78
+ if (p.kind === "archive") await g8Post(`/mailboxes/${p.mailbox.id}/archive`, { is_archived: true });
79
+ else await g8Delete(`/mailboxes/${p.mailbox.id}`);
80
+ await list.refetch();
81
+ });
82
+
83
+ const rows = listOf<MailboxRow>(list.data);
84
+ if (list.error) return <ApiError error={list.error} onRetry={() => void list.refetch()} className={className} />;
85
+ if (list.loading && !list.data) return <Skeleton className={`h-40 w-full ${className ?? ""}`} />;
86
+ if (rows.length === 0)
87
+ return (
88
+ <EmptyState
89
+ className={className}
90
+ title="No mailboxes yet"
91
+ description="Connect a mailbox you own, or buy new ones, to start sending."
92
+ action={emptyAction}
93
+ />
94
+ );
95
+
96
+ return (
97
+ <div className={`space-y-3 ${className ?? ""}`}>
98
+ {act.error ? <ApiError error={act.error} onRetry={act.reset} /> : null}
99
+ {test.error ? <ApiError error={test.error} onRetry={test.reset} /> : null}
100
+ <Table>
101
+ <TableHeader>
102
+ <TableRow>
103
+ <TableHead>Mailbox</TableHead>
104
+ <TableHead>Status</TableHead>
105
+ <TableHead>Warmup</TableHead>
106
+ <TableHead>Daily limit</TableHead>
107
+ <TableHead />
108
+ </TableRow>
109
+ </TableHeader>
110
+ <TableBody>
111
+ {rows.map((m) => (
112
+ <TableRow key={m.id}>
113
+ <TableCell>
114
+ <button type="button" className="text-left font-medium hover:underline" onClick={() => onSelect?.(m)}>
115
+ {m.email ?? `Mailbox ${m.id}`}
116
+ </button>
117
+ <div className="text-xs text-gray-500">
118
+ {m.display_name ?? ""}
119
+ {m.provider ? ` - ${m.provider}` : ""}
120
+ {m.is_archived ? " - archived" : ""}
121
+ </div>
122
+ {tests[m.id] ? (
123
+ <div role="status" className="text-xs text-gray-700">
124
+ {tests[m.id]}
125
+ </div>
126
+ ) : null}
127
+ </TableCell>
128
+ <TableCell>
129
+ <span className={`rounded px-2 py-0.5 text-xs ${statusClass(m.connection_status)}`}>
130
+ {m.connection_status ?? "unknown"}
131
+ </span>
132
+ </TableCell>
133
+ <TableCell>{m.warmup_status ?? "off"}</TableCell>
134
+ <TableCell>{m.daily_limit ?? "-"}</TableCell>
135
+ <TableCell className="space-x-1 text-right">
136
+ <Button
137
+ size="sm"
138
+ variant="outline"
139
+ disabled={testing.has(m.id)}
140
+ aria-label={`Test ${m.email}`}
141
+ onClick={() => void test.mutate(m)}
142
+ >
143
+ Test
144
+ </Button>
145
+ <Button
146
+ size="sm"
147
+ variant="ghost"
148
+ aria-label={`Archive ${m.email}`}
149
+ onClick={() => setPending({ kind: "archive", mailbox: m })}
150
+ >
151
+ Archive
152
+ </Button>
153
+ <Button
154
+ size="sm"
155
+ variant="ghost"
156
+ aria-label={`Delete ${m.email}`}
157
+ onClick={() => setPending({ kind: "delete", mailbox: m })}
158
+ >
159
+ Delete
160
+ </Button>
161
+ </TableCell>
162
+ </TableRow>
163
+ ))}
164
+ </TableBody>
165
+ </Table>
166
+ {pending ? (
167
+ <ConfirmSpendDialog
168
+ open
169
+ tier="destructive"
170
+ title={`${pending.kind === "delete" ? "Delete" : "Archive"} ${pending.mailbox.email ?? "this mailbox"}`}
171
+ description={
172
+ pending.kind === "delete"
173
+ ? "Sequences that send from it can no longer send. This cannot be undone."
174
+ : "It disappears from pickers. History is kept and you can restore it."
175
+ }
176
+ confirmLabel={pending.kind === "delete" ? "Delete mailbox" : "Archive mailbox"}
177
+ affected={1}
178
+ confirming={act.loading}
179
+ onCancel={() => setPending(null)}
180
+ onConfirm={() => void act.mutate(pending).then(() => setPending(null))}
181
+ />
182
+ ) : null}
183
+ </div>
184
+ );
185
+ }
@@ -0,0 +1,232 @@
1
+ "use client";
2
+
3
+ /**
4
+ * WarmupHealth: is it safe to send? Fleet deliverability at a glance, the mailboxes in trouble, and warmup
5
+ * analytics (with start/stop and an inbox-placement test) for one mailbox.
6
+ *
7
+ * SDK operations, in order:
8
+ * 1. getFleetSummary GET /deliverability/fleet-summary (read; counts by health, inbox rate)
9
+ * 2. getSafeToSend GET /deliverability/safe-to-send (read; one verdict and why)
10
+ * 3. listDegradedMailboxes GET /deliverability/degraded (read; worst first)
11
+ * 4. getMailboxWarmupAnalytics GET /mailboxes/{mailbox_id}/warmup (read; only when `mailboxId` is set)
12
+ * 5. enableWarmup / disableWarmup POST /warmup/{mailbox_id}/enable|disable (write; enabling starts SENDING warmup mail)
13
+ * 6. runMailboxPlacementTest POST /mailboxes/{mailbox_id}/health/placement-test
14
+ * (billable: 20 credits, confirmed first; charged when created, not when done)
15
+ *
16
+ * Tiers: only 6 spends credits, so only it opens ConfirmSpendDialog. Show this before a launch; a `false`
17
+ * verdict with a live sequence running is the signal to pause rather than push.
18
+ */
19
+
20
+ import { useState } from "react";
21
+ import { Button, Skeleton } from "@graph8/sdk/ui";
22
+ import { g8Get, g8Post } from "@/g8/lib/g8-client";
23
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
24
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
25
+ import { ApiError } from "@/g8/components/api-error";
26
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
27
+ import { listOf, objectOf, PLACEMENT_TEST_CREDITS, scalars, statusClass } from "../lib/mailboxes-utils";
28
+
29
+ export interface WarmupHealthProps {
30
+ /** Show warmup analytics and controls for this mailbox (an id from MailboxList). */
31
+ mailboxId?: string | number;
32
+ className?: string;
33
+ }
34
+
35
+ interface DegradedRow {
36
+ id?: string | number;
37
+ email?: string;
38
+ mailbox_email?: string;
39
+ severity?: string;
40
+ status?: string;
41
+ reason?: string;
42
+ reasons?: string[];
43
+ }
44
+
45
+ interface WarmupAnalytics {
46
+ warmup_enabled?: boolean | null;
47
+ provider?: string | null;
48
+ error?: string;
49
+ analytics?: {
50
+ status?: string;
51
+ total_sent?: number;
52
+ total_inbox?: number;
53
+ total_spam?: number;
54
+ inbox_rate?: number;
55
+ reputation?: number | null;
56
+ daily_stats?: Array<{ date: string; sent: number; inbox: number; spam: number }>;
57
+ } | null;
58
+ }
59
+
60
+ function Fleet() {
61
+ const summary = useG8Query((signal) => g8Get("/deliverability/fleet-summary", undefined, { signal }), []);
62
+ const verdict = useG8Query((signal) => g8Get("/deliverability/safe-to-send", undefined, { signal }), []);
63
+ const degraded = useG8Query((signal) => g8Get("/deliverability/degraded", { min_severity: "watch" }, { signal }), []);
64
+ if (summary.error) return <ApiError error={summary.error} onRetry={() => void summary.refetch()} />;
65
+ if (summary.loading && !summary.data) return <Skeleton className="h-24 w-full" />;
66
+
67
+ const v = objectOf(verdict.data);
68
+ // A verdict that failed or has not arrived is UNKNOWN, whatever else says: never fall back to the summary's flag.
69
+ const safe = verdict.error || verdict.loading ? undefined : (v.safe_to_send ?? v.safe ?? objectOf(summary.data).safe_to_send);
70
+ const rows = listOf<DegradedRow>(degraded.data);
71
+ return (
72
+ <div className="space-y-3">
73
+ <div
74
+ className={`rounded border p-3 ${safe === false ? "border-red-200 bg-red-50" : safe === true ? "border-green-200 bg-green-50" : "border-amber-200 bg-amber-50"}`}
75
+ role="status"
76
+ data-testid="send-verdict"
77
+ >
78
+ <p className="font-medium">
79
+ {safe === false ? "Not safe to send right now" : safe === true ? "Safe to send" : "Send status unknown"}
80
+ </p>
81
+ {verdict.error ? (
82
+ <p className="text-sm text-amber-900">
83
+ The deliverability check failed, so sending is not confirmed safe. Retry before you send.
84
+ </p>
85
+ ) : null}
86
+ {typeof v.reason === "string" ? <p className="text-sm text-gray-700">{v.reason}</p> : null}
87
+ </div>
88
+ <dl className="grid grid-cols-2 gap-2 sm:grid-cols-4">
89
+ {scalars(objectOf(summary.data))
90
+ .filter(([k]) => k !== "safe to send")
91
+ .map(([k, val]) => (
92
+ <div key={k} className="rounded border border-gray-200 p-2">
93
+ <dt className="text-xs capitalize text-gray-500">{k}</dt>
94
+ <dd className="text-lg font-semibold">{val}</dd>
95
+ </div>
96
+ ))}
97
+ </dl>
98
+ {rows.length > 0 ? (
99
+ <ul className="divide-y rounded border border-gray-200" aria-label="Degraded mailboxes">
100
+ {rows.map((r, i) => (
101
+ <li key={String(r.id ?? i)} className="flex items-center justify-between px-3 py-2 text-sm">
102
+ <span>
103
+ {r.email ?? r.mailbox_email ?? `Mailbox ${r.id}`}
104
+ <span className="ml-2 text-xs text-gray-500">{r.reason ?? r.reasons?.join(", ")}</span>
105
+ </span>
106
+ <span className={`rounded px-2 py-0.5 text-xs ${statusClass(r.severity ?? r.status)}`}>
107
+ {r.severity ?? r.status ?? "watch"}
108
+ </span>
109
+ </li>
110
+ ))}
111
+ </ul>
112
+ ) : null}
113
+ </div>
114
+ );
115
+ }
116
+
117
+ function MailboxWarmup({ mailboxId }: { mailboxId: string | number }) {
118
+ const [confirm, setConfirm] = useState(false);
119
+ const [note, setNote] = useState<string | null>(null);
120
+ const warm = useG8Query((signal) => g8Get<WarmupAnalytics>(`/mailboxes/${mailboxId}/warmup`, undefined, { signal }), [mailboxId]);
121
+ const toggle = useG8Mutation(async (enable: boolean) => {
122
+ await g8Post(`/warmup/${mailboxId}/${enable ? "enable" : "disable"}`, {});
123
+ await warm.refetch();
124
+ });
125
+ const placement = useG8Mutation(async () => {
126
+ await g8Post(`/mailboxes/${mailboxId}/health/placement-test`, {});
127
+ setNote("Placement test started. Results appear in the mailbox's health.");
128
+ });
129
+
130
+ if (warm.error) return <ApiError error={warm.error} onRetry={() => void warm.refetch()} />;
131
+ if (warm.loading && !warm.data) return <Skeleton className="h-28 w-full" />;
132
+ const w = warm.data?.data;
133
+ const a = w?.analytics;
134
+ return (
135
+ <section className="space-y-3 rounded border border-gray-200 p-3" aria-label="Warmup">
136
+ <div className="flex items-center justify-between">
137
+ <h3 className="font-medium">
138
+ Warmup {w?.warmup_enabled ? "is on" : "is off"}
139
+ {w?.provider ? ` (${w.provider})` : ""}
140
+ </h3>
141
+ <div className="flex gap-2">
142
+ <Button
143
+ size="sm"
144
+ variant="outline"
145
+ disabled={toggle.loading || Boolean(w?.warmup_enabled)}
146
+ onClick={() => void toggle.mutate(true)}
147
+ >
148
+ Start warmup
149
+ </Button>
150
+ <Button size="sm" variant="outline" disabled={toggle.loading || !w?.warmup_enabled} onClick={() => void toggle.mutate(false)}>
151
+ Stop warmup
152
+ </Button>
153
+ </div>
154
+ </div>
155
+ {w?.error ? (
156
+ <p role="alert" className="text-sm text-amber-800">
157
+ The warmup provider did not answer: {w.error}
158
+ </p>
159
+ ) : null}
160
+ {a ? (
161
+ <>
162
+ <dl className="grid grid-cols-2 gap-2 sm:grid-cols-4 text-sm">
163
+ <div>
164
+ <dt className="text-xs text-gray-500">Sent</dt>
165
+ <dd className="font-semibold">{a.total_sent ?? 0}</dd>
166
+ </div>
167
+ <div>
168
+ <dt className="text-xs text-gray-500">Inbox rate</dt>
169
+ <dd className="font-semibold">
170
+ {a.inbox_rate != null ? `${Math.round(a.inbox_rate <= 1 ? a.inbox_rate * 100 : a.inbox_rate)}%` : "-"}
171
+ </dd>
172
+ </div>
173
+ <div>
174
+ <dt className="text-xs text-gray-500">Spam</dt>
175
+ <dd className="font-semibold">{a.total_spam ?? 0}</dd>
176
+ </div>
177
+ <div>
178
+ <dt className="text-xs text-gray-500">Reputation</dt>
179
+ <dd className="font-semibold">{a.reputation ?? "-"}</dd>
180
+ </div>
181
+ </dl>
182
+ {(a.daily_stats ?? []).length > 0 ? (
183
+ <div className="flex h-12 items-end gap-0.5" aria-label="Sent per day">
184
+ {a.daily_stats!.slice(-30).map((d) => (
185
+ <span
186
+ key={d.date}
187
+ title={`${d.date}: ${d.sent} sent, ${d.inbox} inbox`}
188
+ className="w-2 rounded-t bg-blue-500"
189
+ style={{ height: `${Math.max(4, Math.min(100, d.sent * 4))}%` }}
190
+ />
191
+ ))}
192
+ </div>
193
+ ) : null}
194
+ </>
195
+ ) : (
196
+ <p className="text-sm text-gray-500">No warmup analytics yet.</p>
197
+ )}
198
+ <Button size="sm" variant="outline" onClick={() => setConfirm(true)}>
199
+ Run placement test
200
+ </Button>
201
+ {toggle.error ? <ApiError error={toggle.error} onRetry={toggle.reset} /> : null}
202
+ {placement.error ? <ApiError error={placement.error} onRetry={placement.reset} /> : null}
203
+ {note ? (
204
+ <p role="status" className="text-sm text-green-700">
205
+ {note}
206
+ </p>
207
+ ) : null}
208
+ {confirm ? (
209
+ <ConfirmSpendDialog
210
+ open
211
+ tier="billable"
212
+ title="Run an inbox-placement test"
213
+ description="Sends test emails to see whether this mailbox reaches the inbox. Credits are charged when the test is created, even if it later fails."
214
+ estimatedCredits={PLACEMENT_TEST_CREDITS}
215
+ confirmLabel="Run test"
216
+ confirming={placement.loading}
217
+ onCancel={() => setConfirm(false)}
218
+ onConfirm={() => void placement.mutate(undefined).then(() => setConfirm(false))}
219
+ />
220
+ ) : null}
221
+ </section>
222
+ );
223
+ }
224
+
225
+ export function WarmupHealth({ mailboxId, className }: WarmupHealthProps) {
226
+ return (
227
+ <div className={`space-y-4 ${className ?? ""}`}>
228
+ <Fleet />
229
+ {mailboxId != null ? <MailboxWarmup mailboxId={mailboxId} /> : null}
230
+ </div>
231
+ );
232
+ }
@@ -0,0 +1,69 @@
1
+ "use client";
2
+
3
+ /** Types and helpers shared by the mailbox components. Open-shaped payloads: every field is optional. */
4
+
5
+ /** Where the person connects a Google or Microsoft mailbox. OAuth consent can only run in the graph8 app. */
6
+ export const GRAPH8_APP_MAILBOXES_URL = "https://app.graph8.com/studio/settings?tab=purchase&view=mailboxes";
7
+
8
+ /** The API holds these when you buy (docs/developer-api-reference.md, Channel Purchase); the real rate can change. */
9
+ export const DEFAULT_DOMAIN_CREDITS = 2500;
10
+ export const DEFAULT_MAILBOX_CREDITS = 750;
11
+ export const PLACEMENT_TEST_CREDITS = 20;
12
+
13
+ export interface MailboxRow {
14
+ id: string;
15
+ email: string | null;
16
+ display_name?: string | null;
17
+ provider?: string | null;
18
+ connection_status?: string | null;
19
+ is_archived?: boolean | null;
20
+ daily_limit?: number | null;
21
+ warmup_provider?: string | null;
22
+ warmup_status?: string | null;
23
+ created_at?: string | null;
24
+ }
25
+
26
+ export interface DomainRow {
27
+ id: number;
28
+ domain: string;
29
+ source?: string;
30
+ status?: string | null;
31
+ provider_connected?: boolean;
32
+ mailbox_count?: number;
33
+ is_external?: boolean;
34
+ }
35
+
36
+ export function listOf<T>(envelope: unknown): T[] {
37
+ const data = (envelope as { data?: unknown } | null)?.data;
38
+ if (Array.isArray(data)) return data as T[];
39
+ const items = (data as { items?: unknown } | null)?.items;
40
+ return Array.isArray(items) ? (items as T[]) : [];
41
+ }
42
+
43
+ export function objectOf(envelope: unknown): Record<string, unknown> {
44
+ const data = (envelope as { data?: unknown } | null)?.data;
45
+ return data && typeof data === "object" && !Array.isArray(data) ? (data as Record<string, unknown>) : {};
46
+ }
47
+
48
+ /** Tailwind classes for a connection or health state. */
49
+ export function statusClass(status: string | null | undefined): string {
50
+ const s = (status ?? "").toLowerCase();
51
+ if (["active", "connected", "healthy", "completed", "receiving"].includes(s)) return "bg-green-100 text-green-800";
52
+ if (["paused", "watch", "warming", "pending", "connecting", "provisioning"].includes(s)) return "bg-amber-100 text-amber-800";
53
+ if (s) return "bg-red-100 text-red-800";
54
+ return "bg-gray-100 text-gray-700";
55
+ }
56
+
57
+ /** Scalar fields of an open-shaped object, for a summary grid. */
58
+ export function scalars(obj: Record<string, unknown>): Array<[string, string]> {
59
+ return Object.entries(obj)
60
+ .filter(([, v]) => ["string", "number", "boolean"].includes(typeof v))
61
+ .map(([k, v]) => [k.replace(/_/g, " "), typeof v === "number" && !Number.isInteger(v) ? v.toFixed(2) : String(v)]);
62
+ }
63
+
64
+ /** A fresh idempotency key for one confirmed purchase (reuse it if the same click is retried). */
65
+ export function newIdempotencyKey(): string {
66
+ return typeof crypto !== "undefined" && "randomUUID" in crypto
67
+ ? crypto.randomUUID()
68
+ : `g8-${Date.now()}-${Math.random().toString(36).slice(2)}`;
69
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @title Buy a sending domain, connect it, then buy mailboxes on it
3
+ * @when Provisioning sending infrastructure from scratch. THIS SPENDS REAL CREDITS and has no per-key cap
4
+ * (domains hold about 2,500 credits each, mailboxes about 750): get a human to approve the exact names and the
5
+ * estimate first. Both purchases use an Idempotency-Key DERIVED from the domain and usernames, so re-running this function after a
6
+ * failure (in polling, or in the connect step) finds the domain already owned and carries on from there, and the purchases use the same keys so nothing is bought twice; pass `attempt` only when you
7
+ * truly want a fresh purchase. 402 = hold could not
8
+ * clear, 422 = plan cannot fund purchases. Provisioning continues in the background: poll the domain list.
9
+ */
10
+ import { createHash } from "node:crypto";
11
+ import { g8 } from "@graph8/sdk";
12
+
13
+ g8.init({ apiKey: process.env.G8_API_KEY! });
14
+
15
+ const key = (...parts: string[]): string => createHash("sha256").update(parts.join("|")).digest("hex").slice(0, 40);
16
+
17
+ export async function buyDomainAndMailbox(
18
+ domain: string,
19
+ usernames: string[],
20
+ registrant: { first_name: string; last_name: string; email: string },
21
+ humanApproved: boolean,
22
+ attempt = "1",
23
+ ) {
24
+ if (!humanApproved) throw new Error("Refusing to spend credits without approval");
25
+
26
+ const findDomain = async (): Promise<number | undefined> => {
27
+ const list = await g8.api.providerDomains.listProviderDomains({ query: { limit: 100 } });
28
+ return (list.data ?? []).find((d) => d.domain === domain)?.id;
29
+ };
30
+
31
+ // A re-run after a partial failure finds the domain already owned: skip the availability check (it is no
32
+ // longer available, because WE own it) and the domain purchase, and continue with connect and mailboxes.
33
+ let domainId = await findDomain();
34
+ if (domainId == null) {
35
+ const check = await g8.api.providerDomains.checkDomainAvailability({ body: { domain, provider: "registrar" } });
36
+ if (!(check.data as { available?: boolean } | undefined)?.available) throw new Error(`${domain} is not available`);
37
+
38
+ await g8.api.providerDomains.checkoutPurchaseDomains(
39
+ { body: { domains: [domain], period: 1, contact_info: registrant, provider: "registrar" } },
40
+ { idempotencyKey: key("domain", domain, attempt) },
41
+ );
42
+
43
+ // The purchase is asynchronous: wait for the domain to appear.
44
+ for (let i = 0; i < 20 && domainId == null; i++) {
45
+ await new Promise((resolve) => setTimeout(resolve, i === 0 ? 0 : 15000));
46
+ domainId = await findDomain();
47
+ }
48
+ }
49
+ if (domainId == null) throw new Error(`${domain} has not appeared yet; check again later`);
50
+
51
+ await g8.api.providerDomains.connectProvider({ path: { domain_id: domainId } });
52
+ return g8.api.providerDomains.checkoutPurchaseMailboxes(
53
+ {
54
+ path: { domain_id: domainId },
55
+ body: {
56
+ mailboxes: usernames.map((username) => ({ username, first_name: registrant.first_name, last_name: registrant.last_name })),
57
+ enable_warmup: true,
58
+ provider: "managed",
59
+ platform: "GOOGLE",
60
+ },
61
+ },
62
+ { idempotencyKey: key("mailboxes", domain, [...usernames].sort().join(","), attempt) },
63
+ );
64
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @title Connect a mailbox you own over IMAP/SMTP and prove it works
3
+ * @when Adding an existing mailbox. Credentials are write-only: stored encrypted, never returned. Google and
4
+ * Microsoft mailboxes CANNOT be connected here (OAuth consent only runs in the graph8 app). Tier `write`:
5
+ * no spend, no email sent. Test right after creating; a failed test leaves the mailbox saved but unusable.
6
+ */
7
+ import { g8 } from "@graph8/sdk";
8
+
9
+ g8.init({ apiKey: process.env.G8_API_KEY! });
10
+
11
+ export interface ImapMailbox {
12
+ email: string;
13
+ smtpHost: string;
14
+ smtpPassword: string;
15
+ imapHost: string;
16
+ imapPassword: string;
17
+ }
18
+
19
+ export async function connectImapMailbox(m: ImapMailbox) {
20
+ const created = await g8.api.mailboxes.createMailbox({
21
+ body: {
22
+ email: m.email,
23
+ smtp_address: m.smtpHost,
24
+ smtp_username: m.email,
25
+ smtp_password: m.smtpPassword,
26
+ imap_address: m.imapHost,
27
+ imap_username: m.email,
28
+ imap_password: m.imapPassword,
29
+ daily_limit: 40,
30
+ },
31
+ });
32
+ const id = (created.data as { id?: number } | undefined)?.id;
33
+ if (id == null) throw new Error("graph8 did not return a mailbox id");
34
+
35
+ const test = await g8.api.mailboxes.testMailboxConnection({ path: { mailbox_id: id }, body: {} });
36
+ const ok = Boolean((test.data as { success?: boolean } | undefined)?.success);
37
+ return { mailboxId: id, connected: ok };
38
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @title Check deliverability before a send
3
+ * @when Right before launching a sequence or a campaign. Reads only (no spend). A `false` verdict means do
4
+ * NOT enroll more contacts: list the degraded mailboxes, swap or pause them, and re-check later.
5
+ */
6
+ import { g8 } from "@graph8/sdk";
7
+
8
+ g8.init({ apiKey: process.env.G8_API_KEY! });
9
+
10
+ export async function safeToSend() {
11
+ const verdict = await g8.api.deliverability.getSafeToSend();
12
+ const v = (verdict.data ?? {}) as { safe_to_send?: boolean; reason?: string };
13
+ if (v.safe_to_send !== false) return { safe: true as const };
14
+
15
+ const degraded = await g8.api.deliverability.listDegradedMailboxes({ query: { min_severity: "watch" } });
16
+ return { safe: false as const, reason: v.reason, degraded: degraded.data };
17
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @title Report warmup progress for every mailbox
3
+ * @when A daily report or before moving a mailbox into a sequence. Reads only. `warmup_enabled` and the live
4
+ * `analytics` come from the warmup provider, so a failed provider call comes back as `error`, not a throw.
5
+ */
6
+ import { g8 } from "@graph8/sdk";
7
+
8
+ g8.init({ apiKey: process.env.G8_API_KEY! });
9
+
10
+ interface Warmup {
11
+ warmup_enabled?: boolean | null;
12
+ error?: string;
13
+ analytics?: { inbox_rate?: number; total_sent?: number; status?: string } | null;
14
+ }
15
+
16
+ export async function warmupReport() {
17
+ const mailboxes = await g8.api.gtmContext.listMailboxes({ query: { limit: 100 } });
18
+ const rows: Array<{ email: unknown; warmup: boolean; inboxRate?: number; sent?: number; error?: string }> = [];
19
+ for (const m of (mailboxes.data ?? []) as Array<{ id: string; email: string }>) {
20
+ const res = await g8.api.gtmContext.getMailboxWarmupAnalytics({ path: { mailbox_id: Number(m.id) } });
21
+ const w = (res.data ?? {}) as Warmup;
22
+ rows.push({
23
+ email: m.email,
24
+ warmup: Boolean(w.warmup_enabled),
25
+ inboxRate: w.analytics?.inbox_rate,
26
+ sent: w.analytics?.total_sent,
27
+ error: w.error,
28
+ });
29
+ }
30
+ return rows;
31
+ }