Vai al contenuto

Campaign Draft

Il Campaign Draft è un aggregate operativo separato dall'Issue. Il percorso normale accetta una sola intenzione esplicita di scheduling, restituisce un jobId durevole e lascia al server l'esecuzione di prepare, content_upload, schedule, riconciliazione e resume. Gli attempt provider restano distinti e immutabili.

Obiettivo e transizione autorevole

flowchart TD
    publish["Issue published"] -.->|"nessun side effect"| ready["Eleggibile"]
    ready --> request["request schedule + confirmation"]
    request --> commit["D1: campaign + snapshot + job"]
    commit --> accepted["202 accepted + jobId"]
    commit --> workflow["Workflow: solo jobId"]
    workflow --> prepare["prepare"]
    prepare --> draft["draft_created"]
    draft --> upload["content_upload"]
    upload --> content["content_uploaded"]
    content --> schedule["schedule"]
    schedule --> scheduled["scheduled"]
    scheduled -.->|"evidenza distinta"| delivery["delivery"]

La freccia tratteggiata indica un confine, non un automatismo.

La legacy email-campaign-draft Queue, il suo producer e il consumer restano ritirati. La nuova email-campaign-orchestration Queue rappresenta un concetto diverso: nasce soltanto dopo un'intenzione esplicita e trasporta esclusivamente jobId. Pubblicazione e approvazione non creano job né messaggi. Un dispatcher tecnico ogni cinque minuti recupera outbox o lease scaduti, ma non possiede il send time: lo scheduling effettivo resta in Mailjet.

Input e fonti accettate

Un Issue è eleggibile soltanto se:

  • status: published;
  • editorialStatus: approved;
  • quality gate verdi.

L'applicazione rende subject, HTML e testo dall'Issue Content canonico. Il target proviene dal catalogo server-owned e viene esposto come alias, display name e riferimento mascherato.

Attore e autorizzazione

L'identità deve possedere email:campaigns:schedule. Il browser seleziona soltanto un target configurato, l'Issue e i metadati temporali richiesti. Credenziali, lista reale e ID provider non sono input arbitrari del client.

Validazione e quality gate

L'API rilegge l'Issue, la configurazione e l'aggregate prima di ogni operazione. Ogni richiesta usa lo stesso targetAlias server-owned e lo stesso scheduledFor RFC3339 scelti alla creazione. Un Campaign Draft attivo duplicato è rifiutato. content_upload richiede esattamente draft_created; schedule richiede esattamente content_uploaded. Un mismatch di target, timestamp, Issue o stato fallisce chiuso.

Responsabilità dell'AI

L'AI non partecipa alla preparazione. HTML e testo sono rendering deterministici del contenuto approvato; non vengono rigenerati dal modello.

Persistenza e audit in D1

issue_email_campaigns conserva lo stato corrente. La stessa transazione D1 crea email_campaign_orchestration_jobs, email_campaign_content_snapshots e email_campaign_orchestration_outbox. Lo snapshot contiene subject, HTML, plain text, hash e revisione Issue ed è immutabile per il job. Il job conserva fase, lease bounded, contatori di attempt/reconciliation e classificazione sanitizzata del failure domain. Gli attempt conservano:

  • operationType: prepare, content_upload o schedule;
  • providerMode: mock, dry_run o live;
  • stato pending, processing, succeeded, failed o unknown;
  • stato durevole della richiesta provider: not_started, started o unknown, con timestamp separato quando la richiesta parte;
  • parent del retry;
  • evidenza provider sanitizzata.

Un attempt terminale non viene riscritto. Job, outbox e log non contengono recipient, contatti, secret, payload Mailjet grezzi o riferimenti target privati.

Evidenza di successo

L'accettazione 202 prova soltanto la persistenza durevole, non lo scheduling. Leggere get_email_campaign_job fino a uno stato terminale. Per prepare, richiedere attempt succeeded e provider draft autorevole. Per content_upload, richiedere aggregate content_uploaded, attempt riuscito e provider mode coerente. Per schedule, richiedere aggregate scheduled, attempt riuscito, timestamp atteso ed evidenza provider; lo scheduling non prova la delivery.

mock e dry_run provano soltanto il contratto locale. Non provano una chiamata Mailjet live.

Fallimento e recupero

La stessa idempotencyKey riusa campaign e job. Due richieste concorrenti per Issue e target non creano due workflow aperti. Due messaggi Queue duplicati non eseguono due side effect perché soltanto una lease D1 non scaduta può possedere il job. Una lease scaduta viene classificata WORKER_ABORT e ridispatchata.

Timeout o network failure senza risposta dopo providerRequestState: started richiedono riconciliazione, mai un nuovo POST cieco. La draft viene adottata solo per correlation ID univoco; il contenuto solo se coincide byte-per-byte con lo snapshot; lo schedule solo se coincide con scheduledFor. Zero candidati esauriscono una policy read-only bounded; più candidati o mismatch portano a operator_required. 429, 5xx, timeout, network error, WORKER_ABORT e persistence failure restano classi distinte. Per l'ambiguità provider seguire il runbook diagnostico.

Effetti collaterali esclusi

La preparazione:

  • non pubblica;
  • non carica automaticamente il contenuto;
  • non schedula;
  • non invia;
  • non gestisce contatti o consenso;
  • non distribuisce Worker.

Il caricamento contenuto non schedula e non invia. Lo scheduling non equivale a delivery e non modifica contatti, consenso o suppression.

Superfici operator

Il path felice è schedule_newsletter e poi get_email_campaign_job. L'unica mutazione di stop è abort_email_campaign. Le route stepwise prepare/upload/schedule/recover/replace/deliver/reconcile sono ritirate.

Riferimenti canonici