Files
warmbly/internal/api/handler/handler.go
Matthew Meszaros 44da2f464f feat: add the control plane for mailbox sync fair use, so a mailbox syncs under an operator-editable policy and its progress survives worker replacement: a sync section on the instance settings document (backfill window in days, backfill cap per mailbox, daily new-mail budget per mailbox and per organization, each clamped on read and write with compiled defaults in constants.go), models.SyncPolicy and models.SyncState with a provider-shaped jsonb SyncCursor, a new email_sync_state table plus an index on tasks.message_id that the reply lookup was scanning sequentially without, an EmailSyncStateRepository whose Put also stamps email_accounts.last_synced_at which nothing had written since the baseline so every admin and dashboard Last synced surface read NULL, an OrganizationID and Sync block on the ADD_EMAIL payload resolved by the loader from instance settings and the saved state and, for IMAP, the saved unibox_mailboxes folder cursors that the loader had never populated so every worker restart re-walked every folder from scratch, a SYNC_STATE consumer handler that persists the relay and publishes ACCOUNT_SYNC_STATE plus a warning when the import completes or fair use flips, an internal own-conversation endpoint the worker's priority lane asks whether a new message replies to a campaign task, a mapped message or a stored thread, GET /emails/:id/sync for the dashboard, and SYNC_FLOOD and SYNC_FAIR_USE mail error codes with user copy for the two patterns that deactivate a mailbox
2026-08-18 08:43:20 -07:00

310 lines
13 KiB
Go

package handler
import (
"github.com/warmbly/warmbly/internal/app/admin"
"github.com/warmbly/warmbly/internal/app/adminoutreach"
"github.com/warmbly/warmbly/internal/app/advanced"
"github.com/warmbly/warmbly/internal/app/advisor"
"github.com/warmbly/warmbly/internal/app/aiagent"
"github.com/warmbly/warmbly/internal/app/aitools"
"github.com/warmbly/warmbly/internal/app/analytics"
"github.com/warmbly/warmbly/internal/app/apikey"
"github.com/warmbly/warmbly/internal/app/audit"
"github.com/warmbly/warmbly/internal/app/auth"
"github.com/warmbly/warmbly/internal/app/behavior"
"github.com/warmbly/warmbly/internal/app/bootstrap"
"github.com/warmbly/warmbly/internal/app/campaign"
"github.com/warmbly/warmbly/internal/app/compose"
"github.com/warmbly/warmbly/internal/app/contact"
"github.com/warmbly/warmbly/internal/app/credits"
"github.com/warmbly/warmbly/internal/app/crm"
"github.com/warmbly/warmbly/internal/app/dangerzone"
"github.com/warmbly/warmbly/internal/app/discount"
"github.com/warmbly/warmbly/internal/app/email"
"github.com/warmbly/warmbly/internal/app/emailsend"
emailverifyapp "github.com/warmbly/warmbly/internal/app/emailverify"
"github.com/warmbly/warmbly/internal/app/feature"
"github.com/warmbly/warmbly/internal/app/group"
"github.com/warmbly/warmbly/internal/app/instancecheck"
"github.com/warmbly/warmbly/internal/app/instanceconfig"
"github.com/warmbly/warmbly/internal/app/instancesettings"
"github.com/warmbly/warmbly/internal/app/integration"
"github.com/warmbly/warmbly/internal/app/leadsync"
"github.com/warmbly/warmbly/internal/app/mcp"
"github.com/warmbly/warmbly/internal/app/notification"
"github.com/warmbly/warmbly/internal/app/oauth"
"github.com/warmbly/warmbly/internal/app/organization"
"github.com/warmbly/warmbly/internal/app/orgtransfer"
"github.com/warmbly/warmbly/internal/app/passkey"
"github.com/warmbly/warmbly/internal/app/placement"
"github.com/warmbly/warmbly/internal/app/ratelimit"
"github.com/warmbly/warmbly/internal/app/referral"
"github.com/warmbly/warmbly/internal/app/releases"
"github.com/warmbly/warmbly/internal/app/research"
"github.com/warmbly/warmbly/internal/app/sequence"
"github.com/warmbly/warmbly/internal/app/skills"
"github.com/warmbly/warmbly/internal/app/socket"
"github.com/warmbly/warmbly/internal/app/stripe"
"github.com/warmbly/warmbly/internal/app/subscription"
"github.com/warmbly/warmbly/internal/app/sysstatus"
"github.com/warmbly/warmbly/internal/app/team"
"github.com/warmbly/warmbly/internal/app/template"
"github.com/warmbly/warmbly/internal/app/token"
"github.com/warmbly/warmbly/internal/app/trial"
"github.com/warmbly/warmbly/internal/app/twofa"
"github.com/warmbly/warmbly/internal/app/tz"
"github.com/warmbly/warmbly/internal/app/unibox"
"github.com/warmbly/warmbly/internal/app/user"
"github.com/warmbly/warmbly/internal/app/warmup"
"github.com/warmbly/warmbly/internal/app/warmupcontent"
"github.com/warmbly/warmbly/internal/app/webhook"
"github.com/warmbly/warmbly/internal/app/worker"
"github.com/warmbly/warmbly/internal/app/worker_orchestrator"
"github.com/warmbly/warmbly/internal/pkg/generation"
"github.com/warmbly/warmbly/internal/infrastructure/encryptedkeys"
"github.com/warmbly/warmbly/internal/infrastructure/pubsub"
"github.com/warmbly/warmbly/internal/infrastructure/storage"
"github.com/warmbly/warmbly/internal/models"
"github.com/warmbly/warmbly/internal/notify"
"github.com/warmbly/warmbly/internal/repository"
"github.com/warmbly/warmbly/internal/tasks"
)
type Handler struct {
AuthService auth.AuthService
TokenService token.TokenService
PasskeyService passkey.Service
// Native-app social sign-in discovery (GET /auth/providers).
ExternalAuthProviders models.ExternalAuthProviders
// Deployment facts served by GET /auth/config so the login screen renders
// what this backend can actually do instead of guessing.
GoogleWebSignIn bool
AppleWebSignIn bool
OIDCEnabled bool
MailDelivers bool
PasskeysUsable bool
MailTransport string
// MailTransportRef backs the admin mail diagnostics, which need Preflight
// and so cannot go through the EmailNotificationService interface.
MailTransportRef *notify.Transport
// BootstrapService backs the first-run setup page.
BootstrapService *bootstrap.Service
UserService user.UserService
EmailService email.EmailService
CampaignService campaign.CampaignService
ContactService contact.ContactService
SequenceService sequence.SequenceService
UniboxService unibox.UniboxService
FolderService group.GroupService
TagService group.GroupService
CategoryService group.GroupService
TzService tz.TzService
SocketService socket.SocketService
TasksService tasks.TasksService
NotificationService notification.Service
TwoFAService twofa.Service
// New services
APIKeyService apikey.APIKeyService
AnalyticsService analytics.AnalyticsService
AuditService audit.AuditService
RateLimitService ratelimit.RateLimitService
// Subscription & billing
SubscriptionService subscription.SubscriptionService
StripeService stripe.StripeService
DiscountService discount.DiscountService
ReferralService referral.Service
// Trial & feature gates
TrialService trial.TrialService
FeatureGateService feature.FeatureGateService
WorkerAssignmentService worker.WorkerAssignmentService
// Organization & IAM
OrganizationService organization.OrganizationService
// CRM
CRMService crm.CRMService
// Teams
TeamService team.TeamService
// Email send & templates
TemplateService template.TemplateService
EmailSendService emailsend.EmailSendService
// Compose mailbox picker: scores the org's mailboxes for a recipient
// (affinity, budget, auth health) and backs auto selection.
ComposeService compose.Service
// Admin
AdminService admin.AdminService
AdminOutreachService adminoutreach.Service
// Worker orchestration (SSH-driven lifecycle for admin-managed workers)
WorkerOrchestrator *worker_orchestrator.Orchestrator
WorkerRepo repository.WorkerRepository
CredentialsRepo repository.CredentialsRepository
ReleasesService *releases.Service
// Notifications
EmailNotificationService notify.EmailNotificationService
// Advanced outreach controls
AdvancedService advanced.Service
// Pre-send email verification (control-plane SMTP RCPT probe / pluggable
// paid backend). Drops hard-bouncing addresses before a worker sends.
EmailVerifyService emailverifyapp.Service
// Per-mailbox human sending behaviour: the ranges a mailbox rolls its
// workday from, and the plan it rolled for today. Nil disables the
// behaviour endpoints (the schedulers then run every mailbox on its fixed
// cap and gap).
BehaviorService behavior.Service
// Warmup health
WarmupService warmup.Service
// Warmup routing rules — customer-defined preferences for premium-pool
// partner selection (e.g. Gmail recipients from Google Workspace senders).
WarmupRoutingRepo repository.WarmupRoutingRepository
// Warmup content bank + offline AI generator (admin control/visibility).
WarmupContentRepo repository.WarmupContentRepository
WarmupContentService warmupcontent.Service
// AI writing assistant + credit ledger.
CreditService credits.CreditService
WritingGenerator generation.WritingGenerator
// AI provider layer (RunAgent tool-loop backend) + pluggable web search,
// shared by the dashboard agent, research, automation AI nodes, and the
// inbox agent. Nil when no LLM provider is configured.
AIProvider generation.Provider
AISearch generation.SearchClient
// AITools is the shared tool registry the dashboard agent and MCP server
// run on. Handlers bound to the invoking user's permissions.
AITools *aitools.Registry
// AIAgentService orchestrates the dashboard agent (sessions, SSE runs,
// approvals, per-iteration credits). Nil when no LLM provider is configured.
AIAgentService aiagent.Service
// ResearchService runs the AI contact-research agent (sync + batch).
ResearchService research.Service
// SkillsService manages org AI skills (playbooks) and injects them into AI
// prompts.
SkillsService skills.Service
// MCPService manages org-connected MCP servers (external tools).
MCPService mcp.Service
// AIDraftRepo stores inbox-agent reply drafts awaiting human review (M10).
// The draft is created in the consumer; these list/approve/discard handlers
// read + resolve it. Nil disables the review endpoints.
AIDraftRepo repository.AIDraftRepository
// Seed inbox-placement testing.
PlacementRepo repository.PlacementRepository
PlacementService placement.Service
// Advisor: continuously-evaluated recommendations about deliverability,
// mailbox config, warmup, campaign performance, copy, and list hygiene.
// The repository is held alongside the service so a read can cheaply check
// how stale the findings are before deciding to re-evaluate inline.
AdvisorService advisor.Service
AdvisorRepository repository.AdvisorRepository
// Customer-facing webhooks (subscribe → HMAC-signed delivery).
WebhookService webhook.Service
// Third-party integrations (Calendly, Cal.com, DMARC, Postmaster,
// SNDS, Cloudflare, GoDaddy, Namecheap, Google Sheets).
IntegrationService integration.Service
ContactRepo repository.ContactRepository
// OAuth 2.1 authorization server (third-party app registration + the
// authorization-code-with-PKCE flow + bearer-token validation).
OAuthService *oauth.Service
// Realtime publisher for handler paths that emit live dashboard events
// directly (inbound meeting webhooks have no service layer of their own).
// nil-safe: realtime is a nicety, not a requirement.
StreamingPublisher *pubsub.StreamingPublisher
// On-demand Google Sheets -> leads sync. Reuses the google_sheets OAuth
// connection's token to read sheets and the contact import path to upsert.
LeadSyncService leadsync.Service
// Public websocket URL used by frontend clients
WebsocketURI string
// Object storage for user-uploaded artifacts (avatars, etc.).
Storage storage.Store
// Encrypted-DEK store. Served to workers over HTTPS at
// /api/v1/internal/dek/:userID so workers don't need direct Postgres
// access. Backend processes use Postgres directly; workers use the
// HTTP-proxy implementation.
EncryptedKeys encryptedkeys.Store
// Worker messageId -> internal email map, served to workers over HTTPS at
// /api/v1/internal/email-message-map for the same no-direct-Postgres reason
// as EncryptedKeys. Backed by Postgres in the backend.
EmailMessageMap repository.EmailMessageMapRepository
// Mailbox sync state, read by the dashboard (GET /emails/:id/sync) and by
// the worker's priority lane over /api/v1/internal/sync/own-conversation.
EmailSyncState repository.EmailSyncStateRepository
// Click-link store, served to the tracking service over HTTPS at
// /api/v1/internal/tracked-links/:id (same no-direct-Postgres rule).
TrackedLinks repository.TrackedLinkRepository
// Direct repositories used by handlers that don't yet have a
// service layer (avatars, etc.). Keep narrow and add a service
// only when business logic accumulates.
UserRepo repository.UserRepository
OrgRepo repository.OrganizationRepository
AttachmentRepo repository.AttachmentRepository
StorageBackendRepo repository.StorageBackendRepository
CloudCredentialRepo repository.CloudCredentialRepository
ProvisioningTemplateRepo repository.ProvisioningTemplateRepository
ProvisioningJobRepo repository.ProvisioningJobRepository
ProvisioningPolicyRepo repository.ProvisioningPolicyRepository
// Danger zone (delayed deletions for orgs & user accounts)
DangerZoneService dangerzone.Service
// Workspace archives: export an organization to a portable file and import
// one back, for moving between instances. Nil disables the endpoints.
OrgTransferService orgtransfer.Service
// Infrastructure liveness probes for the admin System Status page.
// Wired in cmd/backend/main.go where the concrete clients live.
SystemChecker *sysstatus.Checker
// Operator visibility (admin panel, Instance section).
//
// InstanceRuntime carries the facts only boot knows (the resolved CORS
// list, the WebAuthn RP, the OIDC redirect, the auth policy) so the
// configuration page shows what the process actually derived. Nil is safe:
// every resolver falls back to the environment.
InstanceRuntime *instanceconfig.Runtime
// InstanceChecks is the setup and health registry. Nil falls back to the
// environment-only checks, so the page is never blank.
InstanceChecks *instancecheck.Registry
// InstanceSettings is the database-backed settings tier. It holds only
// keys no environment variable owns.
InstanceSettings instancesettings.Service
}