---
title: "Schedule a CRM email campaign"
method: POST
path: "/api/campaigns"
tags: ["crm-email"]
---

# Schedule a CRM email campaign

`POST /api/campaigns`

Admin-only endpoint that persists a scheduled CRM email campaign. The audience is stored as criteria (stato/motivo filters resolved at send time, or an inline recipient_emails list) — never as materialized recipient rows — so a campaign is a single row regardless of how many patients it targets. Sending is performed later by the scheduled batch dispatcher.

## Request body

- CRMScheduleEmailRequest — Request model for scheduling a CRM email campaign for later sending. Mirrors :class:`CRMSendEmailRequest` minus the to_email preview field, plus a ``scheduled_at``. The audience is stored as criteria, not resolved recipients: filter audiences (``patient_states`` / ``registration_reasons``) are resolved when the campaign is sent; a ``recipient_emails`` list (e.g. an uploaded CSV) is stored inline.
  - `scheduled_at` string, date-time, required — When to send the campaign (timezone-aware).
  - `subject` string, required — Email subject.
  - `notes` string, nullable — Internal note on why the campaign is being sent; shown in the CRM only, never emailed.
  - `email_html` string, nullable — Composed HTML body (image placeholders already replaced by URLs).
  - `template_path` string, nullable — Storage path of the uploaded template under public/email/templates/.
  - `placeholder_images` object — Map of placeholder name -> image public URL used to compose email_html.
  - `patient_states` string[] — Stato paziente filters. Combined with registration_reasons per filter_match at send time.
  - `patient_filters` string[] — Deprecated alias of patient_states (kept for the current frontend payload).
  - `registration_reasons` string[] — Motivo iscrizione filters. Combined with patient_states per filter_match at send time.
  - `filter_match` 'any' | 'all' — How stato and motivo filters combine at send time: 'any' (OR, default) unions them; 'all' (AND) keeps only patients matching both a selected stato and motivo.
  - `recipient_emails` string[] — Explicit recipient email addresses (e.g. from an uploaded CSV). When present the campaign audience is this list. Capped at 50000 addresses.

## Response `201`

Successful Response

- CRMEmailCampaignResponse — Response model for a scheduled CRM email campaign.
  - `id` string, required — Campaign UUID.
  - `status` string, required — scheduled | running | sent | failed | cancelled.
  - `scheduled_at` string, date-time, nullable — When the campaign will be sent.
  - `subject` string, required — Email subject.
  - `notes` string, nullable — Internal note on why the campaign was sent; CRM-only.
  - `audience_type` string, required — 'filters' or 'csv'.
  - `audience_size` integer, nullable — Recipient count for a csv audience; null for a filter audience (resolved at send time).
  - `created_at` string, date-time, required — When the campaign was created.

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.dev/corabea/apis/corabea-api.md) · [All operations](https://skmtc.dev/corabea/apis/corabea-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/corabea/corabea-api/revisions/13c1bce1bdc4/schema)
