Preview Bulk Customer Email
POST/api/sales-orders/bulk-email/preview
Resolves and groups a bulk-email selection without sending anything, powering the confirmation dialog's recipient summary and a masked sample render. Accepts the exact same body as POST /sales-orders/bulk-email.
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). 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.
Response data:
order_count(int): Total orders resolved from the selection.email_count(int): Distinct emails that will be sent (after grouping + cap).skipped_no_email(array of int): Order IDs with no resolvable email.skipped_opted_out(array of int): Order IDs whose customer opted out.skipped_overflow(int): Recipients beyond the cap that would be dropped.groups_preview(array, max 50): Per-recipient summary with a maskedemail,order_count, andorder_numbers.sample(object|null): First group's maskedrecipientplus the renderedsubjectandbodyHTML; null when no group is emailable.
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.
Validation failed — the body is a field → messages map (Laravel shape) or the platform envelope with a stable machine-readable code. Fix the payload and resubmit.
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.