---
title: "Create Email automation"
method: POST
path: "/automations/email"
tags: ["Automations"]
---

# Create Email automation

`POST /automations/email`

Create an outbound email automation. Different recipient model from outreach — use `selection_mode: mailing_list` (with vault list IDs or upload emails inline) OR `selection_mode: filters` (creator search criteria). An `email_account_id` may back several active email automations. Each one adds to that inbox's daily send volume, and the daily cap is applied per automation, so heavy reuse risks provider throttling or spam classification.

## Headers

- `Idempotency-Key` string, nullable
- `X-Dry-Run` string, nullable
- `X-Created-Via` string, nullable

## Request body

- AutomationEmailRequest — Body for POST /automations/email. `email_account_id` MAY back several active email automations in this shop (CORE-6369 — previously limited to one, returning 409 EMAIL_ACCOUNT_ALREADY_LINKED). Reuse deliberately: every automation on a mailbox adds to its daily send volume, and the daily cap is applied per automation, so N automations send up to N x the limit from one address. NOTE: schedule, end_date, and email_sent_per_day are NOT supported on email automations in v1 — the underlying `email_automations` table has no columns for them and silently dropping them in the contract would lie to callers about what's persisted. v1.1 will add the columns + expose these fields. Email automation timing is currently governed by portal-level defaults (one cycle per `next_run_cycle`). Per PR #1513 round-5 review (cursor + coderabbitai).
  - `automation_name` string, required
  - `email_account_id` integer, required — Pre-connected email account. May back several active email automations; each one adds to that inbox's daily send volume.
  - `subject` string, required
  - `body` string, required — HTML or plaintext.
  - `selection_mode` 'mailing_list' | 'filters', required
  - `mailing_list` EmailMailingList — selection_mode=mailing_list — recipients via uploaded emails or existing Lists (vault_files with non-empty `creator_emails`). NOTE: lists referenced by `lists_selected` MUST contain at least one email address — the `vault_files.creator_emails` JSONB column must be non-empty. Targeting requires emails to send to. The route validates this at create time and returns 400 `LIST_MISSING_EMAILS` for any list that lacks emails. Per architecture review 2026-05-01.
    - `list_upload_emails` string[]
    - `lists_selected` string[]
  - `filters` EmailFilters — selection_mode=filters — recipients via creator search filters. Subset of the saved_searches.filters JSONB shape — fields that make sense for email outreach. Backend translates this into the legacy filters JSON.
    - `min_creator_gmv` number, nullable
    - `min_followers` integer, nullable
    - `min_engagement_rate` number, nullable
    - `min_avg_views` integer, nullable
    - `regions` string[], nullable
    - `categories` string[], nullable
  - `creators_to_exclude` CreatorsToExclude — Outreach-paradigm recipient exclusion. Mirrors `creators_to_omit` in the legacy DB column / frontend payload, plus the boolean `exclude_previously_messaged` flag from the portal's Exclude Creators panel.
    - `list_upload` string[]
    - `lists_selected` string[]
    - `crm_groups` string[] — UUIDs of existing CRM Groups whose current members should be excluded. Use `GET /crm-groups` to discover.
    - `exclude_previously_messaged` boolean — Skip creators this shop has DM'd before in any prior automation. Maps to config.exclude_previously_messaged.
  - `attachments` EmailAttachment[] — Email attachments — `[{name, url}]` shape. URL must be a hosted HTTPS URL (no file uploads). Server fetches at create time and re-hosts internally.
    - `name` string, required — Display name for the attachment (e.g. `deck.pdf`).
    - `url` string, required — Hosted HTTPS URL of the attachment. Reacher fetches the file from this URL at create time, runs the standard upload validation pipeline (size, MIME type, virus scan), and re-hosts to internal storage. The provided URL is NOT used at send time — only at create time.

## Response `201`

Successful Response

- AutomationWriteResponseV2
  - `data` AutomationResourceV2, required — Canonical write-endpoint response. Replaces the v1 AutomationResource (which mirrored the legacy single-shape body). Now reflects the four-type taxonomy — `automation_type` is the public-API type, not the DB string. The DB string is in `_internal_type` for debugging only.
    - `automation_id` integer, nullable — None on dry-run.
    - `shop_id` integer, required
    - `automation_name` string, required
    - `automation_type` string, required — One of: target_collab | dm | email | sample_request.
    - `automation_status` string, required
    - `state` string, required
    - `config` object — Full config as stored. Type-specific.
    - `schedule` object, nullable
    - `creators_to_include` object, nullable
    - `creators_to_exclude` object, nullable
    - `crm_group_id` string, nullable
    - `ai_enabled` boolean
    - `end_date` string, nullable
    - `created_at` string, nullable
    - `updated_at` string, nullable
    - `created_via` string, nullable — Surface that created the automation. Always populated on response: rows with NULL or empty `created_via` (predating migration 020) are surfaced as `portal` since the portal was historically the only writer. Common values: portal | api | api_direct | mcp | agent | slack | internal | campaign_tc_auto (set is not closed — other internal surfaces may appear). `api_direct` marks single-creator invites created by `POST /public/v1/target-collabs`.
    - `side_effects` object, nullable — Backend side-effects, e.g. {'crm_group_created': '<uuid>'} on sample_request creates.
  - `dry_run` boolean
  - `side_effects` object, nullable — Backend side-effects, e.g. {'crm_group_created': 'uuid-...'} for sample_request creates.

## Other responses

- `400` — Validation, missing/invalid Idempotency-Key, or forbidden update field.
- `403` — API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).
- `404` — Automation/list/group not in this shop.
- `409` — Idempotency conflict or archived row.
- `422` — Validation Error
- `429` — Write-tier rate limit (10/min, 50/hr, 100/day) exceeded.

---

[API](https://skmtc.dev/reacherapp/apis/reacher-data-api.md) · [All operations](https://skmtc.dev/reacherapp/apis/reacher-data-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/reacherapp/reacher-data-api/revisions/5eefff009b5b/schema)
