Bulk Email Customers About Orders
POST/api/sales-orders/bulk-email
Dispatches a tracked background job that emails customers about their selected sales orders. Multiple orders for the same customer (same store + email, case-insensitive) are grouped into a single email (Cin7 Omni model). Recipients without a resolvable email and opted-out customers are suppressed. The send is capped at config('mail.bulk_sales_order_recipient_cap') distinct recipients (default 5000); overflow recipients are reported via the job's skipped_overflow result and a warning log — never silently dropped. Every send is written to the order's activity log under one batch.
orders:writeGrant this scope to your token under Settings → Developer → Personal Access Tokens.
Authentication: Requires Bearer token.
Request body (scope: one of ids or filters is required):
ids(array of int, required withoutfilters): Sales order IDs to email about.filters(object, required withoutids): Filter set that resolves to a list of sales order IDs (same shape used by the V2 sales-orders listing endpoint). It accepts the groupedfilter_groupsshape ({ \"filter_groups\": \"<base64 JSON>\" }, same as the V2 list) as well as the legacy JSON-string filter set. Combine withapply_to_all.apply_to_all(bool, optional): When true, the action targets every order matching the activefiltersrather than an explicitidslist.template_id(int|null, optional): Thebulk_sales_orderstore email template the content was loaded from. Null when the body was composed ad hoc.subject(string, required, max 998): Email subject. Supports{{customer_name}},{{order_count}},{{store_name}}tokens.html_body(string, required): Email body HTML. Supports the same tokens plus{{orders_table}}(renders the customer's grouped orders as a table).cc(array of email, optional, max 25): CC addresses added to every email.attachments(array of string, optional, max 25): Attachable-document keys to include (e.g.["invoice_pdf"]).group_by_customer(bool, optional, default true): Group every selected order for one customer (same store + email) into a single email (Cin7 Omni model). When false, one email per order.skip_without_email(bool, optional, default true): Orders with no resolvable email are always skipped. Must be true — sendingfalseis rejected (422).respect_opt_out(bool, optional, default true): Skip customers flaggedemail_opt_out.
Behavior:
- Returns immediately with the tracked job log ID; poll job progress via the tracked job log endpoints (job name
Bulk Email Sales Orders). - The job records per-recipient sent/skipped/failed counts and an
activity_batch_uuidin its results. - The job has
tries = 1and never auto-retries (prevents double-sends).
Requires permission: sales_orders.update
Request
Responses
- 200
- 401
- 403
- 422
- 429
OK
Response Headers
Unauthenticated — the bearer token is missing, revoked, expired, or malformed. Never retry automatically; fix the credential. See the Errors guide.
Forbidden — the token lacks a required scope, the endpoint is not available to API tokens, or the user behind the token lacks the permission. A human must adjust the token scopes or user permissions; do not retry.
Unprocessable Entity
Response Headers
Rate limited — platform limit is 1,000 requests/min; individual tokens may carry lower limits. Honor the Retry-After header before retrying. See the Rate Limits guide.