# Create Email Send

`POST /v1/email/batches/{batch_id}/sends`

Send rows. **This mails real people and cannot be undone.**

`scope: "selected"` sends `recipient_ids`; a row that cannot be sent comes
back in `rejected` and stays a draft. `scope: "all"` sends every row as it
finishes drafting — including drafts nobody has read — and waits while
drafting runs. It cannot be live beside another send of the batch (409).

`start_at` schedules it, `window` holds it to business hours, and
`send_gap_seconds` and `send_daily_cap` (a fixed limit or a ramp) pace the
whole batch. Everything
omitted comes from the batch. Cancelling it leaves the unsent rows as drafts.

Authenticate with `Authorization: Bearer $TALQING_API_KEY`. Errors use the shared envelope: https://docs.talqing.com/api-reference/errors.md

## OpenAPI

```json
{
 "openapi":"3.1.0",
 "servers":[
  {
   "description":"Talqing API",
   "url":"https://api.talqing.com"
  }
 ],
 "paths":{
  "/v1/email/batches/{batch_id}/sends":{
   "post":{
    "description":"Send rows. **This mails real people and cannot be undone.**\n\n`scope: \"selected\"` sends `recipient_ids`; a row that cannot be sent comes\nback in `rejected` and stays a draft. `scope: \"all\"` sends every row as it\nfinishes drafting \u2014 including drafts nobody has read \u2014 and waits while\ndrafting runs. It cannot be live beside another send of the batch (409).\n\n`start_at` schedules it, `window` holds it to business hours, and\n`send_gap_seconds` and `send_daily_cap` (a fixed limit or a ramp) pace the\nwhole batch. Everything\nomitted comes from the batch. Cancelling it leaves the unsent rows as drafts.",
    "operationId":"email.batches.sends.create",
    "parameters":[
     {
      "in":"path",
      "name":"batch_id",
      "required":true,
      "schema":{
       "format":"uuid",
       "title":"Batch Id",
       "type":"string"
      }
     }
    ],
    "requestBody":{
     "content":{
      "application/json":{
       "schema":{
        "$ref":"#/components/schemas/CreateEmailSendRequest"
       }
      }
     },
     "required":true
    },
    "responses":{
     "201":{
      "content":{
       "application/json":{
        "schema":{
         "$ref":"#/components/schemas/CreateEmailSendResponse"
        }
       }
      },
      "description":"Successful Response"
     },
     "400":{
      "$ref":"#/components/responses/BadRequest"
     },
     "401":{
      "$ref":"#/components/responses/Unauthorized"
     },
     "403":{
      "$ref":"#/components/responses/Forbidden"
     },
     "404":{
      "$ref":"#/components/responses/NotFound"
     },
     "409":{
      "$ref":"#/components/responses/Conflict"
     },
     "422":{
      "$ref":"#/components/responses/ValidationFailed"
     },
     "default":{
      "$ref":"#/components/responses/Error"
     }
    },
    "security":[
     {
      "BearerAuth":[]
     },
     {
      "SessionCookie":[]
     }
    ],
    "summary":"Create Email Send",
    "tags":[
     "email"
    ]
   }
  }
 },
 "components":{
  "responses":{
   "BadRequest":{
    "content":{
     "application/json":{
      "schema":{
       "$ref":"#/components/schemas/ErrorResponse"
      }
     }
    },
    "description":"Well-formed, but rejected by a rule; `detail.errors` lists each problem."
   },
   "Conflict":{
    "content":{
     "application/json":{
      "schema":{
       "$ref":"#/components/schemas/ErrorResponse"
      }
     }
    },
    "description":"The resource's current state forbids this \u2014 a name already taken, a job already running."
   },
   "Error":{
    "content":{
     "application/json":{
      "schema":{
       "$ref":"#/components/schemas/ErrorResponse"
      }
     }
    },
    "description":"Unexpected error. Every error this API returns carries this body."
   },
   "Forbidden":{
    "content":{
     "application/json":{
      "schema":{
       "$ref":"#/components/schemas/ErrorResponse"
      }
     }
    },
    "description":"Authenticated, but this organization role may not perform the operation."
   },
   "NotFound":{
    "content":{
     "application/json":{
      "schema":{
       "$ref":"#/components/schemas/ErrorResponse"
      }
     }
    },
    "description":"No such resource in this organization."
   },
   "Unauthorized":{
    "content":{
     "application/json":{
      "schema":{
       "$ref":"#/components/schemas/ErrorResponse"
      }
     }
    },
    "description":"Missing credential, or one that is revoked or no longer a member of this organization."
   },
   "ValidationFailed":{
    "content":{
     "application/json":{
      "schema":{
       "$ref":"#/components/schemas/ErrorResponse"
      }
     }
    },
    "description":"The request did not match this operation's schema; `detail.errors` lists each field."
   }
  },
  "schemas":{
   "CreateEmailSendRequest":{
    "additionalProperties":false,
    "description":"Send these rows. **This is the call that mails real people.**\n\n``scope: \"selected\"`` sends exactly ``recipient_ids``. ``scope: \"all\"`` is a\nstanding send: every row of the batch as it reaches `draft`, including rows\nnot drafted yet \u2014 so it mails drafts nobody has read. Everything else\ndefaults to the batch's own setting; ``window`` and ``send_daily_cap`` accept\nan explicit ``null`` to mean \"no window\" and \"uncapped\" rather than\n\"inherit\", so the service reads ``model_fields_set``.\n\nThe sender applies to THIS send alone and never writes back to the batch.",
    "properties":{
     "from_email":{
      "title":"From Email",
      "type":"string"
     },
     "from_name":{
      "anyOf":[
       {
        "maxLength":200,
        "type":"string"
       },
       {
        "type":"null"
       }
      ],
      "title":"From Name"
     },
     "recipient_ids":{
      "anyOf":[
       {
        "items":{
         "format":"uuid",
         "type":"string"
        },
        "maxItems":10000,
        "type":"array"
       },
       {
        "type":"null"
       }
      ],
      "title":"Recipient Ids"
     },
     "reply_to":{
      "anyOf":[
       {
        "type":"string"
       },
       {
        "type":"null"
       }
      ],
      "title":"Reply To"
     },
     "scope":{
      "description":"`selected` sends `recipient_ids`; `all` sends every row as it is drafted.",
      "enum":[
       "all",
       "selected"
      ],
      "title":"Scope",
      "type":"string"
     },
     "send_attempts":{
      "ge":1,
      "le":5,
      "title":"Send Attempts",
      "type":"integer"
     },
     "send_daily_cap":{
      "anyOf":[
       {
        "discriminator":{
         "mapping":{
          "fixed":"#/components/schemas/FixedDailyCap",
          "ramp":"#/components/schemas/RampDailyCap"
         },
         "propertyName":"kind"
        },
        "oneOf":[
         {
          "$ref":"#/components/schemas/FixedDailyCap"
         },
         {
          "$ref":"#/components/schemas/RampDailyCap"
         }
        ]
       },
       {
        "type":"null"
       }
      ],
      "title":"Send Daily Cap"
     },
     "send_gap_seconds":{
      "ge":1,
      "le":3600,
      "title":"Send Gap Seconds",
      "type":"integer"
     },
     "send_retry_after_minutes":{
      "ge":5,
      "le":1440,
      "title":"Send Retry After Minutes",
      "type":"integer"
     },
     "start_at":{
      "anyOf":[
       {
        "format":"date-time",
        "type":"string"
       },
       {
        "type":"null"
       }
      ],
      "title":"Start At"
     },
     "timezone":{
      "title":"Timezone",
      "type":"string"
     },
     "window":{
      "anyOf":[
       {
        "$ref":"#/components/schemas/TimeWindow"
       },
       {
        "type":"null"
       }
      ]
     }
    },
    "required":[
     "scope"
    ],
    "title":"CreateEmailSendRequest",
    "type":"object"
   },
   "CreateEmailSendResponse":{
    "description":"The send that was created, and every row that did not go into it.\n\nTwo halves rather than one, because they answer different questions and a\ncaller needs both: what is now scheduled to go out, and which rows it\nrefused and why. If NOTHING in the selection could be sent there is no send\nto describe, so that case is a 400 naming the reasons instead.",
    "properties":{
     "rejected":{
      "items":{
       "$ref":"#/components/schemas/RejectedRecipient"
      },
      "title":"Rejected",
      "type":"array"
     },
     "send":{
      "$ref":"#/components/schemas/EmailSendResponse"
     }
    },
    "required":[
     "send",
     "rejected"
    ],
    "title":"CreateEmailSendResponse",
    "type":"object"
   },
   "EmailSendCounts":{
    "description":"Where this send's rows are.\n\n``total`` is the rows a `selected` send was created with, and null for an\n`all` send, which cannot know it while drafting runs. ``queued`` and\n``drafting`` are what it still covers \u2014 drafts ready to go, and rows still\nbeing written \u2014 so both are 0 once it has ended.",
    "properties":{
     "drafting":{
      "title":"Drafting",
      "type":"integer"
     },
     "queued":{
      "title":"Queued",
      "type":"integer"
     },
     "send_failed":{
      "title":"Send Failed",
      "type":"integer"
     },
     "sending":{
      "title":"Sending",
      "type":"integer"
     },
     "sent":{
      "title":"Sent",
      "type":"integer"
     },
     "skipped":{
      "title":"Skipped",
      "type":"integer"
     },
     "total":{
      "anyOf":[
       {
        "type":"integer"
       },
       {
        "type":"null"
       }
      ],
      "title":"Total"
     }
    },
    "required":[
     "total",
     "queued",
     "drafting",
     "sending",
     "sent",
     "send_failed",
     "skipped"
    ],
    "title":"EmailSendCounts",
    "type":"object"
   },
   "EmailSendResponse":{
    "properties":{
     "batch_id":{
      "format":"uuid",
      "title":"Batch Id",
      "type":"string"
     },
     "counts":{
      "$ref":"#/components/schemas/EmailSendCounts"
     },
     "created_at":{
      "format":"date-time",
      "title":"Created At",
      "type":"string"
     },
     "failure_reason":{
      "anyOf":[
       {
        "type":"string"
       },
       {
        "type":"null"
       }
      ],
      "title":"Failure Reason"
     },
     "finished_at":{
      "anyOf":[
       {
        "format":"date-time",
        "type":"string"
       },
       {
        "type":"null"
       }
      ],
      "title":"Finished At"
     },
     "from_email":{
      "title":"From Email",
      "type":"string"
     },
     "from_name":{
      "anyOf":[
       {
        "type":"string"
       },
       {
        "type":"null"
       }
      ],
      "title":"From Name"
     },
     "id":{
      "format":"uuid",
      "title":"Id",
      "type":"string"
     },
     "next_send_at":{
      "anyOf":[
       {
        "format":"date-time",
        "type":"string"
       },
       {
        "type":"null"
       }
      ],
      "title":"Next Send At"
     },
     "next_send_reason":{
      "anyOf":[
       {
        "enum":[
         "start",
         "window",
         "daily_cap",
         "retry",
         "gap",
         "drafting"
        ],
        "type":"string"
       },
       {
        "type":"null"
       }
      ],
      "title":"Next Send Reason"
     },
     "reply_to":{
      "anyOf":[
       {
        "type":"string"
       },
       {
        "type":"null"
       }
      ],
      "title":"Reply To"
     },
     "scope":{
      "enum":[
       "all",
       "selected"
      ],
      "title":"Scope",
      "type":"string"
     },
     "send_attempts":{
      "title":"Send Attempts",
      "type":"integer"
     },
     "send_daily_cap":{
      "anyOf":[
       {
        "discriminator":{
         "mapping":{
          "fixed":"#/components/schemas/FixedDailyCap",
          "ramp":"#/components/schemas/RampDailyCap"
         },
         "propertyName":"kind"
        },
        "oneOf":[
         {
          "$ref":"#/components/schemas/FixedDailyCap"
         },
         {
          "$ref":"#/components/schemas/RampDailyCap"
         }
        ]
       },
       {
        "type":"null"
       }
      ],
      "title":"Send Daily Cap"
     },
     "send_daily_cap_today":{
      "anyOf":[
       {
        "type":"integer"
       },
       {
        "type":"null"
       }
      ],
      "title":"Send Daily Cap Today"
     },
     "send_gap_seconds":{
      "title":"Send Gap Seconds",
      "type":"integer"
     },
     "send_retry_after_minutes":{
      "title":"Send Retry After Minutes",
      "type":"integer"
     },
     "start_at":{
      "anyOf":[
       {
        "format":"date-time",
        "type":"string"
       },
       {
        "type":"null"
       }
      ],
      "title":"Start At"
     },
     "started_at":{
      "anyOf":[
       {
        "format":"date-time",
        "type":"string"
       },
       {
        "type":"null"
       }
      ],
      "title":"Started At"
     },
     "status":{
      "enum":[
       "scheduled",
       "sending",
       "paused",
       "sent",
       "canceled",
       "failed"
      ],
      "title":"Status",
      "type":"string"
     },
     "timezone":{
      "title":"Timezone",
      "type":"string"
     },
     "window":{
      "anyOf":[
       {
        "$ref":"#/components/schemas/TimeWindow"
       },
       {
        "type":"null"
       }
      ]
     }
    },
    "required":[
     "id",
     "batch_id",
     "scope",
     "status",
     "failure_reason",
     "from_email",
     "from_name",
     "reply_to",
     "start_at",
     "timezone",
     "window",
     "send_attempts",
     "send_retry_after_minutes",
     "send_gap_seconds",
     "send_daily_cap",
     "send_daily_cap_today",
     "counts",
     "next_send_at",
     "next_send_reason",
     "created_at",
     "started_at",
     "finished_at"
    ],
    "title":"EmailSendResponse",
    "type":"object"
   },
   "ErrorBody":{
    "description":"The structured half of our error contract: a human `message` plus a list\nof individual `errors` (used wherever we surface many problems at once,\nincluding tool publish, op-tree checks, and request validation). The global\nexception handlers normalize every API error to\n`{detail: {message, errors, code}}`, so SDKs can parse one shape consistently.",
    "properties":{
     "code":{
      "anyOf":[
       {
        "type":"string"
       },
       {
        "type":"null"
       }
      ],
      "default":null,
      "description":"A stable identifier for this error, when it has one.",
      "title":"Code"
     },
     "errors":{
      "items":{
       "type":"string"
      },
      "title":"Errors",
      "type":"array"
     },
     "message":{
      "title":"Message",
      "type":"string"
     }
    },
    "required":[
     "message",
     "errors"
    ],
    "title":"ErrorBody",
    "type":"object"
   },
   "ErrorResponse":{
    "description":"The body of every error response, at every status code.",
    "properties":{
     "detail":{
      "$ref":"#/components/schemas/ErrorBody"
     }
    },
    "required":[
     "detail"
    ],
    "title":"ErrorResponse",
    "type":"object"
   },
   "FixedDailyCap":{
    "additionalProperties":false,
    "description":"The same limit every day.",
    "properties":{
     "kind":{
      "const":"fixed",
      "title":"Kind",
      "type":"string"
     },
     "limit":{
      "minimum":1.0,
      "title":"Limit",
      "type":"integer"
     }
    },
    "required":[
     "kind",
     "limit"
    ],
    "title":"FixedDailyCap",
    "type":"object"
   },
   "RampDailyCap":{
    "additionalProperties":false,
    "description":"`start` a day, raised by `step` every `interval_days` sending days, up to `end`.",
    "properties":{
     "end":{
      "title":"End",
      "type":"integer"
     },
     "interval_days":{
      "maximum":30.0,
      "minimum":1.0,
      "title":"Interval Days",
      "type":"integer"
     },
     "kind":{
      "const":"ramp",
      "title":"Kind",
      "type":"string"
     },
     "start":{
      "minimum":1.0,
      "title":"Start",
      "type":"integer"
     },
     "step":{
      "minimum":1.0,
      "title":"Step",
      "type":"integer"
     }
    },
    "required":[
     "kind",
     "start",
     "end",
     "step",
     "interval_days"
    ],
    "title":"RampDailyCap",
    "type":"object"
   },
   "RejectedRecipient":{
    "properties":{
     "reason":{
      "title":"Reason",
      "type":"string"
     },
     "recipient_id":{
      "format":"uuid",
      "title":"Recipient Id",
      "type":"string"
     },
     "row_number":{
      "title":"Row Number",
      "type":"integer"
     }
    },
    "required":[
     "recipient_id",
     "row_number",
     "reason"
    ],
    "title":"RejectedRecipient",
    "type":"object"
   },
   "TimeWindow":{
    "description":"The hours a list may run in, on the list's own clock.\n\nShared rather than duplicated, for the same reason ``window_state`` is: a\ncall batch and an email send run store the same three values under the same\nnames and must not each get their own copy of the rules. The field that\ncarries one is named for what it gates \u2014 ``calling_window`` on a campaign,\n``window`` on an email batch and its runs.\n\nA window that crosses midnight (``21:00``-``06:00``) is valid and means\nexactly what an evening consumer-calling window means: the day check applies\nto the *start* side, so ``21:00``-``06:00`` on ``days: [5]`` runs Friday\n21:00 through Saturday 06:00.",
    "properties":{
     "days":{
      "default":[
       1,
       2,
       3,
       4,
       5,
       6,
       7
      ],
      "description":"ISO weekdays the window opens on, 1 = Monday.",
      "items":{
       "type":"integer"
      },
      "title":"Days",
      "type":"array"
     },
     "end":{
      "format":"time",
      "title":"End",
      "type":"string"
     },
     "start":{
      "format":"time",
      "title":"Start",
      "type":"string"
     }
    },
    "required":[
     "start",
     "end"
    ],
    "title":"TimeWindow",
    "type":"object"
   }
  }
 }
}
```
