@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.
- package/README.md +81 -0
- package/blocks/README.md +32 -0
- package/blocks/_foundation/README.md +78 -0
- package/blocks/_foundation/block.json +79 -0
- package/blocks/_foundation/components/api-error.tsx +62 -0
- package/blocks/_foundation/components/confirm-spend-dialog.tsx +142 -0
- package/blocks/_foundation/components/empty-state.tsx +24 -0
- package/blocks/_foundation/hooks/use-g8-mutation.ts +88 -0
- package/blocks/_foundation/hooks/use-g8-query.ts +87 -0
- package/blocks/_foundation/hooks/use-job-poll.ts +130 -0
- package/blocks/_foundation/hooks/use-paginated.ts +105 -0
- package/blocks/_foundation/lib/g8-client.ts +128 -0
- package/blocks/_foundation/recipes/verify-proxy-key.ts +18 -0
- package/blocks/_foundation/server/proxy.ts +233 -0
- package/blocks/_foundation/server/route.ts +30 -0
- package/blocks/agents/README.md +133 -0
- package/blocks/agents/block.json +223 -0
- package/blocks/agents/components/agent-builder.tsx +199 -0
- package/blocks/agents/components/agent-knowledge-panel.tsx +144 -0
- package/blocks/agents/components/agent-runs.tsx +175 -0
- package/blocks/agents/components/agent-skills-panel.tsx +126 -0
- package/blocks/agents/components/agent-test-console.tsx +175 -0
- package/blocks/agents/lib/agent-types.ts +114 -0
- package/blocks/agents/recipes/create-agent-and-attach-skill.ts +50 -0
- package/blocks/agents/recipes/run-and-follow-agent-run.ts +46 -0
- package/blocks/agents/recipes/test-agent-before-activating.ts +36 -0
- package/blocks/analytics/README.md +73 -0
- package/blocks/analytics/block.json +178 -0
- package/blocks/analytics/components/funnel-chart.tsx +79 -0
- package/blocks/analytics/components/kpi-tiles.tsx +74 -0
- package/blocks/analytics/components/leaderboard.tsx +86 -0
- package/blocks/analytics/components/report-builder.tsx +200 -0
- package/blocks/analytics/hooks/use-report.ts +26 -0
- package/blocks/analytics/lib/analytics-types.ts +108 -0
- package/blocks/analytics/lib/use-debounced.ts +16 -0
- package/blocks/analytics/recipes/01-weekly-outbound-report.ts +32 -0
- package/blocks/analytics/recipes/02-revenue-snapshot.ts +22 -0
- package/blocks/analytics/recipes/03-saved-report.ts +30 -0
- package/blocks/appointments/README.md +80 -0
- package/blocks/appointments/block.json +218 -0
- package/blocks/appointments/components/availability-editor.tsx +192 -0
- package/blocks/appointments/components/booking-page.tsx +154 -0
- package/blocks/appointments/components/event-type-editor.tsx +214 -0
- package/blocks/appointments/components/meeting-detail.tsx +147 -0
- package/blocks/appointments/components/meeting-list.tsx +148 -0
- package/blocks/appointments/hooks/use-available-slots.ts +29 -0
- package/blocks/appointments/hooks/use-schedules.ts +18 -0
- package/blocks/appointments/lib/appointments-types.ts +159 -0
- package/blocks/appointments/lib/idempotency.ts +30 -0
- package/blocks/appointments/recipes/01-create-event-type-and-hours.ts +27 -0
- package/blocks/appointments/recipes/02-find-slots-and-book.ts +34 -0
- package/blocks/appointments/recipes/03-bookings-digest.ts +22 -0
- package/blocks/appointments/recipes/04-calendar-invite.ts +21 -0
- package/blocks/block.schema.json +130 -0
- package/blocks/block.types.ts +109 -0
- package/blocks/campaigns/README.md +118 -0
- package/blocks/campaigns/block.json +361 -0
- package/blocks/campaigns/components/campaign-assets.tsx +180 -0
- package/blocks/campaigns/components/campaign-audience-step.tsx +91 -0
- package/blocks/campaigns/components/campaign-launch-step.tsx +147 -0
- package/blocks/campaigns/components/campaign-list.tsx +120 -0
- package/blocks/campaigns/components/campaign-metrics.tsx +93 -0
- package/blocks/campaigns/components/campaign-select.tsx +52 -0
- package/blocks/campaigns/components/campaign-sequence-step.tsx +158 -0
- package/blocks/campaigns/components/campaign-wizard.tsx +238 -0
- package/blocks/campaigns/components/form-field.tsx +29 -0
- package/blocks/campaigns/components/icp-editor.tsx +230 -0
- package/blocks/campaigns/components/persona-editor.tsx +210 -0
- package/blocks/campaigns/hooks/use-company-profile.ts +34 -0
- package/blocks/campaigns/lib/campaigns-utils.ts +155 -0
- package/blocks/campaigns/recipes/attach-audience-and-launch.ts +33 -0
- package/blocks/campaigns/recipes/campaign-report.ts +22 -0
- package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +51 -0
- package/blocks/campaigns/recipes/set-up-icp-and-personas.ts +37 -0
- package/blocks/coach/README.md +93 -0
- package/blocks/coach/block.json +125 -0
- package/blocks/coach/components/call-scorecard.tsx +126 -0
- package/blocks/coach/components/coaching-feed.tsx +140 -0
- package/blocks/coach/lib/coach-types.ts +116 -0
- package/blocks/coach/recipes/read-a-scorecard.ts +27 -0
- package/blocks/coach/recipes/weekly-coaching-digest.ts +53 -0
- package/blocks/copilot/README.md +99 -0
- package/blocks/copilot/block.json +151 -0
- package/blocks/copilot/components/copilot-memory.tsx +87 -0
- package/blocks/copilot/components/copilot-panel.tsx +189 -0
- package/blocks/copilot/components/copilot-team-chat.tsx +111 -0
- package/blocks/copilot/lib/copilot-types.ts +111 -0
- package/blocks/copilot/recipes/ask-the-copilot.ts +35 -0
- package/blocks/copilot/recipes/post-in-a-work-channel.ts +33 -0
- package/blocks/copilot/recipes/remember-and-recall.ts +19 -0
- package/blocks/crm/README.md +122 -0
- package/blocks/crm/block.json +378 -0
- package/blocks/crm/components/activity-timeline.tsx +99 -0
- package/blocks/crm/components/company-detail.tsx +118 -0
- package/blocks/crm/components/company-table.tsx +173 -0
- package/blocks/crm/components/contact-detail.tsx +135 -0
- package/blocks/crm/components/contact-table.tsx +217 -0
- package/blocks/crm/components/dedupe-review.tsx +164 -0
- package/blocks/crm/components/import-wizard.tsx +193 -0
- package/blocks/crm/components/notes-panel.tsx +112 -0
- package/blocks/crm/components/record-form.tsx +191 -0
- package/blocks/crm/components/task-list.tsx +107 -0
- package/blocks/crm/hooks/use-custom-fields.ts +18 -0
- package/blocks/crm/hooks/use-debounced.ts +13 -0
- package/blocks/crm/hooks/use-page-query.ts +52 -0
- package/blocks/crm/lib/crm-types.ts +136 -0
- package/blocks/crm/recipes/custom-fields-and-values.ts +28 -0
- package/blocks/crm/recipes/dedupe-and-merge.ts +30 -0
- package/blocks/crm/recipes/notes-tasks-and-activity.ts +23 -0
- package/blocks/crm/recipes/upsert-contact-with-company.ts +33 -0
- package/blocks/deals/README.md +78 -0
- package/blocks/deals/block.json +198 -0
- package/blocks/deals/components/deal-detail.tsx +134 -0
- package/blocks/deals/components/forecast-view.tsx +112 -0
- package/blocks/deals/components/pipeline-board.tsx +142 -0
- package/blocks/deals/components/stage-editor.tsx +96 -0
- package/blocks/deals/hooks/use-pipeline-deals.ts +67 -0
- package/blocks/deals/hooks/use-pipelines.ts +19 -0
- package/blocks/deals/lib/deals-types.ts +128 -0
- package/blocks/deals/recipes/01-create-deal-and-advance.ts +32 -0
- package/blocks/deals/recipes/02-pipeline-report.ts +35 -0
- package/blocks/deals/recipes/03-stale-deals-sweep.ts +20 -0
- package/blocks/dialer/README.md +114 -0
- package/blocks/dialer/block.json +260 -0
- package/blocks/dialer/components/call-log.tsx +78 -0
- package/blocks/dialer/components/call-queue.tsx +188 -0
- package/blocks/dialer/components/dialer-workspace.tsx +47 -0
- package/blocks/dialer/components/disposition-panel.tsx +75 -0
- package/blocks/dialer/components/live-calls-table.tsx +107 -0
- package/blocks/dialer/components/session-start-form.tsx +111 -0
- package/blocks/dialer/components/softphone.tsx +122 -0
- package/blocks/dialer/components/transcript-viewer.tsx +82 -0
- package/blocks/dialer/hooks/use-dialer-numbers.ts +19 -0
- package/blocks/dialer/hooks/use-dialer-session.ts +68 -0
- package/blocks/dialer/hooks/use-dispositions.ts +29 -0
- package/blocks/dialer/lib/dialer-types.ts +158 -0
- package/blocks/dialer/lib/softphone-transport.ts +67 -0
- package/blocks/dialer/lib/twilio-transport.ts +74 -0
- package/blocks/dialer/recipes/01-create-session-and-dial.ts +39 -0
- package/blocks/dialer/recipes/02-disposition-calls.ts +26 -0
- package/blocks/dialer/recipes/03-review-a-call.ts +23 -0
- package/blocks/enrichment/README.md +105 -0
- package/blocks/enrichment/block.json +204 -0
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +205 -0
- package/blocks/enrichment/components/email-verify-badge.tsx +82 -0
- package/blocks/enrichment/components/enrich-button.tsx +136 -0
- package/blocks/enrichment/components/waterfall-config.tsx +127 -0
- package/blocks/enrichment/hooks/use-providers.ts +18 -0
- package/blocks/enrichment/lib/enrichment-types.ts +104 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +41 -0
- package/blocks/enrichment/recipes/lookup-and-verify.ts +25 -0
- package/blocks/enrichment/recipes/waterfall-pipeline.ts +26 -0
- package/blocks/forms/README.md +112 -0
- package/blocks/forms/block.json +200 -0
- package/blocks/forms/components/form-builder.tsx +150 -0
- package/blocks/forms/components/form-embed.tsx +103 -0
- package/blocks/forms/components/form-mapping-editor.tsx +125 -0
- package/blocks/forms/components/site-audit-panel.tsx +73 -0
- package/blocks/forms/components/submissions-table.tsx +158 -0
- package/blocks/forms/lib/form-types.ts +127 -0
- package/blocks/forms/recipes/install-snippet-and-form.ts +21 -0
- package/blocks/forms/recipes/retry-failed-deliveries.ts +26 -0
- package/blocks/forms/recipes/route-form-to-list.ts +44 -0
- package/blocks/inbox/README.md +72 -0
- package/blocks/inbox/block.json +194 -0
- package/blocks/inbox/components/inbox-list.tsx +156 -0
- package/blocks/inbox/components/inbox-screen.tsx +49 -0
- package/blocks/inbox/components/reply-composer.tsx +183 -0
- package/blocks/inbox/components/tag-and-assign.tsx +189 -0
- package/blocks/inbox/components/thread-badge.tsx +17 -0
- package/blocks/inbox/components/thread-view.tsx +108 -0
- package/blocks/inbox/hooks/use-inbox-thread.ts +24 -0
- package/blocks/inbox/hooks/use-inbox-threads.ts +40 -0
- package/blocks/inbox/lib/idempotency.ts +30 -0
- package/blocks/inbox/lib/inbox-types.ts +100 -0
- package/blocks/inbox/recipes/01-triage-and-reply.ts +43 -0
- package/blocks/inbox/recipes/02-tag-and-assign.ts +31 -0
- package/blocks/inbox/recipes/03-hot-contacts-digest.ts +16 -0
- package/blocks/integrations/README.md +75 -0
- package/blocks/integrations/block.json +172 -0
- package/blocks/integrations/components/integration-list.tsx +165 -0
- package/blocks/integrations/components/sync-status.tsx +223 -0
- package/blocks/integrations/components/webhook-manager.tsx +300 -0
- package/blocks/integrations/lib/integrations-utils.ts +62 -0
- package/blocks/integrations/recipes/crm-sync-health.ts +19 -0
- package/blocks/integrations/recipes/push-contacts-to-crm.ts +20 -0
- package/blocks/integrations/recipes/register-webhook.ts +18 -0
- package/blocks/integrations/recipes/verify-webhook-route.ts +33 -0
- package/blocks/intent/README.md +57 -0
- package/blocks/intent/block.json +138 -0
- package/blocks/intent/components/account-intent-score.tsx +91 -0
- package/blocks/intent/components/intent-feed.tsx +215 -0
- package/blocks/intent/components/intent-keyword-manager.tsx +331 -0
- package/blocks/intent/hooks/use-account-intent.ts +46 -0
- package/blocks/intent/lib/intent-utils.ts +95 -0
- package/blocks/intent/recipes/rank-intent-accounts.ts +36 -0
- package/blocks/intent/recipes/track-competitor-domain.ts +23 -0
- package/blocks/intent/recipes/who-visited-url.ts +18 -0
- package/blocks/lifecycle/README.md +79 -0
- package/blocks/lifecycle/block.json +198 -0
- package/blocks/lifecycle/components/churn-list.tsx +92 -0
- package/blocks/lifecycle/components/customer-table.tsx +119 -0
- package/blocks/lifecycle/components/lead-queue.tsx +127 -0
- package/blocks/lifecycle/components/lifecycle-board.tsx +102 -0
- package/blocks/lifecycle/components/stage-move-dialog.tsx +75 -0
- package/blocks/lifecycle/hooks/use-debounced.ts +13 -0
- package/blocks/lifecycle/hooks/use-lifecycle-stages.ts +27 -0
- package/blocks/lifecycle/lib/lifecycle.ts +99 -0
- package/blocks/lifecycle/recipes/customer-brief.ts +23 -0
- package/blocks/lifecycle/recipes/move-company-stage.ts +17 -0
- package/blocks/lifecycle/recipes/read-lifecycle-board.ts +21 -0
- package/blocks/lifecycle/recipes/unlock-stage-contacts.ts +25 -0
- package/blocks/lists/README.md +96 -0
- package/blocks/lists/block.json +166 -0
- package/blocks/lists/components/add-to-list-button.tsx +111 -0
- package/blocks/lists/components/list-picker.tsx +94 -0
- package/blocks/lists/components/list-table.tsx +160 -0
- package/blocks/lists/components/suppression-manager.tsx +153 -0
- package/blocks/lists/recipes/create-list-and-add-contacts.ts +35 -0
- package/blocks/lists/recipes/refresh-and-check-lists.ts +21 -0
- package/blocks/lists/recipes/suppress-and-reinstate.ts +30 -0
- package/blocks/mailboxes/README.md +71 -0
- package/blocks/mailboxes/block.json +187 -0
- package/blocks/mailboxes/components/connect-mailbox.tsx +284 -0
- package/blocks/mailboxes/components/domain-purchase.tsx +271 -0
- package/blocks/mailboxes/components/mailbox-list.tsx +185 -0
- package/blocks/mailboxes/components/warmup-health.tsx +232 -0
- package/blocks/mailboxes/lib/mailboxes-utils.ts +69 -0
- package/blocks/mailboxes/recipes/buy-domain-and-mailboxes.ts +64 -0
- package/blocks/mailboxes/recipes/connect-imap-mailbox.ts +38 -0
- package/blocks/mailboxes/recipes/is-it-safe-to-send.ts +17 -0
- package/blocks/mailboxes/recipes/warmup-report.ts +31 -0
- package/blocks/newsletter/README.md +122 -0
- package/blocks/newsletter/block.json +224 -0
- package/blocks/newsletter/components/issue-form.tsx +124 -0
- package/blocks/newsletter/components/issue-scheduler.tsx +152 -0
- package/blocks/newsletter/components/newsletter-editor.tsx +131 -0
- package/blocks/newsletter/components/newsletter-stats.tsx +93 -0
- package/blocks/newsletter/components/subscriber-table.tsx +173 -0
- package/blocks/newsletter/lib/newsletter-types.ts +97 -0
- package/blocks/newsletter/recipes/create-newsletter-and-draft-issue.ts +27 -0
- package/blocks/newsletter/recipes/import-audience-and-send.ts +43 -0
- package/blocks/newsletter/recipes/issue-performance-report.ts +26 -0
- package/blocks/nurture/README.md +110 -0
- package/blocks/nurture/block.json +191 -0
- package/blocks/nurture/components/nurture-builder.tsx +114 -0
- package/blocks/nurture/components/nurture-enroll.tsx +107 -0
- package/blocks/nurture/components/nurture-form.tsx +120 -0
- package/blocks/nurture/components/nurture-stats.tsx +81 -0
- package/blocks/nurture/lib/nurture-types.ts +109 -0
- package/blocks/nurture/recipes/create-a-nurture.ts +34 -0
- package/blocks/nurture/recipes/enroll-contacts-and-launch.ts +24 -0
- package/blocks/nurture/recipes/nurture-health-check.ts +28 -0
- package/blocks/objects/README.md +120 -0
- package/blocks/objects/block.json +243 -0
- package/blocks/objects/components/attribute-field-input.tsx +82 -0
- package/blocks/objects/components/attribute-form-dialog.tsx +159 -0
- package/blocks/objects/components/object-not-found.tsx +34 -0
- package/blocks/objects/components/object-record-detail.tsx +143 -0
- package/blocks/objects/components/object-schema-editor.tsx +197 -0
- package/blocks/objects/components/object-table.tsx +130 -0
- package/blocks/objects/components/record-form-dialog.tsx +112 -0
- package/blocks/objects/components/record-roles-panel.tsx +106 -0
- package/blocks/objects/hooks/use-object-schema.ts +36 -0
- package/blocks/objects/lib/object-types.ts +214 -0
- package/blocks/objects/recipes/define-object-and-add-records.ts +24 -0
- package/blocks/objects/recipes/update-audit-and-archive-records.ts +39 -0
- package/blocks/objects/recipes/upsert-records-from-your-system.ts +30 -0
- package/blocks/pages/README.md +104 -0
- package/blocks/pages/block.json +223 -0
- package/blocks/pages/components/brand-kit-editor.tsx +152 -0
- package/blocks/pages/components/content-library.tsx +163 -0
- package/blocks/pages/components/page-editor.tsx +253 -0
- package/blocks/pages/components/page-list.tsx +159 -0
- package/blocks/pages/components/seo-report.tsx +159 -0
- package/blocks/pages/lib/studio.ts +77 -0
- package/blocks/pages/recipes/audit-site-seo.ts +28 -0
- package/blocks/pages/recipes/create-and-publish-page.ts +26 -0
- package/blocks/pages/recipes/edit-page-section.ts +25 -0
- package/blocks/pages/recipes/refresh-brand-kit.ts +22 -0
- package/blocks/quotes/README.md +79 -0
- package/blocks/quotes/block.json +183 -0
- package/blocks/quotes/components/product-picker.tsx +80 -0
- package/blocks/quotes/components/quote-builder.tsx +195 -0
- package/blocks/quotes/components/quote-list.tsx +79 -0
- package/blocks/quotes/components/quote-viewer.tsx +135 -0
- package/blocks/quotes/components/signature-status.tsx +77 -0
- package/blocks/quotes/hooks/use-debounced.ts +13 -0
- package/blocks/quotes/lib/quotes.ts +142 -0
- package/blocks/quotes/recipes/create-and-send-quote.ts +44 -0
- package/blocks/quotes/recipes/revise-sent-quote.ts +18 -0
- package/blocks/quotes/recipes/track-quote-signature.ts +22 -0
- package/blocks/radar/README.md +106 -0
- package/blocks/radar/block.json +189 -0
- package/blocks/radar/components/competitor-battle-card.tsx +53 -0
- package/blocks/radar/components/competitor-tracker.tsx +158 -0
- package/blocks/radar/components/radar-overview.tsx +67 -0
- package/blocks/radar/components/radar-report.tsx +86 -0
- package/blocks/radar/hooks/use-competitors.ts +23 -0
- package/blocks/radar/lib/radar-types.ts +124 -0
- package/blocks/radar/recipes/battle-card.ts +28 -0
- package/blocks/radar/recipes/deep-dive-report.ts +32 -0
- package/blocks/radar/recipes/marketing-snapshot.ts +18 -0
- package/blocks/radar/recipes/track-and-scan.ts +39 -0
- package/blocks/search/README.md +129 -0
- package/blocks/search/block.json +185 -0
- package/blocks/search/components/filter-builder.tsx +149 -0
- package/blocks/search/components/prospect-results.tsx +175 -0
- package/blocks/search/components/save-to-list-dialog.tsx +135 -0
- package/blocks/search/components/saved-searches.tsx +95 -0
- package/blocks/search/hooks/use-filter-options.ts +41 -0
- package/blocks/search/lib/search-types.ts +149 -0
- package/blocks/search/recipes/explore-filter-values.ts +27 -0
- package/blocks/search/recipes/saved-searches.ts +34 -0
- package/blocks/search/recipes/search-and-save-to-list.ts +37 -0
- package/blocks/sequencer/README.md +87 -0
- package/blocks/sequencer/block.json +214 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +93 -0
- package/blocks/sequencer/components/sequence-builder.tsx +306 -0
- package/blocks/sequencer/components/sequence-list.tsx +200 -0
- package/blocks/sequencer/components/sequence-stats.tsx +256 -0
- package/blocks/sequencer/components/step-editor.tsx +213 -0
- package/blocks/sequencer/lib/sequencer-utils.ts +168 -0
- package/blocks/sequencer/recipes/create-multichannel-sequence.ts +51 -0
- package/blocks/sequencer/recipes/launch-and-enroll.ts +44 -0
- package/blocks/sequencer/recipes/pause-when-unsafe.ts +30 -0
- package/blocks/sequencer/recipes/sequence-report.ts +22 -0
- package/blocks/social-ads/README.md +128 -0
- package/blocks/social-ads/block.json +256 -0
- package/blocks/social-ads/components/ad-campaign-list.tsx +136 -0
- package/blocks/social-ads/components/audience-sync.tsx +150 -0
- package/blocks/social-ads/components/creative-gallery.tsx +121 -0
- package/blocks/social-ads/components/post-composer.tsx +122 -0
- package/blocks/social-ads/components/social-post-list.tsx +116 -0
- package/blocks/social-ads/hooks/use-social-accounts.ts +16 -0
- package/blocks/social-ads/lib/social-ads-types.ts +167 -0
- package/blocks/social-ads/recipes/creative-to-gallery.ts +41 -0
- package/blocks/social-ads/recipes/schedule-post.ts +51 -0
- package/blocks/social-ads/recipes/sync-audience-and-launch.ts +41 -0
- package/blocks/visitors/README.md +56 -0
- package/blocks/visitors/block.json +139 -0
- package/blocks/visitors/components/snippet-install.tsx +169 -0
- package/blocks/visitors/components/traffic-chart.tsx +156 -0
- package/blocks/visitors/components/visitor-company-card.tsx +92 -0
- package/blocks/visitors/components/visitor-feed.tsx +157 -0
- package/blocks/visitors/lib/visitors-utils.ts +78 -0
- package/blocks/visitors/recipes/check-snippet-installed.ts +24 -0
- package/blocks/visitors/recipes/recent-company-visits.ts +27 -0
- package/blocks/visitors/recipes/traffic-summary.ts +24 -0
- package/blocks/webchat/README.md +87 -0
- package/blocks/webchat/block.json +134 -0
- package/blocks/webchat/components/chat-widget-config.tsx +160 -0
- package/blocks/webchat/components/conversation-list.tsx +74 -0
- package/blocks/webchat/lib/webchat-types.ts +83 -0
- package/blocks/webchat/recipes/create-bot-and-style-widget.ts +24 -0
- package/blocks/webchat/recipes/list-chat-visitors.ts +24 -0
- package/blocks/workbench/README.md +135 -0
- package/blocks/workbench/block.json +259 -0
- package/blocks/workbench/components/column-recipe-editor.tsx +183 -0
- package/blocks/workbench/components/run-progress.tsx +132 -0
- package/blocks/workbench/components/workbench-add-rows.tsx +100 -0
- package/blocks/workbench/components/workbench-grid.tsx +159 -0
- package/blocks/workbench/components/workbench-row-actions.tsx +102 -0
- package/blocks/workbench/components/workbench-table-list.tsx +105 -0
- package/blocks/workbench/hooks/use-workbench-table.ts +30 -0
- package/blocks/workbench/lib/waterfall-presets.ts +119 -0
- package/blocks/workbench/lib/workbench-types.ts +191 -0
- package/blocks/workbench/recipes/build-enriched-table.ts +65 -0
- package/blocks/workbench/recipes/import-from-search.ts +42 -0
- package/blocks/workbench/recipes/scrape-into-table.ts +36 -0
- package/blocks/workbench/recipes/waterfall-column.ts +55 -0
- package/blocks/workflows/README.md +100 -0
- package/blocks/workflows/block.json +188 -0
- package/blocks/workflows/components/run-history.tsx +136 -0
- package/blocks/workflows/components/trigger-config.tsx +134 -0
- package/blocks/workflows/components/workflow-editor.tsx +142 -0
- package/blocks/workflows/components/workflow-list.tsx +153 -0
- package/blocks/workflows/hooks/use-workflow.ts +51 -0
- package/blocks/workflows/lib/graph.ts +187 -0
- package/blocks/workflows/recipes/create-workflow.ts +35 -0
- package/blocks/workflows/recipes/find-failed-runs.ts +15 -0
- package/blocks/workflows/recipes/run-workflow-and-wait.ts +32 -0
- package/blocks/workflows/recipes/set-trigger.ts +22 -0
- package/dist/cli.js +5330 -29
- package/dist/index.browser.js +7740 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.browser.mjs +7717 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.d.mts +1440 -21
- package/dist/index.d.ts +1440 -21
- package/dist/index.js +42 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +42 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.browser.js +7598 -0
- package/dist/react.browser.js.map +1 -0
- package/dist/react.browser.mjs +7568 -0
- package/dist/react.browser.mjs.map +1 -0
- package/dist/react.d.mts +1340 -18
- package/dist/react.d.ts +1340 -18
- package/dist/react.js +41 -2
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +41 -2
- package/dist/react.mjs.map +1 -1
- package/dist/types/agentSetup.d.ts +6 -6
- package/dist/types/blocks/add.d.ts +53 -0
- package/dist/types/blocks/create.d.ts +46 -0
- package/dist/types/blocks/doctor.d.ts +26 -0
- package/dist/types/blocks/registry.d.ts +134 -0
- package/dist/types/cli.d.ts +16 -0
- package/dist/types/core.d.ts +1 -1
- package/dist/types/crypto-browser.d.ts +2 -0
- package/dist/types/crypto-node.d.ts +6 -0
- package/dist/types/react.d.ts +1 -1
- package/dist/types/version.d.ts +2 -2
- package/dist/types/webhooks.d.ts +2 -2
- package/dist/ui/index.d.mts +54 -0
- package/dist/ui/index.d.ts +54 -0
- package/dist/ui/index.js +105 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/index.mjs +44 -0
- package/dist/ui/index.mjs.map +1 -0
- package/dist/ui/lib/compose-refs.d.mts +19 -0
- package/dist/ui/lib/compose-refs.d.ts +19 -0
- package/dist/ui/lib/compose-refs.js +76 -0
- package/dist/ui/lib/compose-refs.js.map +1 -0
- package/dist/ui/lib/compose-refs.mjs +41 -0
- package/dist/ui/lib/compose-refs.mjs.map +1 -0
- package/dist/ui/lib/use-mobile.d.mts +4 -0
- package/dist/ui/lib/use-mobile.d.ts +4 -0
- package/dist/ui/lib/use-mobile.js +57 -0
- package/dist/ui/lib/use-mobile.js.map +1 -0
- package/dist/ui/lib/use-mobile.mjs +22 -0
- package/dist/ui/lib/use-mobile.mjs.map +1 -0
- package/dist/ui/lib/utils.d.mts +10 -0
- package/dist/ui/lib/utils.d.ts +10 -0
- package/dist/ui/lib/utils.js +33 -0
- package/dist/ui/lib/utils.js.map +1 -0
- package/dist/ui/lib/utils.mjs +9 -0
- package/dist/ui/lib/utils.mjs.map +1 -0
- package/dist/ui/primitives/alert-dialog.d.mts +22 -0
- package/dist/ui/primitives/alert-dialog.d.ts +22 -0
- package/dist/ui/primitives/alert-dialog.js +163 -0
- package/dist/ui/primitives/alert-dialog.js.map +1 -0
- package/dist/ui/primitives/alert-dialog.mjs +119 -0
- package/dist/ui/primitives/alert-dialog.mjs.map +1 -0
- package/dist/ui/primitives/avatar.d.mts +8 -0
- package/dist/ui/primitives/avatar.d.ts +8 -0
- package/dist/ui/primitives/avatar.js +91 -0
- package/dist/ui/primitives/avatar.js.map +1 -0
- package/dist/ui/primitives/avatar.mjs +55 -0
- package/dist/ui/primitives/avatar.mjs.map +1 -0
- package/dist/ui/primitives/breadcrumb.d.mts +13 -0
- package/dist/ui/primitives/breadcrumb.d.ts +13 -0
- package/dist/ui/primitives/breadcrumb.js +127 -0
- package/dist/ui/primitives/breadcrumb.js.map +1 -0
- package/dist/ui/primitives/breadcrumb.mjs +97 -0
- package/dist/ui/primitives/breadcrumb.mjs.map +1 -0
- package/dist/ui/primitives/button.d.mts +15 -0
- package/dist/ui/primitives/button.d.ts +15 -0
- package/dist/ui/primitives/button.js +91 -0
- package/dist/ui/primitives/button.js.map +1 -0
- package/dist/ui/primitives/button.mjs +56 -0
- package/dist/ui/primitives/button.mjs.map +1 -0
- package/dist/ui/primitives/card.d.mts +10 -0
- package/dist/ui/primitives/card.d.ts +10 -0
- package/dist/ui/primitives/card.js +101 -0
- package/dist/ui/primitives/card.js.map +1 -0
- package/dist/ui/primitives/card.mjs +62 -0
- package/dist/ui/primitives/card.mjs.map +1 -0
- package/dist/ui/primitives/carousel.d.mts +45 -0
- package/dist/ui/primitives/carousel.d.ts +45 -0
- package/dist/ui/primitives/carousel.js +300 -0
- package/dist/ui/primitives/carousel.js.map +1 -0
- package/dist/ui/primitives/carousel.mjs +261 -0
- package/dist/ui/primitives/carousel.mjs.map +1 -0
- package/dist/ui/primitives/chart.d.mts +68 -0
- package/dist/ui/primitives/chart.d.ts +68 -0
- package/dist/ui/primitives/chart.js +280 -0
- package/dist/ui/primitives/chart.js.map +1 -0
- package/dist/ui/primitives/chart.mjs +241 -0
- package/dist/ui/primitives/chart.mjs.map +1 -0
- package/dist/ui/primitives/collapsible.d.mts +8 -0
- package/dist/ui/primitives/collapsible.d.ts +8 -0
- package/dist/ui/primitives/collapsible.js +72 -0
- package/dist/ui/primitives/collapsible.js.map +1 -0
- package/dist/ui/primitives/collapsible.mjs +36 -0
- package/dist/ui/primitives/collapsible.mjs.map +1 -0
- package/dist/ui/primitives/context-menu.d.mts +29 -0
- package/dist/ui/primitives/context-menu.d.ts +29 -0
- package/dist/ui/primitives/context-menu.js +203 -0
- package/dist/ui/primitives/context-menu.js.map +1 -0
- package/dist/ui/primitives/context-menu.mjs +155 -0
- package/dist/ui/primitives/context-menu.mjs.map +1 -0
- package/dist/ui/primitives/dialog.d.mts +26 -0
- package/dist/ui/primitives/dialog.d.ts +26 -0
- package/dist/ui/primitives/dialog.js +192 -0
- package/dist/ui/primitives/dialog.js.map +1 -0
- package/dist/ui/primitives/dialog.mjs +148 -0
- package/dist/ui/primitives/dialog.mjs.map +1 -0
- package/dist/ui/primitives/drawer.d.mts +26 -0
- package/dist/ui/primitives/drawer.d.ts +26 -0
- package/dist/ui/primitives/drawer.js +146 -0
- package/dist/ui/primitives/drawer.js.map +1 -0
- package/dist/ui/primitives/drawer.mjs +103 -0
- package/dist/ui/primitives/drawer.mjs.map +1 -0
- package/dist/ui/primitives/input.d.mts +5 -0
- package/dist/ui/primitives/input.d.ts +5 -0
- package/dist/ui/primitives/input.js +46 -0
- package/dist/ui/primitives/input.js.map +1 -0
- package/dist/ui/primitives/input.mjs +22 -0
- package/dist/ui/primitives/input.mjs.map +1 -0
- package/dist/ui/primitives/item.d.mts +27 -0
- package/dist/ui/primitives/item.d.ts +27 -0
- package/dist/ui/primitives/item.js +221 -0
- package/dist/ui/primitives/item.js.map +1 -0
- package/dist/ui/primitives/item.mjs +188 -0
- package/dist/ui/primitives/item.mjs.map +1 -0
- package/dist/ui/primitives/kbd.d.mts +15 -0
- package/dist/ui/primitives/kbd.d.ts +15 -0
- package/dist/ui/primitives/kbd.js +45 -0
- package/dist/ui/primitives/kbd.js.map +1 -0
- package/dist/ui/primitives/kbd.mjs +21 -0
- package/dist/ui/primitives/kbd.mjs.map +1 -0
- package/dist/ui/primitives/label.d.mts +6 -0
- package/dist/ui/primitives/label.d.ts +6 -0
- package/dist/ui/primitives/label.js +55 -0
- package/dist/ui/primitives/label.js.map +1 -0
- package/dist/ui/primitives/label.mjs +21 -0
- package/dist/ui/primitives/label.mjs.map +1 -0
- package/dist/ui/primitives/resizable.d.mts +10 -0
- package/dist/ui/primitives/resizable.d.ts +10 -0
- package/dist/ui/primitives/resizable.js +68 -0
- package/dist/ui/primitives/resizable.js.map +1 -0
- package/dist/ui/primitives/resizable.mjs +42 -0
- package/dist/ui/primitives/resizable.mjs.map +1 -0
- package/dist/ui/primitives/save-status.d.mts +31 -0
- package/dist/ui/primitives/save-status.d.ts +31 -0
- package/dist/ui/primitives/save-status.js +150 -0
- package/dist/ui/primitives/save-status.js.map +1 -0
- package/dist/ui/primitives/save-status.mjs +126 -0
- package/dist/ui/primitives/save-status.mjs.map +1 -0
- package/dist/ui/primitives/separator.d.mts +6 -0
- package/dist/ui/primitives/separator.d.ts +6 -0
- package/dist/ui/primitives/separator.js +62 -0
- package/dist/ui/primitives/separator.js.map +1 -0
- package/dist/ui/primitives/separator.mjs +28 -0
- package/dist/ui/primitives/separator.mjs.map +1 -0
- package/dist/ui/primitives/sheet.d.mts +16 -0
- package/dist/ui/primitives/sheet.d.ts +16 -0
- package/dist/ui/primitives/sheet.js +170 -0
- package/dist/ui/primitives/sheet.js.map +1 -0
- package/dist/ui/primitives/sheet.mjs +129 -0
- package/dist/ui/primitives/sheet.mjs.map +1 -0
- package/dist/ui/primitives/sidebar.d.mts +74 -0
- package/dist/ui/primitives/sidebar.d.ts +74 -0
- package/dist/ui/primitives/sidebar.js +697 -0
- package/dist/ui/primitives/sidebar.js.map +1 -0
- package/dist/ui/primitives/sidebar.mjs +651 -0
- package/dist/ui/primitives/sidebar.mjs.map +1 -0
- package/dist/ui/primitives/skeleton.d.mts +5 -0
- package/dist/ui/primitives/skeleton.d.ts +5 -0
- package/dist/ui/primitives/skeleton.js +40 -0
- package/dist/ui/primitives/skeleton.js.map +1 -0
- package/dist/ui/primitives/skeleton.mjs +16 -0
- package/dist/ui/primitives/skeleton.mjs.map +1 -0
- package/dist/ui/primitives/slider.d.mts +20 -0
- package/dist/ui/primitives/slider.d.ts +20 -0
- package/dist/ui/primitives/slider.js +112 -0
- package/dist/ui/primitives/slider.js.map +1 -0
- package/dist/ui/primitives/slider.mjs +78 -0
- package/dist/ui/primitives/slider.mjs.map +1 -0
- package/dist/ui/primitives/sortable.d.mts +50 -0
- package/dist/ui/primitives/sortable.d.ts +50 -0
- package/dist/ui/primitives/sortable.js +456 -0
- package/dist/ui/primitives/sortable.js.map +1 -0
- package/dist/ui/primitives/sortable.mjs +435 -0
- package/dist/ui/primitives/sortable.mjs.map +1 -0
- package/dist/ui/primitives/switch.d.mts +6 -0
- package/dist/ui/primitives/switch.d.ts +6 -0
- package/dist/ui/primitives/switch.js +67 -0
- package/dist/ui/primitives/switch.js.map +1 -0
- package/dist/ui/primitives/switch.mjs +33 -0
- package/dist/ui/primitives/switch.mjs.map +1 -0
- package/dist/ui/primitives/table.d.mts +12 -0
- package/dist/ui/primitives/table.d.ts +12 -0
- package/dist/ui/primitives/table.js +130 -0
- package/dist/ui/primitives/table.js.map +1 -0
- package/dist/ui/primitives/table.mjs +89 -0
- package/dist/ui/primitives/table.mjs.map +1 -0
- package/dist/ui/primitives/tabs.d.mts +9 -0
- package/dist/ui/primitives/tabs.d.ts +9 -0
- package/dist/ui/primitives/tabs.js +86 -0
- package/dist/ui/primitives/tabs.js.map +1 -0
- package/dist/ui/primitives/tabs.mjs +49 -0
- package/dist/ui/primitives/tabs.mjs.map +1 -0
- package/dist/ui/primitives/textarea.d.mts +5 -0
- package/dist/ui/primitives/textarea.d.ts +5 -0
- package/dist/ui/primitives/textarea.js +43 -0
- package/dist/ui/primitives/textarea.js.map +1 -0
- package/dist/ui/primitives/textarea.mjs +19 -0
- package/dist/ui/primitives/textarea.mjs.map +1 -0
- package/dist/ui/primitives/tooltip.d.mts +9 -0
- package/dist/ui/primitives/tooltip.d.ts +9 -0
- package/dist/ui/primitives/tooltip.js +94 -0
- package/dist/ui/primitives/tooltip.js.map +1 -0
- package/dist/ui/primitives/tooltip.mjs +57 -0
- package/dist/ui/primitives/tooltip.mjs.map +1 -0
- package/docs/agents.md +72 -29
- package/docs/api/README.md +6 -3
- package/docs/api/appObjects.d.ts +1 -1
- package/docs/api/appObjects.md +1 -1
- package/docs/api/by-path.md +29 -0
- package/docs/api/objects.d.ts +72 -5
- package/docs/api/objects.md +84 -7
- package/docs/api/processLists.d.ts +247 -0
- package/docs/api/processLists.md +409 -0
- package/docs/api/reports.d.ts +476 -0
- package/docs/api/reports.md +539 -0
- package/docs/api/scheduleBuy.d.ts +12 -8
- package/docs/api/scheduleBuy.md +13 -10
- package/docs/api/seo.d.ts +406 -3
- package/docs/api/seo.md +505 -1
- package/docs/api/workspaceBackup.d.ts +324 -0
- package/docs/api/workspaceBackup.md +427 -0
- package/package.json +174 -5
- package/stacks/6sense.json +46 -0
- package/stacks/README.md +20 -0
- package/stacks/apollo.json +128 -0
- package/stacks/clay.json +59 -0
- package/stacks/gong.json +72 -0
- package/stacks/hubspot.json +162 -0
- package/stacks/intercom.json +53 -0
- package/stacks/mailchimp.json +71 -0
- package/stacks/outreach.json +86 -0
- package/stacks/salesforce.json +94 -0
- package/stacks/stack.schema.json +32 -0
- package/stacks/starter.json +22 -0
- package/styles/styles.css +2 -0
- package/styles/theme.css +181 -0
- package/styles/tokens.css +284 -0
- package/templates/next-app/README.md.tmpl +35 -0
- package/templates/next-app/_pages/redirect-page.tsx.tmpl +5 -0
- package/templates/next-app/_pages/route-page.tsx.tmpl +9 -0
- package/templates/next-app/app/globals.css.tmpl +8 -0
- package/templates/next-app/app/layout.tsx.tmpl +19 -0
- package/templates/next-app/components/app-shell.tsx.tmpl +34 -0
- package/templates/next-app/dot-env.local.example.tmpl +6 -0
- package/templates/next-app/dot-gitignore.tmpl +7 -0
- package/templates/next-app/lib/nav.ts.tmpl +2 -0
- package/templates/next-app/next.config.mjs.tmpl +4 -0
- package/templates/next-app/package.json.tmpl +25 -0
- package/templates/next-app/postcss.config.mjs.tmpl +4 -0
- package/templates/next-app/tsconfig.json.tmpl +21 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SuppressionManager: the do-not-contact ledger. See who is suppressed (filter by channel and
|
|
5
|
+
* reason category), suppress contacts by id, check one contact, and reinstate contacts.
|
|
6
|
+
*
|
|
7
|
+
* SDK operations, in order:
|
|
8
|
+
* 1. listSuppressions GET /contacts/suppressions?channel&category&limit&offset (read)
|
|
9
|
+
* 2. bulkAddSuppressions POST /contacts/suppressions/bulk-add { contact_ids, channel, reason } (write)
|
|
10
|
+
* 3. checkContactSuppression GET /contacts/{contact_id}/suppression (read)
|
|
11
|
+
* 4. reinstateSuppressions POST /contacts/suppressions/reinstate { contact_ids, reason } (write, but it lets
|
|
12
|
+
* sequences and campaigns contact people again, so it goes through ConfirmSpendDialog as "external")
|
|
13
|
+
*
|
|
14
|
+
* The ledger holds ids, not names or emails (no personal data): resolve a name with ContactDetail.
|
|
15
|
+
* Suppressing only ever REDUCES outreach, so it runs without a confirmation.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { useState } from "react";
|
|
19
|
+
import { Button, Input, Label, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Textarea } from "@graph8/sdk/ui";
|
|
20
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
21
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
22
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
23
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
24
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
25
|
+
import { g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
26
|
+
|
|
27
|
+
export interface SuppressionRecord {
|
|
28
|
+
id: string;
|
|
29
|
+
contact_id: number;
|
|
30
|
+
channel: string;
|
|
31
|
+
category: string;
|
|
32
|
+
source?: string | null;
|
|
33
|
+
reason?: string | null;
|
|
34
|
+
created_at?: string | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface ContactStatus { contact_id: number; is_suppressed: boolean; active_channels: string[]; suppressions: SuppressionRecord[] }
|
|
38
|
+
interface BulkAdd { suppressed: number; total_requested: number; channel: string; not_found: number[] }
|
|
39
|
+
interface Reinstate { reinstated: number[]; skipped: Array<{ contact_id: number; reason: string }>; not_found: number[] }
|
|
40
|
+
|
|
41
|
+
export interface SuppressionManagerProps {
|
|
42
|
+
pageSize?: number;
|
|
43
|
+
/** Open the contact behind a ledger row in your ContactDetail. */
|
|
44
|
+
onOpenContact?: (contactId: number) => void;
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** "12, 15 16\n17" -> [12, 15, 16, 17]. Anything that is not a positive whole number is dropped. */
|
|
49
|
+
export function parseIds(text: string): number[] {
|
|
50
|
+
return [...new Set(text.split(/[\s,;]+/).map(Number).filter((n) => Number.isInteger(n) && n > 0))];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function SuppressionManager({ pageSize = 50, onOpenContact, className }: SuppressionManagerProps) {
|
|
54
|
+
const [channel, setChannel] = useState("");
|
|
55
|
+
const [category, setCategory] = useState("");
|
|
56
|
+
const [offset, setOffset] = useState(0);
|
|
57
|
+
const [idsText, setIdsText] = useState("");
|
|
58
|
+
const [addChannel, setAddChannel] = useState("all");
|
|
59
|
+
const [reason, setReason] = useState("");
|
|
60
|
+
const [picked, setPicked] = useState<Set<number>>(new Set());
|
|
61
|
+
const [confirmReinstate, setConfirmReinstate] = useState(false);
|
|
62
|
+
const [checkId, setCheckId] = useState("");
|
|
63
|
+
const [reinstateReason, setReinstateReason] = useState("");
|
|
64
|
+
|
|
65
|
+
const ledger = useG8Query((signal) => g8Get<SuppressionRecord[]>("/contacts/suppressions", { channel: channel || undefined, category: category || undefined, limit: pageSize, offset }, { signal }), [channel, category, offset, pageSize]);
|
|
66
|
+
const add = useG8Mutation((ids: number[]) => g8Post<BulkAdd>("/contacts/suppressions/bulk-add", { contact_ids: ids, channel: addChannel, reason: reason.trim() || undefined }), {
|
|
67
|
+
onSuccess: () => { setIdsText(""); setReason(""); void ledger.refetch(); },
|
|
68
|
+
});
|
|
69
|
+
const reinstate = useG8Mutation((ids: number[]) => g8Post<Reinstate>("/contacts/suppressions/reinstate", { contact_ids: ids, reason: reinstateReason.trim() || undefined }), {
|
|
70
|
+
onSuccess: () => { setConfirmReinstate(false); setReinstateReason(""); setPicked(new Set()); void ledger.refetch(); },
|
|
71
|
+
});
|
|
72
|
+
const check = useG8Mutation((id: number) => g8Get<ContactStatus>(`/contacts/${id}/suppression`));
|
|
73
|
+
|
|
74
|
+
const rows = (ledger.data?.data ?? []) as SuppressionRecord[];
|
|
75
|
+
const ids = parseIds(idsText);
|
|
76
|
+
const selectedContacts = [...picked];
|
|
77
|
+
const togglePick = (contactId: number) => setPicked((p) => { const n = new Set(p); if (!n.delete(contactId)) n.add(contactId); return n; });
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className={`flex flex-col gap-5 ${className ?? ""}`}>
|
|
81
|
+
<section className="flex flex-col gap-2" aria-label="Suppress contacts">
|
|
82
|
+
<Label htmlFor="suppress-ids">Suppress contacts by id</Label>
|
|
83
|
+
<Textarea id="suppress-ids" rows={2} placeholder="101, 102, 103" value={idsText} onChange={(e) => setIdsText(e.target.value)} />
|
|
84
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
85
|
+
<select aria-label="Channel to suppress" className="h-9 rounded-md border bg-background px-2 text-sm" value={addChannel} onChange={(e) => setAddChannel(e.target.value)}>
|
|
86
|
+
<option value="all">All channels</option><option value="email">Email</option><option value="phone">Phone</option><option value="linkedin">LinkedIn</option>
|
|
87
|
+
</select>
|
|
88
|
+
<Input aria-label="Reason" placeholder="Reason (optional)" className="w-64" value={reason} onChange={(e) => setReason(e.target.value)} />
|
|
89
|
+
<Button size="sm" loading={add.loading} disabled={ids.length === 0} onClick={() => void add.mutate(ids)}>Suppress {ids.length || ""} contact{ids.length === 1 ? "" : "s"}</Button>
|
|
90
|
+
</div>
|
|
91
|
+
{add.data ? <p role="status" className="text-sm">Suppressed {add.data.data.suppressed} of {add.data.data.total_requested}.{add.data.data.not_found.length ? ` ${add.data.data.not_found.length} ids do not exist.` : ""}</p> : null}
|
|
92
|
+
{add.error ? <ApiError error={add.error} title="Could not suppress" /> : null}
|
|
93
|
+
</section>
|
|
94
|
+
|
|
95
|
+
<section className="flex items-end gap-2" aria-label="Check a contact">
|
|
96
|
+
<div className="flex flex-col gap-1"><Label htmlFor="check-id">Check one contact</Label><Input id="check-id" inputMode="numeric" placeholder="Contact id" className="w-40" value={checkId} onChange={(e) => setCheckId(e.target.value)} /></div>
|
|
97
|
+
<Button variant="outline" size="sm" loading={check.loading} disabled={!Number(checkId)} onClick={() => void check.mutate(Number(checkId))}>Check</Button>
|
|
98
|
+
{check.data ? <p role="status" className="text-sm">{check.data.data.is_suppressed ? `Suppressed on: ${check.data.data.active_channels.join(", ")}` : "Not suppressed"}</p> : null}
|
|
99
|
+
{check.error ? <ApiError error={check.error} title="Could not check that contact" /> : null}
|
|
100
|
+
</section>
|
|
101
|
+
|
|
102
|
+
<section className="flex flex-col gap-2" aria-label="Suppression ledger">
|
|
103
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
104
|
+
<select aria-label="Filter by channel" className="h-9 rounded-md border bg-background px-2 text-sm" value={channel} onChange={(e) => { setChannel(e.target.value); setOffset(0); }}>
|
|
105
|
+
<option value="">Any channel</option><option value="all">All</option><option value="email">Email</option><option value="phone">Phone</option><option value="linkedin">LinkedIn</option>
|
|
106
|
+
</select>
|
|
107
|
+
<select aria-label="Filter by reason" className="h-9 rounded-md border bg-background px-2 text-sm" value={category} onChange={(e) => { setCategory(e.target.value); setOffset(0); }}>
|
|
108
|
+
<option value="">Any reason</option><option value="contact_initiated">Contact asked</option><option value="org_initiated">Our team</option><option value="system">System</option>
|
|
109
|
+
</select>
|
|
110
|
+
<Button className="ml-auto" variant="outline" size="sm" disabled={selectedContacts.length === 0} onClick={() => setConfirmReinstate(true)}>Reinstate {selectedContacts.length || ""}</Button>
|
|
111
|
+
</div>
|
|
112
|
+
{ledger.error ? <ApiError error={ledger.error} title="Could not load the ledger" onRetry={() => void ledger.refetch()} /> : null}
|
|
113
|
+
{reinstate.error ? <ApiError error={reinstate.error} title="Could not reinstate" /> : null}
|
|
114
|
+
{reinstate.data ? <p role="status" className="text-sm">Reinstated {reinstate.data.data.reinstated.length}.{reinstate.data.data.skipped.length ? ` Skipped ${reinstate.data.data.skipped.length}: ${reinstate.data.data.skipped.map((s) => `${s.contact_id} (${s.reason})`).join(", ")}.` : ""}</p> : null}
|
|
115
|
+
<div className="rounded-md border">
|
|
116
|
+
<Table>
|
|
117
|
+
<TableHeader><TableRow><TableHead className="w-8" /><TableHead>Contact</TableHead><TableHead>Channel</TableHead><TableHead>Reason</TableHead><TableHead>Note</TableHead><TableHead>Since</TableHead></TableRow></TableHeader>
|
|
118
|
+
<TableBody>
|
|
119
|
+
{ledger.loading && rows.length === 0
|
|
120
|
+
? Array.from({ length: 4 }, (_, i) => <TableRow key={`sk${i}`}><TableCell colSpan={6}><Skeleton className="h-5 w-full" /></TableCell></TableRow>)
|
|
121
|
+
: rows.map((r) => (
|
|
122
|
+
<TableRow key={r.id} data-testid="suppression-row">
|
|
123
|
+
<TableCell><input type="checkbox" aria-label={`Select contact ${r.contact_id}`} checked={picked.has(r.contact_id)} onChange={() => togglePick(r.contact_id)} /></TableCell>
|
|
124
|
+
<TableCell>{onOpenContact ? <Button variant="link" size="sm" onClick={() => onOpenContact(r.contact_id)}>#{r.contact_id}</Button> : `#${r.contact_id}`}</TableCell>
|
|
125
|
+
<TableCell className="capitalize">{r.channel}</TableCell>
|
|
126
|
+
<TableCell>{r.category.replace(/_/g, " ")}</TableCell>
|
|
127
|
+
<TableCell>{r.reason ?? "-"}</TableCell>
|
|
128
|
+
<TableCell>{r.created_at ? new Date(r.created_at).toLocaleDateString() : "-"}</TableCell>
|
|
129
|
+
</TableRow>
|
|
130
|
+
))}
|
|
131
|
+
</TableBody>
|
|
132
|
+
</Table>
|
|
133
|
+
{!ledger.loading && !ledger.error && rows.length === 0 ? <EmptyState title="Nobody is suppressed" description="Suppressed contacts are skipped by every sequence, campaign and newsletter." /> : null}
|
|
134
|
+
</div>
|
|
135
|
+
<div className="flex items-center justify-end gap-2 text-sm">
|
|
136
|
+
<Button variant="outline" size="sm" disabled={offset === 0 || ledger.loading} onClick={() => setOffset(Math.max(0, offset - pageSize))}>Previous</Button>
|
|
137
|
+
<Button variant="outline" size="sm" disabled={rows.length < pageSize || ledger.loading} onClick={() => setOffset(offset + pageSize)}>Next</Button>
|
|
138
|
+
</div>
|
|
139
|
+
</section>
|
|
140
|
+
|
|
141
|
+
<ConfirmSpendDialog open={confirmReinstate} tier="external" title={`Reinstate ${selectedContacts.length} contact${selectedContacts.length === 1 ? "" : "s"}`}
|
|
142
|
+
description={(
|
|
143
|
+
<div className="flex flex-col gap-2">
|
|
144
|
+
<p>Reinstating a contact lifts EVERY suppression on it (all channels), not only the row you ticked. They can be messaged again by sequences, campaigns and newsletters. Contacts who unsubscribed themselves may be refused.</p>
|
|
145
|
+
<Label htmlFor="reinstate-reason">Reason (kept in the audit trail)</Label>
|
|
146
|
+
<Input id="reinstate-reason" value={reinstateReason} onChange={(e) => setReinstateReason(e.target.value)} />
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
recipients={selectedContacts.length} confirmLabel="Reinstate" confirming={reinstate.loading}
|
|
150
|
+
onConfirm={() => void reinstate.mutate(selectedContacts)} onCancel={() => setConfirmReinstate(false)} />
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Create a list, add contacts to it and read the members
|
|
3
|
+
* @when Grouping people for a sequence, campaign or export. A list's `type` is `contacts` or `companies`.
|
|
4
|
+
* The list id comes from the create response; contact ids come from a contact read or search, never a guess.
|
|
5
|
+
* Adding is idempotent: contacts already in the list are reported in `already_member`, ids that do not
|
|
6
|
+
* exist in `not_found`. Contacts owned by other reps can answer 409 CONFLICT_REVIEW_REQUIRED: repeat the
|
|
7
|
+
* call with `conflict_resolution` once a person decided.
|
|
8
|
+
*/
|
|
9
|
+
import { g8, G8Error } from "@graph8/sdk";
|
|
10
|
+
|
|
11
|
+
g8.init({ apiKey: process.env.G8_API_KEY! });
|
|
12
|
+
|
|
13
|
+
export async function buildList(title: string, contactIds: number[]) {
|
|
14
|
+
const list = await g8.lists.create(title, "contacts");
|
|
15
|
+
try {
|
|
16
|
+
const res = await g8.api.lists.addContactsToList({ path: { list_id: list.id }, body: { contact_ids: contactIds } });
|
|
17
|
+
return { listId: list.id, result: res.data };
|
|
18
|
+
} catch (err) {
|
|
19
|
+
if (err instanceof G8Error && err.status === 409) {
|
|
20
|
+
// detail = { code: "CONFLICT_REVIEW_REQUIRED", ready_count, warned: [...], blocked: [...] }: show it to a person.
|
|
21
|
+
return { listId: list.id, conflict: err.detail };
|
|
22
|
+
}
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function members(listId: number) {
|
|
28
|
+
const { data, total } = await g8.lists.contacts(listId, 1, 100);
|
|
29
|
+
return { total, ids: data.map((c) => c.id) };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Removing members does not delete the contacts. */
|
|
33
|
+
export async function removeMembers(listId: number, contactIds: number[]) {
|
|
34
|
+
return g8.lists.removeContacts(listId, contactIds);
|
|
35
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Refresh a dynamic list and check a list's health
|
|
3
|
+
* @when A list is built from filters (dynamic) and must be re-evaluated, or a list looks wrong
|
|
4
|
+
* (stuck importing, member count off). `refresh` re-runs the filters; the integrity check is read-only and
|
|
5
|
+
* reports problems without changing anything.
|
|
6
|
+
*/
|
|
7
|
+
import { g8 } from "@graph8/sdk";
|
|
8
|
+
|
|
9
|
+
g8.init({ apiKey: process.env.G8_API_KEY! });
|
|
10
|
+
|
|
11
|
+
export async function refreshDynamic(listId: number) {
|
|
12
|
+
const res = await g8.api.lists.refreshDynamicList({ path: { audience_id: listId } });
|
|
13
|
+
return res.data;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function checkHealth(listId: number) {
|
|
17
|
+
const check = await g8.api.lists.checkListIntegrity({ path: { list_id: listId } });
|
|
18
|
+
const all = await g8.lists.list(1, 50);
|
|
19
|
+
const list = all.data.find((l) => l.id === listId);
|
|
20
|
+
return { integrity: check.data, memberCount: list?.contact_count ?? null };
|
|
21
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Suppress contacts and reinstate them later
|
|
3
|
+
* @when Honouring an opt-out, a bounce or a "do not call" request. Suppression is respected by every
|
|
4
|
+
* sequence, campaign and newsletter. Reinstating lets outreach resume, so ask a human first. The ledger
|
|
5
|
+
* holds contact ids only; resolve names with `g8.contacts.get`.
|
|
6
|
+
*/
|
|
7
|
+
import { g8 } from "@graph8/sdk";
|
|
8
|
+
|
|
9
|
+
g8.init({ apiKey: process.env.G8_API_KEY! });
|
|
10
|
+
|
|
11
|
+
export async function suppress(contactIds: number[], channel: "all" | "email" | "phone" | "linkedin" = "all", reason?: string) {
|
|
12
|
+
const res = await g8.api.suppressions.bulkAddSuppressions({ body: { contact_ids: contactIds, channel, reason } });
|
|
13
|
+
return res.data; // { suppressed, total_requested, channel, not_found }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function isSuppressed(contactId: number) {
|
|
17
|
+
const res = await g8.api.suppressions.checkContactSuppression({ path: { contact_id: contactId } });
|
|
18
|
+
return res.data; // { is_suppressed, active_channels, suppressions }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function listEmailSuppressions() {
|
|
22
|
+
const res = await g8.api.suppressions.listSuppressions({ query: { channel: "email", limit: 200 } });
|
|
23
|
+
return res.data;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Only after a person approved: contacts who unsubscribed themselves may be refused. */
|
|
27
|
+
export async function reinstate(contactIds: number[], reason: string) {
|
|
28
|
+
const res = await g8.api.suppressions.reinstateSuppressions({ body: { contact_ids: contactIds, reason } });
|
|
29
|
+
return res.data; // { reinstated, skipped: [{ contact_id, reason }], not_found }
|
|
30
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# mailboxes
|
|
2
|
+
|
|
3
|
+
Everything about the inboxes you send from: list them, connect or buy new ones, buy the domains behind them,
|
|
4
|
+
and check deliverability and warmup before a send. The Apollo / Outreach "mailboxes" settings screen.
|
|
5
|
+
|
|
6
|
+
Depends on: `foundation`. Install: `npx @graph8/sdk add mailboxes`.
|
|
7
|
+
|
|
8
|
+
## Components
|
|
9
|
+
|
|
10
|
+
| Component | What it does | Tiers |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| `MailboxList` | Mailboxes with connection status, warmup and daily limit. Test connection; archive and delete are confirmed. | read, write, destructive |
|
|
13
|
+
| `ConnectMailbox` | Three modes: IMAP/SMTP form (create then test), Google/Microsoft (a link to the app, no API call), and buy mailboxes on a connected domain. | read, write, billable |
|
|
14
|
+
| `DomainPurchase` | Suggest names, check availability, buy domains, connect them, check DNS propagation. | read, billable |
|
|
15
|
+
| `WarmupHealth` | "Safe to send?" verdict, fleet counts, degraded mailboxes; for one mailbox, warmup analytics, start/stop and a placement test. | read, write, billable |
|
|
16
|
+
|
|
17
|
+
Every billable or destructive action opens `ConfirmSpendDialog` first, with a credit estimate where one
|
|
18
|
+
exists. There is no credit-balance read, so the numbers are estimates (about 2,500 credits held per domain,
|
|
19
|
+
750 per mailbox, 20 per placement test).
|
|
20
|
+
|
|
21
|
+
## The flow, in order
|
|
22
|
+
|
|
23
|
+
1. **Connect what you own.** `ConnectMailbox` IMAP tab -> `createMailbox` (returns `id`) -> `testMailboxConnection`
|
|
24
|
+
with that `id`. Credentials are write-only.
|
|
25
|
+
2. **Google or Microsoft (the human).** OAuth consent only runs in the graph8 app. `ConnectMailbox` shows a button
|
|
26
|
+
to `appUrl`; the mailbox then appears in `MailboxList`.
|
|
27
|
+
3. **Buy infrastructure** (needs approval): `DomainPurchase` -> `generateDomainNames` -> `checkDomainAvailability`
|
|
28
|
+
-> `checkoutPurchaseDomains` (sent with an Idempotency-Key) -> `listProviderDomains` until the domain
|
|
29
|
+
shows -> `connectProvider` -> `checkDomainPropagation`. Then `ConnectMailbox` Buy tab ->
|
|
30
|
+
`getSendingPricing` -> `checkoutPurchaseMailboxes` for that domain's `id`.
|
|
31
|
+
4. **Warm up.** `WarmupHealth mailboxId={id}` -> `getMailboxWarmupAnalytics`, `enableWarmup` / `disableWarmup`.
|
|
32
|
+
5. **Gate every launch.** `WarmupHealth` (no `mailboxId`) shows `getSafeToSend`, `getFleetSummary` and
|
|
33
|
+
`listDegradedMailboxes`. A `false` verdict means pause instead of pushing more volume.
|
|
34
|
+
|
|
35
|
+
## Prerequisites
|
|
36
|
+
|
|
37
|
+
- Google/Microsoft mailboxes connected by the person in the graph8 app (`where: "app"`).
|
|
38
|
+
- A plan with credits if buying (`where: "api"`). A 402 on purchase means the credit hold could not clear;
|
|
39
|
+
a 422 means the plan cannot fund purchases.
|
|
40
|
+
|
|
41
|
+
## Defaults (do not ask the human)
|
|
42
|
+
|
|
43
|
+
- Daily limit 40 for a new IMAP mailbox; warmup on for bought mailboxes; domains via `registrar`, mailboxes via `managed`.
|
|
44
|
+
- **Ask the human**: which domains and usernames to buy, and the registrant contact. Never buy without approval.
|
|
45
|
+
|
|
46
|
+
## Operations
|
|
47
|
+
|
|
48
|
+
| Operation | Method and path | Tier | Used by |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `list_mailboxes_mailboxes_get` | GET /mailboxes | read | MailboxList |
|
|
51
|
+
| `test_mailbox_connection_mailboxes__mailbox_id__test_connection_post` | POST /mailboxes/{mailbox_id}/test-connection | write | MailboxList, ConnectMailbox |
|
|
52
|
+
| `archive_mailbox_mailboxes__mailbox_id__archive_post` | POST /mailboxes/{mailbox_id}/archive | destructive | MailboxList |
|
|
53
|
+
| `delete_mailbox_mailboxes__mailbox_id__delete` | DELETE /mailboxes/{mailbox_id} | destructive | MailboxList |
|
|
54
|
+
| `create_mailbox_mailboxes_post` | POST /mailboxes | write | ConnectMailbox |
|
|
55
|
+
| `list_provider_domains_provider_domains_get` | GET /provider-domains | read | ConnectMailbox, DomainPurchase |
|
|
56
|
+
| `get_sending_pricing_provider_domains_pricing_get` | GET /provider-domains/pricing | read | ConnectMailbox |
|
|
57
|
+
| `checkout_purchase_mailboxes_provider_domains__domain_id__checkout_mailboxes_post` | POST /provider-domains/{domain_id}/checkout-mailboxes | billable | ConnectMailbox |
|
|
58
|
+
| `generate_domain_names_provider_domains_generate_domains_post` | POST /provider-domains/generate-domains | read | DomainPurchase |
|
|
59
|
+
| `check_domain_availability_provider_domains_check_availability_post` | POST /provider-domains/check-availability | billable | DomainPurchase |
|
|
60
|
+
| `checkout_purchase_domains_provider_domains_checkout_purchase_post` | POST /provider-domains/checkout-purchase | billable | DomainPurchase |
|
|
61
|
+
| `connect_provider_provider_domains__domain_id__connect_provider_post` | POST /provider-domains/{domain_id}/connect-provider | billable | DomainPurchase |
|
|
62
|
+
| `check_domain_propagation_provider_domains__domain_id__check_propagation_post` | POST /provider-domains/{domain_id}/check-propagation | billable | DomainPurchase |
|
|
63
|
+
| `get_fleet_summary_deliverability_fleet_summary_get` | GET /deliverability/fleet-summary | read | WarmupHealth |
|
|
64
|
+
| `get_safe_to_send_deliverability_safe_to_send_get` | GET /deliverability/safe-to-send | read | WarmupHealth |
|
|
65
|
+
| `list_degraded_mailboxes_deliverability_degraded_get` | GET /deliverability/degraded | read | WarmupHealth |
|
|
66
|
+
| `get_mailbox_warmup_analytics_mailboxes__mailbox_id__warmup_get` | GET /mailboxes/{mailbox_id}/warmup | read | WarmupHealth |
|
|
67
|
+
| `enable_warmup_warmup__mailbox_id__enable_post` | POST /warmup/{mailbox_id}/enable | write | WarmupHealth |
|
|
68
|
+
| `disable_warmup_warmup__mailbox_id__disable_post` | POST /warmup/{mailbox_id}/disable | write | WarmupHealth |
|
|
69
|
+
| `run_mailbox_placement_test_mailboxes__mailbox_id__health_placement_test_post` | POST /mailboxes/{mailbox_id}/health/placement-test | billable | WarmupHealth |
|
|
70
|
+
|
|
71
|
+
Recipes (`recipes/`): `connect-imap-mailbox.ts`, `buy-domain-and-mailboxes.ts`, `is-it-safe-to-send.ts`, `warmup-report.ts`.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../block.schema.json",
|
|
3
|
+
"id": "mailboxes",
|
|
4
|
+
"title": "Mailboxes & deliverability",
|
|
5
|
+
"summary": "List, connect and buy sending mailboxes and domains, and watch warmup and deliverability before you send.",
|
|
6
|
+
"features": [
|
|
7
|
+
"mailbox list with connection test, archive and delete",
|
|
8
|
+
"connect over IMAP/SMTP, link out for Google/Microsoft, buy mailboxes",
|
|
9
|
+
"domain search, availability and purchase",
|
|
10
|
+
"fleet deliverability verdict, degraded mailboxes, warmup analytics, placement test"
|
|
11
|
+
],
|
|
12
|
+
"dependsOn": [
|
|
13
|
+
"foundation"
|
|
14
|
+
],
|
|
15
|
+
"operations": [
|
|
16
|
+
"list_mailboxes_mailboxes_get",
|
|
17
|
+
"test_mailbox_connection_mailboxes__mailbox_id__test_connection_post",
|
|
18
|
+
"archive_mailbox_mailboxes__mailbox_id__archive_post",
|
|
19
|
+
"delete_mailbox_mailboxes__mailbox_id__delete",
|
|
20
|
+
"create_mailbox_mailboxes_post",
|
|
21
|
+
"list_provider_domains_provider_domains_get",
|
|
22
|
+
"get_sending_pricing_provider_domains_pricing_get",
|
|
23
|
+
"checkout_purchase_mailboxes_provider_domains__domain_id__checkout_mailboxes_post",
|
|
24
|
+
"generate_domain_names_provider_domains_generate_domains_post",
|
|
25
|
+
"check_domain_availability_provider_domains_check_availability_post",
|
|
26
|
+
"checkout_purchase_domains_provider_domains_checkout_purchase_post",
|
|
27
|
+
"connect_provider_provider_domains__domain_id__connect_provider_post",
|
|
28
|
+
"check_domain_propagation_provider_domains__domain_id__check_propagation_post",
|
|
29
|
+
"get_fleet_summary_deliverability_fleet_summary_get",
|
|
30
|
+
"get_safe_to_send_deliverability_safe_to_send_get",
|
|
31
|
+
"list_degraded_mailboxes_deliverability_degraded_get",
|
|
32
|
+
"get_mailbox_warmup_analytics_mailboxes__mailbox_id__warmup_get",
|
|
33
|
+
"enable_warmup_warmup__mailbox_id__enable_post",
|
|
34
|
+
"disable_warmup_warmup__mailbox_id__disable_post",
|
|
35
|
+
"run_mailbox_placement_test_mailboxes__mailbox_id__health_placement_test_post"
|
|
36
|
+
],
|
|
37
|
+
"components": [
|
|
38
|
+
{
|
|
39
|
+
"name": "MailboxList",
|
|
40
|
+
"file": "components/mailbox-list.tsx",
|
|
41
|
+
"purpose": "Sending mailboxes with status, warmup and daily limit; test, archive (confirmed) and delete (confirmed).",
|
|
42
|
+
"operations": [
|
|
43
|
+
"list_mailboxes_mailboxes_get",
|
|
44
|
+
"test_mailbox_connection_mailboxes__mailbox_id__test_connection_post",
|
|
45
|
+
"archive_mailbox_mailboxes__mailbox_id__archive_post",
|
|
46
|
+
"delete_mailbox_mailboxes__mailbox_id__delete"
|
|
47
|
+
],
|
|
48
|
+
"tiers": [
|
|
49
|
+
"read",
|
|
50
|
+
"write",
|
|
51
|
+
"destructive"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "ConnectMailbox",
|
|
56
|
+
"file": "components/connect-mailbox.tsx",
|
|
57
|
+
"purpose": "Connect over IMAP/SMTP and test, link to the app for Google/Microsoft, or buy mailboxes on a connected domain (confirmed).",
|
|
58
|
+
"operations": [
|
|
59
|
+
"create_mailbox_mailboxes_post",
|
|
60
|
+
"test_mailbox_connection_mailboxes__mailbox_id__test_connection_post",
|
|
61
|
+
"list_provider_domains_provider_domains_get",
|
|
62
|
+
"get_sending_pricing_provider_domains_pricing_get",
|
|
63
|
+
"checkout_purchase_mailboxes_provider_domains__domain_id__checkout_mailboxes_post"
|
|
64
|
+
],
|
|
65
|
+
"tiers": [
|
|
66
|
+
"read",
|
|
67
|
+
"write",
|
|
68
|
+
"billable"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "DomainPurchase",
|
|
73
|
+
"file": "components/domain-purchase.tsx",
|
|
74
|
+
"purpose": "Suggest, check and buy sending domains (confirmed with a credit estimate), then connect them and check DNS.",
|
|
75
|
+
"operations": [
|
|
76
|
+
"generate_domain_names_provider_domains_generate_domains_post",
|
|
77
|
+
"check_domain_availability_provider_domains_check_availability_post",
|
|
78
|
+
"checkout_purchase_domains_provider_domains_checkout_purchase_post",
|
|
79
|
+
"list_provider_domains_provider_domains_get",
|
|
80
|
+
"connect_provider_provider_domains__domain_id__connect_provider_post",
|
|
81
|
+
"check_domain_propagation_provider_domains__domain_id__check_propagation_post"
|
|
82
|
+
],
|
|
83
|
+
"tiers": [
|
|
84
|
+
"read",
|
|
85
|
+
"billable"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "WarmupHealth",
|
|
90
|
+
"file": "components/warmup-health.tsx",
|
|
91
|
+
"purpose": "Safe-to-send verdict, fleet counts, degraded mailboxes, per-mailbox warmup analytics with start/stop and a confirmed placement test.",
|
|
92
|
+
"operations": [
|
|
93
|
+
"get_fleet_summary_deliverability_fleet_summary_get",
|
|
94
|
+
"get_safe_to_send_deliverability_safe_to_send_get",
|
|
95
|
+
"list_degraded_mailboxes_deliverability_degraded_get",
|
|
96
|
+
"get_mailbox_warmup_analytics_mailboxes__mailbox_id__warmup_get",
|
|
97
|
+
"enable_warmup_warmup__mailbox_id__enable_post",
|
|
98
|
+
"disable_warmup_warmup__mailbox_id__disable_post",
|
|
99
|
+
"run_mailbox_placement_test_mailboxes__mailbox_id__health_placement_test_post"
|
|
100
|
+
],
|
|
101
|
+
"tiers": [
|
|
102
|
+
"read",
|
|
103
|
+
"write",
|
|
104
|
+
"billable"
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
"hooks": [],
|
|
109
|
+
"lib": [
|
|
110
|
+
{
|
|
111
|
+
"file": "lib/mailboxes-utils.ts",
|
|
112
|
+
"purpose": "Mailbox and domain row types, credit estimate constants, status colors and idempotency-key helper."
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
"recipes": [
|
|
116
|
+
{
|
|
117
|
+
"file": "recipes/connect-imap-mailbox.ts",
|
|
118
|
+
"title": "Connect a mailbox you own over IMAP/SMTP and prove it works",
|
|
119
|
+
"when": "Adding an existing mailbox (not Google or Microsoft)."
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"file": "recipes/buy-domain-and-mailboxes.ts",
|
|
123
|
+
"title": "Buy a sending domain, connect it, then buy mailboxes on it",
|
|
124
|
+
"when": "Provisioning sending infrastructure; spends real credits, needs human approval."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"file": "recipes/is-it-safe-to-send.ts",
|
|
128
|
+
"title": "Check deliverability before a send",
|
|
129
|
+
"when": "Right before launching a sequence or campaign."
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"file": "recipes/warmup-report.ts",
|
|
133
|
+
"title": "Report warmup progress for every mailbox",
|
|
134
|
+
"when": "A daily report or before moving a mailbox into a sequence."
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
"prerequisites": [
|
|
138
|
+
{
|
|
139
|
+
"id": "oauth-mailboxes-in-app",
|
|
140
|
+
"description": "Google and Microsoft mailboxes are connected by the person in the graph8 app. The API cannot run the OAuth consent screen.",
|
|
141
|
+
"where": "app",
|
|
142
|
+
"verify": "The mailbox appears in GET /mailboxes with connection_status active."
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"id": "credits-for-purchases",
|
|
146
|
+
"description": "The workspace has an active plan with credits if you buy domains or mailboxes. There is no balance read, so the UI shows estimates.",
|
|
147
|
+
"where": "api",
|
|
148
|
+
"verify": "A purchase returns 202; a 402 means insufficient credits and a 422 means the plan cannot fund purchases."
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"scopes": [
|
|
152
|
+
"campaigns:delete",
|
|
153
|
+
"campaigns:read",
|
|
154
|
+
"campaigns:run",
|
|
155
|
+
"campaigns:write"
|
|
156
|
+
],
|
|
157
|
+
"defaults": [
|
|
158
|
+
{
|
|
159
|
+
"decision": "Daily send limit for a new IMAP mailbox",
|
|
160
|
+
"default": "40",
|
|
161
|
+
"askHuman": false
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"decision": "Start warmup on newly bought mailboxes",
|
|
165
|
+
"default": "Yes",
|
|
166
|
+
"askHuman": false
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"decision": "Which domain names and mailbox usernames to buy",
|
|
170
|
+
"default": "None: never buy without approval",
|
|
171
|
+
"askHuman": true
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"decision": "Registrant contact for domain purchases",
|
|
175
|
+
"default": "None",
|
|
176
|
+
"askHuman": true
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"decision": "Domain provider for purchases",
|
|
180
|
+
"default": "registrar (domains) and managed (mailboxes)",
|
|
181
|
+
"askHuman": false
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
"peerDependencies": {
|
|
185
|
+
"react": ">=18"
|
|
186
|
+
}
|
|
187
|
+
}
|