Replace the client-side, 100-row-capped deals board as the default view
with a server-driven cross-pipeline table so totals stop lying at scale.
- POST /crm/deals/search: faceted filter body (query, status, pipeline,
stage, owner, campaign, value range, close-date range), whitelisted
sort, offset pagination, and Contact/Stage/campaign hydrated via JOIN.
- POST /crm/deals/summary: COUNT + SUM(value) per status and per stage
over the same filter, with a mixed-currency guard, so header and board
column totals are true server aggregates.
- DealsTable: cross-pipeline default view (infinite scroll, server
filters/sort, honest summary stats) with a Table|Board toggle; the
kanban stays as the single-pipeline working view.
- Deal attribution: campaign_id + source_mailbox_id columns + indexes
(migration 000022) as groundwork for revenue lineage.
Add read and replace endpoints for conversation labels, wire service methods, and route sender/category filtering through the thread-collapsed search path.
Add an organization credit ledger with idempotent consumption, plan credit grants, Anthropic/OpenAI writing providers, and a gated generation endpoint for campaign writing assistance.
Add OpenAI Batch API support for warmup content generation, including job metadata, polling, cancellation, and completed-batch ingestion.
Share the generation prompt between sync and batch modes and humanize/lint generated threads before storing them in the warmup content library.
Add an advisory content score endpoint that reuses warmup linting heuristics for campaign templates.
Surface the score in the sequence editor so users can check subject and body deliverability before sending.
Add an email auth-check endpoint and dashboard panel for SPF, DKIM, and DMARC validation.
Expose warmup content segment editing on mailboxes so segment-aware warmup content can be selected intentionally.
Add warmup content generation and admin review surfaces, plus mailbox warmup appeal/status APIs.
Track warmup engagement and tampering signals so unsafe mailboxes can be handled by the warmup flow.
Reserve dedicated worker allocation for the control plane, auto-promote spare capacity when needed, and keep risky or quarantined mailboxes off clean shared workers.
Add request and mail-delivery timeouts around auth flows so login requests cannot hang indefinitely when notification delivery stalls.
Allow the local admin dev origin through default CORS and update context-aware lint fixes so the repository lint gate passes.
Resolve the main-branch conflict in realtime event publishing by keeping both warmup account-health and audit-created events. Renumber the warmup migrations after the current main migration tail.
Make the warmup task reconciler re-check account state and org warmup access before scheduling replacement tasks. Remove pool membership for accounts whose org can no longer use warmup to avoid repeated no-access task churn.
Add authenticated session listing and revocation APIs, track the auth provider on sessions, and expose active session controls in account security settings.
Adds OAuth-backed integration connection management across the API, repository, event dispatch, migrations, docs, and dashboard UI.
Includes realtime invalidation and small dashboard type compatibility fixes needed for the web typecheck gate.
Move audit logs into org-scoped Postgres storage, wire audit events across backend handlers, and surface actor details in the dashboard activity log.
Add realtime audit invalidation and retention pruning so the trail stays current and bounded.
Add passkey enrollment and login wiring, including a Safari-safe explicit login path that prefetches the WebAuthn challenge before the click and calls the credential ceremony immediately from the user gesture.
Add a thread_id filter for scheduled Unibox sends and expose a per-thread hook for the dashboard.
Render queued sends inline in ThreadView with cancellation, refreshing the thread, scheduled list, and overview caches after cancel.
Adds GET /admin/mailboxes — paginated platform-wide mailbox list that
joins email_accounts → users → organizations so the table answers
"whose mailbox is this and where does it live" without N+1 fetches.
Search covers mailbox email / owner email / org name; status filter
defaults to active so the active surface shows first ("inactive" /
"all" both available). Provider filter speeds up "show me every Gmail
mailbox" investigations. Cursor pagination matches the rest of the
admin lists.
Frontend page surfaces warmup-on/off, send budget, and last-sync time
with red-when-never / amber-when-stale-over-24h tone so an
investigator can spot dead mailboxes fast. Mailbox email links into
the owning user's detail page; org name links into the workspace
admin so the pivot path stays one click in either direction.
Gated on AdminPermViewUsers since mailbox triage is tightly coupled to
user/org context today; a dedicated bit can be carved later if
mailbox-specific actions land.
Adds a dedicated admin path for sending platform email — distinct from
the campaign emailsend service (which sends through customer mailboxes)
so the two abuse surfaces never share code paths.
Schema (000047) adds admin_outreach_messages: every send is recorded
with sent_by, the resolved to_email, the optional reply_to, subject,
body, and a queued → sent/failed status. Failed sends keep their error
column populated for the audit log.
Extends notify.EmailNotificationService with SendOutreach so both
backends (SES + SMTP) support custom Reply-To: SES via the native
ReplyToAddresses field, SMTP via a forged Reply-To header. The
existing transactional Send() remains unchanged so no other caller is
affected.
Service (internal/app/adminoutreach) resolves recipients three ways:
to_email (raw address), to_user_id (sends to the user's account email),
or to_org_id (sends to the workspace owner). Persist-then-send-then-
mark ensures the audit row exists even if the mailer hangs, and
mark-failed captures the error string verbatim.
Routes:
POST /admin/outreach manage_organizations
GET /admin/outreach view_organizations
Admin UI: composer with recipient mode picker (email / user_id / org_id),
configurable Reply-To (defaults to support@warmbly.com so customers can
actually reply), subject + HTML body editor, and an outreach log below
showing the last 50 sends with status badges and error details. Sidebar
entry under Accounts (Send icon).
Wire the customer self-serve path for asking "please give me more
mailboxes / campaigns / contacts." Migration 000046 adds
limit_increase_requests with a partial unique index ensuring only one
pending request per (org, field) so the queue can't be spammed, plus
a CHECK requiring requested > current_effective so no-op rows never
reach an admin.
Service layer:
- SubmitLimitIncreaseRequest validates membership, rejects unknown
fields, snapshots the user's current effective limit at submission
time so the queue row carries the context the admin needs.
- CancelLimitRequest lets the original submitter walk back a pending
request; approved/rejected rows are immutable as the audit record.
- ApproveLimitRequest stamps the row and writes the corresponding
column on organization_limit_overrides via SetLimitOverrides —
same write path direct admin overrides use, so granted_by and
notes carry through and the audit log treats both flows uniformly.
- RejectLimitRequest stamps the row with required review notes.
Routes:
POST /v1/organization/:orgId/limit-requests
GET /v1/organization/:orgId/limit-requests
DELETE /v1/limit-requests/:id (submitter only)
GET /admin/limit-requests?status=pending
POST /admin/limit-requests/:id/approve
POST /admin/limit-requests/:id/reject
Admin approval and rejection both fire admin audit log entries with
field + requested + notes so the decision history survives any future
reorg of the request table.
UI (admin queue page + dashboard request form) plus the ToS clause
giving Warmbly the right to refuse any increase land in the next commit.