Warmup content generation

Where warmup conversations come from, how they rotate, generation controls and batch draining.

Warmup mail is a plain-text thread between two pool mailboxes that reads like ordinary correspondence between people who know each other loosely. It never says or hints that it is automated, generated, a test or a warmup. Content comes from two sources that rotate together: a hand-written library of about seventy threads that ships with the application, and an optional bank of AI-generated threads that grows with demand. Neither is evidence of inbox placement, authentication, improved reputation or human engagement.

Effective policy and stopping

In Admin > Warmup content > Overview, the effective settings editor shows the configuration actually read from admin_settings.warmup_generation. Save supported operator values without removing provider credentials. Manage settings permission is required for writes; existing read access is unchanged.

PUT /admin/warmup-content/settings accepts a JSON settings object and returns { "data": effectiveSettings }. Omitted fields retain their current effective values. Unknown fields, non-object payloads, multiple JSON documents and documents over 64 KiB are rejected. The overview returns effective_settings, generation_enabled, reserved_today and provider_capability. This is an admin endpoint, not a workspace API-key scope.

SettingDefault for an absent fieldSupported behavior
generation_enabledtruefalse prevents new persisted generation-job reservations. It does not delete credentials or disable cached-content use.
enabledtrueAdmits approved AI threads to selection. When false, new threads use only the built-in library.
schedule_enabledtrueEnables scheduled runs; the generation stop takes precedence.
cadence_hours6Integer from 1 to 168.
refresh_enabledfalsePreserves usable stock by default; explicit saved settings remain effective. Permits replacement of unused stock. Reserved plans stay readable for replies.
refresh_per_run10Integer from 0 to 25; zero means no refresh retirement.
daily_generation_cap1000Non-negative; zero retains the existing unlimited policy. Counts reserved requests, including unfinished, rejected or cancelled work, so concurrent replicas cannot overspend by counting only approved output. Resets at the UTC day boundary.
ai_selection_share100Legacy setting retained and round-tripped for rollback. Selection no longer rolls a share; AI and library threads compete on recency and use.
modelExisting default gpt-6-lunaA stored non-empty model is retained, not replaced. Availability is provider-checked before submission; the default is not a claim of availability.
max_messages_per_thread5Integer from 1 to 5 ordered replies.
poolsOne enabled premium library with target 200 and the generic segmentContent is shared across mailbox tiers. Legacy multi-pool settings collapse to one canonical library, preferring an enabled entry. Its enabled flag, target from 1 to 5000 and distinct trimmed segments survive.
engagementExisting engagement defaultsPercentages clamp to 0 to 100; dwell bounds clamp to 0 to 3600 seconds with maximum at least minimum. These controls do not constitute consent or evidence of engagement.

Selection draws, for each new thread, the conversation neither mailbox has seen within the last 30 days in either role. Among those it prefers the mailbox's segment, then a thread neither has ever seen, then the least used, with a random tie-break. When a pair has seen everything recently, it repeats the thread they saw longest ago rather than waiting, so warmup never stops for lack of content. Generation targets count only AI threads and use a conservative lower bound: approved threads minus the two largest active-mailbox exposure counts within the same 30 days, clamped at zero. The scheduler raises configured target floors with the busiest mailbox's average daily send and receive demand over the preceding seven days, rounded up and bounded at 5000. Demand counts distinct conversation IDs from retained warmup tokens in both roles, so replies do not multiply it. The scheduler also maintains the generic fallback segment. Supported values round-trip; out-of-range values clamp to the documented bounds.

Outstanding batches and capability

A committed stop serializes against new job reservations. Jobs reserved before it, including a provider submission already in progress, cannot be retroactively unsent. Already submitted batches continue polling and ingesting completed output. Jobs > Cancel remains an explicit, separate provider operation; cancellation may leave completed output to ingest. Do not remove credentials while batches still need polling or cancellation.

The provider must expose the configured model to the configured client. Model visibility alone does not prove Batch API or structured-output support. Submission errors from the provider determine those capabilities; the system does not silently substitute a different model. A configured client is not an assurance that a generation run will succeed.

Rendering, review and old data

New threads carry scenario version natural-v1 and rendering version canonical-v1. New jobs retain the requested reply count, so a later settings change does not reinterpret accepted output. Each generation request draws a topic plus a tone, an opening approach and a length, so threads on the same topic still differ in voice, structure and size. Turns may open with a short greeting and close with a sign-off line; rendering appends only the actual sender name, directly under a sign-off when there is one. Body text and subjects are immutable once approved; there is no spintax or random appended question.

Every outgoing turn, opening, reply or queued execution, is refused when it describes itself as simulated, diagnostic, hypothetical, fictional, automated, generated, a test message or a warmup. Generation rejects the same wording, and so does the semantic review. Threads from the retired diagnostic-v1 content stop at their next reply and the mailbox resumes its schedule with new content; stored subjects and bodies are never rewritten. Replies stay bound to the exact selected thread. A transaction records both participants before dispatch, and retries of the same task return the original thread without consuming another use. Those records survive task and token cleanup until the conversation or mailbox is deleted; a database trigger records both participants when a warmup token is deleted or reassigned, so history from before this table existed is kept without a deployment-time backfill. Duplicate subjects or openings, ignoring case and whitespace, are rejected within a batch and against approved content, including the built-in library. Approved inserts serialize under a database transaction lock, so concurrent batches cannot admit identical content. This is not a guarantee of semantic uniqueness.

Review states distinguish passed, rejected, unavailable, unreviewed, legacy_unknown and legacy_unreviewed. TypeSafe reviews the full rendered thread when configured. Otherwise a separate OpenAI request reviews it using the job’s configured model. Both paths require a coherent closed thread that reads as ordinary mail, with no pitch, no self-description as generated or a warmup, no real names, prices or exact dates, and a confidence of at least 0.70. Missing or invalid verdict fields are not approval. An unavailable judge is not approval. Rejected or unavailable newly generated conversations stay archived and cannot be activated through the status control. Existing stored conversations and their IDs are not replaced or relabeled as reviewed; their missing historical evidence is legacy_unknown. Existing provider job IDs remain pollable. Unversioned old-job output is retained archived as legacy_unreviewed, without claiming it meets the new contract. Output from a batch queued under diagnostic-v1 is still reviewed and, because of its wording, retained archived as rejected; its original text and IDs remain intact.

The built-in library lives in code. Each thread's ID is derived from its theme and opening, so an edited thread gets a new ID and threads already in flight on the old one close at their next reply. The backend mirrors the library into the conversation bank as static rows the first time it picks content, so library threads share the same rotation and per-mailbox records as AI threads. Only rows matching the running release's library are selectable, and an admin who archives a library row keeps it archived.

Live continuation binds the exact task, token, recipient receipt and received Message-ID to the provider-reported sent Message-ID. It preserves subject, immutable source/version, ordered References and the original maximum of one opening plus the requested one to five replies, including the final closure. One parent has at most one successor. An unrelated mailbox pair, an expired/retired token or unversioned legacy lineage cannot substitute for that parent. Trusted configured display names, including Unicode and punctuation, remain intact in plaintext signatures and MIME headers; CR/LF/NUL are rejected.

This application-owned correspondence proof is not itself cryptographic authentication. Updated protocol-2 Gmail/Graph workers can additionally retrieve one exact authorized warmup diagnostic's bounded MIME and verify DKIM cryptographically. IMAP, legacy workers, unrelated seed/placement contexts, unavailable MIME and transient key lookup remain unknown. Copied Authentication-Results remains unverified metadata; SPF, DMARC and receiving-hop TLS remain unknown absent independent evidence. See actual-message authentication and limits. DNS discovery and prose cannot establish pass. Received text and model output do not authorize recipients, permissions or actions.

Operation and compatibility

AI generation needs AI_API_KEY with AI_PROVIDER=openai or the provider unset. Other assistant providers do not implement this OpenAI Batch generator. TYPESAFE_API_KEY is optional for warmup review. Without AI credentials, warmup runs on the built-in library alone; nothing else is required.

Generation and polling remain automatic. Batches can take up to 24 hours; OpenAI review without TypeSafe adds one separate completion per reviewed thread. Adjust the daily generation cap to expected pool replenishment after reuse, and include review usage in the provider budget. Failed submissions are recorded as job failures and retry on the next scheduler tick rather than consuming a successful six-hour cadence. A generation outage never pauses sending: selection falls back to the library and to the least recently seen threads.

The supported rollback window includes the immediately preceding application release. This change keeps diagnostic-v1, canonical-v1, the existing JSON shape and database rows readable. Migration 000332 adds the per-mailbox record table and its token trigger without changing existing rows or stored bodies; retain it during rollback. The preceding release never selects natural-v1 rows, so a rollback returns to its own content, and threads it cannot resolve close at their next reply. During a mixed-version rollout an older poller rejects output from a natural-v1 job it did not submit, so pause scheduled generation until every backend is upgraded. Pause warmup dispatch too, as described in the upgrade order below. Keep provider credentials while batches drain.

Self-host upgrade

Nothing to do. The built-in library is available as soon as the new backend runs, with or without an AI provider, and existing enabled and schedule_enabled settings keep their meaning. No stored conversation or credential is deleted.

Upgrade order

Apply the additive content-provenance and participant-reservation migrations before starting the new backend. Deploy every backend instance before relying on generation_enabled=false; mixed old backends do not honor the new stop or resolve the new library IDs. Pause warmup dispatch through existing controls during a mixed-version rollout, and resume only after all control-plane replicas understand exact-parent resolution. Deploy the matching admin frontend after the backend. Keep provider credentials while existing jobs drain. No migration replaces saved models, existing job IDs, source IDs or customer credentials, and no live efficacy claim follows from local tests.

For the integrated release, preserve released migrations through 265 and main's workspace-link migration 266, then apply additive migrations 267 through 274 in order. Upgrade all result consumers and control-plane replicas, then execution workers. New shared dispatch requires an active worker reporting warmup_send_protocol=2; protocol 1 remains only its earlier lineage capability. Duplicate commands replay a durable terminal result rather than executing another native send. An unresolved native outcome holds the mailbox until reconciliation, not a timed blind reset. Known legacy throttle codes produce a conservative mailbox/provider cooldown without fabricated HTTP/SMTP evidence. Authentication, permanent and conflict holds need confirmed repair/operator evidence, not elapsed time or an unrelated success.

Resolving a repair hold

There is no automatic hold reset or end-user override. Pause dispatch while an instance operator investigates the exact stored organization, mailbox, configured provider, held task and reason. An unknown send must first receive a correlated definite provider result; do not change its accounting or call it failed merely to release traffic. For an authentication hold, complete the normal authenticated credential repair and independently confirm the provider now accepts it. For permanent/configuration or conflicting results, reconcile that specific provider evidence and accounting first. Record the operator, time and evidence reference in the incident audit record without credentials or message bodies.

Only then use the existing organization-scoped mailbox PATCH with send_recovery_resolution, the exact inspected held_task_id and held_reason, and typed repair/provider evidence. A newer successful sync is required for authentication repair; operator provider confirmation requires a same-mailbox definitive evidence task and reference. The guarded transaction refuses inactive/mismatched authority and any unknown task, records durable history, and preserves cooldowns and separate restrictions. See the resolution contract. Re-read admission before resuming. A changed hold or missing evidence stays restrictive. On hosted Warmbly, ask the platform operator to review; reconnecting, sleeping or another identity is not a quota/hold bypass.

Old executors remain compatible with old commands but cannot retroactively cancel work already accepted before upgrade. Pause and drain/reconcile that work before resuming new dispatch; no stronger mixed-old-worker guarantee is claimed. Correlated managed OAuth requires upgrading every Cloud control-plane replica before linked instances, as described in Warmbly Cloud. No new environment variable is required. Rollback refuses unresolved dispatches, successors, recovery restrictions or durable managed consent history rather than deleting negative state.

On this page