* feat: give a cold mailbox a rotation lifecycle so a tired one can rest and come back, instead of running at full volume until a hard band trips: send_lifecycle is warming, active, resting or reserve and decides whether cold sender resolution offers the mailbox at all, which is a different axis from risk_band deciding which worker and IP host it, so a resting mailbox is still a clean-band mailbox that keeps its warmup traffic and its reputation; the hourly rebalancer rests a mailbox at throttled and worse but never at watch, since watch is defined as the band that changes nothing a customer can feel and leaving cold rotation is very much something they feel, and a rested mailbox returns only after three clean days so one good hour cannot bounce it back to full volume; reserve is the owner's hold and is never overridden, the default is active so no existing mailbox changes on deploy, and the state never travels in a workspace archive because it is this instance's decision about sending it watched
* feat: stop a query error re-admitting rested mailboxes, make probation measure healthy time, and rotate the candidate window so no mailbox starves: sendLifecycles returned a nil map on failure and an unresolved state reads as active, so one bad query quietly put every resting and reserved mailbox back into cold rotation, and the gate is now applied only when the states were actually read, with the skip logged rather than silent; ReadyToResume measured total time resting, so a mailbox that sat unhealthy for three days resumed on its first healthy tick having served no clean time, and an unhealthy evaluation now restarts the streak; and ordering candidates by send_lifecycle_since put every never-moved mailbox equal-first, so on an install with more than one page of them the same page was re-examined forever, which a checked-at stamp and its index fix
* feat: refuse a launch whose list is known to be largely undeliverable
* feat: count the deliverable audience in SQL instead of subtracting two overlapping totals, surface the unverified advice, and stop a query error skipping the gate silently: a contact can be both suppressed and unsubscribed, so Total minus both removed it twice and inflated every share computed against the remainder, which for a fully overlapping list produced a negative deliverable clamped to zero; the unverified-list branch set a summary and remediation but neither Block nor Warn, and a preflight report only surfaces checks that did not pass, so that advice could never be displayed; and a failed audience query was treated as a pass, which is fail-open on a safety gate without even a log line saying the check did not run
* feat: measure an uploaded list at import time and report what it looks like
* feat: document the import assessment and show it in the wizard
* feat: count only sendable leads as invalid, and stop guessing which column held a malformed address: an invalid lead that was also suppressed sat in the numerator while Deliverable excluded it from the denominator, so a campaign whose sendable list was clean could project above 100% and be refused; every verification count now shares the deliverable predicate, and a row whose MAPPED address will not parse is recorded as malformed directly rather than scanning other cells for an at sign, which could pick up a notes field
* feat: refuse a launch whose list is known to be largely undeliverable
* feat: count the deliverable audience in SQL instead of subtracting two overlapping totals, surface the unverified advice, and stop a query error skipping the gate silently: a contact can be both suppressed and unsubscribed, so Total minus both removed it twice and inflated every share computed against the remainder, which for a fully overlapping list produced a negative deliverable clamped to zero; the unverified-list branch set a summary and remediation but neither Block nor Warn, and a preflight report only surfaces checks that did not pass, so that advice could never be displayed; and a failed audience query was treated as a pass, which is fail-open on a safety gate without even a log line saying the check did not run
* feat: give an organization one fused abuse posture, because every existing control watches a single subject and an actor slightly wrong on several axes sits under all of them: organizations gains risk_state, risk_score, risk_reason and an append-only risk_signals evidence blob, modelled on the warmup participant health machine that already works rather than a second vocabulary for the same idea; restricted cuts per-mailbox cold volume to a quarter and forces the free warmup pool so a risky tenant cannot spend the paid pool's shared reputation, suspended stops sending at the send gate, and watch deliberately changes nothing a customer can feel so evidence accumulates before anything is taken away; an operator's suspension outranks the derived band so a detector clearing cannot release a workspace a human suspended, transitions ride the audit spine to every teammate's dashboard, a banner says which limit is active and why rather than letting volume drop silently, and the posture never travels in a workspace archive since it is one platform's verdict reached from evidence the destination never saw
* feat: make the suspension actually stop sending, and emit the audit transitions the PR claimed: emailsend.SendEmail is only the manual and API path, so campaign and warmup sends went nowhere near the gate and a suspended workspace kept sending on its schedule, while the restricted multiplier floored every mailbox at one a day which quietly turned suspension into a trickle rather than a stop; the campaign scheduler now defers the whole campaign with a reason and the warmup task skips as org-suspended, since warmup is outbound mail from the same domains; separately the band change emitted no audit entry at all despite the entity type and the frontend spine entry both existing, so no banner moved for a teammate and there was no trail of who was restricted when, and only a real transition is logged so a detector re-recording the same finding cannot fill the feed; one of my own live tests also asserted how far out a slot lands, which depends on the hour the suite runs, and now asserts the property it was about
* feat: capture and score a signup's origin instead of discarding it
* feat: document what a signup records, and prove the origin write is on the account path with a seam test rather than only testing the scorer
* chore: drop the em dashes and trim the comments the review flagged
* feat: give an organization one fused abuse posture, because every existing control watches a single subject and an actor slightly wrong on several axes sits under all of them: organizations gains risk_state, risk_score, risk_reason and an append-only risk_signals evidence blob, modelled on the warmup participant health machine that already works rather than a second vocabulary for the same idea; restricted cuts per-mailbox cold volume to a quarter and forces the free warmup pool so a risky tenant cannot spend the paid pool's shared reputation, suspended stops sending at the send gate, and watch deliberately changes nothing a customer can feel so evidence accumulates before anything is taken away; an operator's suspension outranks the derived band so a detector clearing cannot release a workspace a human suspended, transitions ride the audit spine to every teammate's dashboard, a banner says which limit is active and why rather than letting volume drop silently, and the posture never travels in a workspace archive since it is one platform's verdict reached from evidence the destination never saw
* feat: make the suspension actually stop sending, and emit the audit transitions the PR claimed: emailsend.SendEmail is only the manual and API path, so campaign and warmup sends went nowhere near the gate and a suspended workspace kept sending on its schedule, while the restricted multiplier floored every mailbox at one a day which quietly turned suspension into a trickle rather than a stop; the campaign scheduler now defers the whole campaign with a reason and the warmup task skips as org-suspended, since warmup is outbound mail from the same domains; separately the band change emitted no audit entry at all despite the entity type and the frontend spine entry both existing, so no banner moved for a teammate and there was no trail of who was restricted when, and only a real transition is logged so a detector re-recording the same finding cannot fill the feed; one of my own live tests also asserted how far out a slot lands, which depends on the hour the suite runs, and now asserts the property it was about
* chore: drop a test that belongs to the signup-risk change and reached this branch by mistake, where the type it exercises does not exist
* feat: make send-time optimization actually schedule sends, instead of being a documented setting with no caller: advanced.OptimizeSendTime had zero call sites anywhere in the codebase, so an org that enabled send_time_optimization through PATCH /outreach/settings changed nothing about when its campaign mail left, and the API reference said so in a callout; the campaign scheduler now resolves the recipient's timezone from the contact's timezone custom field, then the country-code suffix of its email domain, then the org fallback, and holds the slot until that clock reaches a preferred hour, raising hardFloor so the task handler reschedules rather than sending immediately, the snap is forward-only and never crosses the campaign end date, the default flips to off so no existing workspace silently re-times its sends, and Settings > Sending gives the block its first UI
* feat: stop the recipient-hour gate deferring a send forever, and fix the window parser that silently disabled every campaign schedule: recipientSlot now searches for a moment BOTH calendars accept and yields when they never meet, because raising a hard floor at an hour the sender cannot serve made every tick re-derive it, defer, wake in the sender's window and defer again; separately parseTimeOfDay accepted only the 15:04 layout while start_time, end_time, warmup_start_time and warmup_end_time are Postgres time columns pgx renders as 09:00:00.000000, so every read parsed to 0 and effectiveWindows read that as unconstrained, leaving both the campaign sending window and its day-of-week gate off for every campaign on the legacy fields and pinning warmup to its 08:00-20:00 fallbacks; Settings > Sending also gains the MANAGE_SETTINGS gate a direct visit needs