SMTP/IMAP passwords on the validation round-trip are now encrypted and
decrypted under the organization DEK, carried as OrgID on
EventWorkerEmailValidation. Onboarding requires an organization before
a mailbox can be validated.
This fixes a latent key mismatch: the payload never set UserID, so the
worker decrypted with the zero-UUID platform key while the backend had
encrypted with the user key.
The send pipeline (publisher subject/body encryption, the S3 emsg blob,
and the worker-side decrypt) now keys off models.SendEmail.OrgID
instead of UserID. EmailMessage loses its UserID field entirely:
emailSender.Send derives the cipher identity from the email account's
OrganizationID and refuses to send for an account without one.
This also fixes two latent bugs. Emails sent through user_email_task
never set UserID, so they were silently encrypted under the zero-UUID
platform key. campaign_task's discarded Encrypt() pair is replaced with
an explicit DEK warm that fails fast when KMS is unavailable.
Support chained automation execution from native actions, guard automation recursion depth, carry idempotency context through campaign-launched automations, allow team task assignment in backend action execution, and reject deletes while campaign steps still reference an automation.
Adds the persisted notification feed, user preferences, realtime notification events, and advanced-service notification hooks for replies and deliverability health events.
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 the conversation-label join table and expose thread label fields on the Unibox response models so inbox rows and overview data can carry category metadata.
Add campaign lead ordering repository methods for manual send order and tighten strict ESP matching so SMTP/IMAP is only a fallback outside strict mode.
Evaluate branch conditions relative to the previous send, defer while windows are still undecided, and let the routed pair finder handle deleted targets and loops at schedule time.
Add an organization credit ledger with idempotent consumption, plan credit grants, Anthropic/OpenAI writing providers, and a gated generation endpoint for campaign writing assistance.
Scope campaign A/B variants to individual sequence steps and select step variants deterministically per contact and step. Keep campaign-level variants on the existing assignment flow.
Store per-step branching conditions, validate branch targets, and select campaign contact steps by following the flow graph. Preserve linear progression for steps without branch conditions.
Store per-day schedule windows on campaigns and validate updates before persistence. Use the schedule windows in campaign scheduling, including legacy day/time derivation for existing campaigns.
Treat warmup content as a shared library in settings and admin tables, and realign the conversation pick index with segment-based selection.
Add generation job details and fix warmup placement displays so backend percentages and provider counts render correctly.
Remove the worker-side dwell timer now that delayed warmup engagement is scheduled durably by the consumer.
Keep DelaySeconds only for wire compatibility and run received warmup action batches immediately on the worker.
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.
Wire warmup health webhooks in the consumer and log skipped warmup actions when a recipient mailbox has no assigned worker.
Run foldering and spam rescue immediately while keeping only low-stakes engagement signals behind the dwell delay.