Перейти к документации
PHP

Отправка пакета

`emails->sendBatch`: до 100 сообщений, результат по каждому элементу.

emails->sendBatch

send_batch.php
$invoices = [    ['number' => 'INV-1042', 'email' => '[email protected]'],    ['number' => 'INV-1043', 'email' => '[email protected]'],]; $messages = []; foreach ($invoices as $invoice) {    $messages[] = [        'from' => '[email protected]',        'to' => $invoice['email'],        'subject' => 'Invoice ' . $invoice['number'],        'text' => 'Your invoice is attached.',    ];} $result = $client->emails->sendBatch($messages, idempotencyKey: 'invoices:2026-09'); echo $result->sent, ' sent, ', $result->failed, ' failed', PHP_EOL; foreach ($result as $item) {    if ($item['status'] === 'error') {        error_log($item['index'] . ' ' . $item['error']['code'] . ' ' . $item['error']['message']);    } else {        echo $item['index'], ' ', $item['email']['id'], PHP_EOL;    }}

sendBatch принимает список массивов сообщений, каждый в точности такой же формы, как массив, который принимает emails->send, и возвращает OpenEmail\Result\BatchResult. Его items содержат по одному массиву на каждое сообщение, по порядку: либо ok с сообщением, либо error с конвертом, с которым это сообщение было бы отклонено, и цикл по результату обходит именно их. Ничего не откатывается, поэтому счётчик failed больше 0 является списком для разбора, а не поводом отправить пакет заново.

Один ключ идемпотентности покрывает весь пакет, а сервер расширяет его для каждого элемента, поэтому повторённый пакет воспроизводит каждое сообщение, а не сводит их все к первому. При повторе отправляйте тот же список в том же порядке: элемент, сменивший место, привязывается к ключу другой позиции и возвращается с ошибкой idempotency_key_reuse.

Отклонённое сообщение не выбрасывает исключение. Исключение выбрасывает только проблема с пакетом в целом: пустой список, больше 100 сообщений, больше 10 сообщений с translate, сбой ключа или области либо сбой сервера. Сбой сервера посередине происходит после того, как более ранние элементы уже ушли, и клиент повторяет пакет с тем же ключом, что воспроизводит эти элементы, а не отправляет их дважды.

Элементы отправляются один за другим внутри одного запроса, поэтому большой пакет немедленных отправок выполняется заметно дольше, чем один send. Задавайте клиенту timeout: с запасом.

Параметры: emails->sendBatch

emailsarrayобязательно
От 1 до 100 сообщений, которые отправляются как `{"emails": [...]}` и принимаются по одному в указанном порядке. Каждое обрабатывается так же, как в `emails->send`: одиночный получатель оборачивается, `DateTimeInterface` становится моментом времени, байты вложений кодируются, а элемент, который не является массивом, выбрасывает `InvalidArgumentException` ещё до отправки. Пустой список, больше 100 сообщений или больше 10 сообщений с `translate` приводят к отклонению всего вызова с `validation_error` для `emails`. Отсутствие области `emails:send` и неправильно сформированный `idempotencyKey:` тоже отклоняют весь вызов, ещё до отправки первого сообщения.
idempotencyKeystring
Устраняет дубликаты пакета между процессами. Клиент в любом случае добавляет к каждому вызову свежесгенерированный ключ, поэтому его собственные повторы никогда не отправляют дважды, а сервер расширяет полученный ключ для каждого элемента как `key/0`, `key/1` и так далее, через косую черту, которую ваш собственный ключ содержать не может, поэтому один ключ на сотню сообщений не сведёт их все к первому.
apiKeystring
Отправляет пакет с этим ключом вместо ключа клиента.

Каждое сообщение в emails

fromstring or arrayобязательно
Отправитель в виде голого адреса, `Name <addr@host>` или массива с `email` и `name`. Запасного отправителя нет, и ключу должен быть разрешён этот адрес. Отказ проваливает только этот элемент, как `permission_error` с кодом `from_address_forbidden`.
tostring or arrayобязательно
Хотя бы один получатель, а одиночного клиент оборачивает в список. Не более 50 адресов в сумме по `to`, `cc` и `bcc`, считая на каждое сообщение, а не на весь пакет.
ccstring or array
По умолчанию отсутствует и засчитывается в те же 50 адресов, что `to` и `bcc`.
bccstring or array
По умолчанию отсутствует и засчитывается в те же 50 адресов. `Bcc` входит в число имён, которые `headers` задавать не может, так что это единственный способ отправить скрытую копию. Форма заголовка свела бы на нет отдельный конверт для каждого получателя, который и делает адрес скрытым.
replyTostring or array
Куда идут ответы. Применяется после `headers`, так что он перезаписывает `Reply-To`, который вы задали и там, а не добавляет второй.
subjectstring
Не более 998 символов, предел строки по RFC 5322, по умолчанию пустая строка. При пустой теме используется собственная тема шаблона, если её предоставляет `template`.
htmlstring
Часть HTML, не длиннее миллиона символов, и именно её видят получатели, когда заданы оба тела. Требуется одно из `html`, `text`, `template` или `draftId`, и элемент без них проваливается как `validation_error` на `html`.
textstring
Текстовая часть, не длиннее миллиона символов. Можно передать обе, но каждый транспорт на этом пути собирает одно тело из одной строки, так что при наличии `html` побеждает он.
headersarray
Только `X-*`, `List-*`, Reply-To, Precedence, Auto-Submitted, Importance, Priority и Feedback-ID. Всё, что транспорт задаёт сам (From, To, Bcc, Subject, Message-ID, заголовки DKIM и ARC), отклоняется как `reserved_header`, а не отбрасывается молча. Значения не длиннее 998 символов и не могут содержать CR, LF или NUL, потому что вторая строка становится вторым заголовком.
attachmentsarray
Не более 20 файлов на сообщение, а встроенные файлы в сумме не более 5 МБ после декодирования, считая на сообщение, а не на пакет. `content` передаётся по сети в base64. Передайте поток из `fopen`, `SplFileInfo` или поток PSR-7, и клиент прочитает и закодирует его, либо строку, которая уже в base64. Массив только с `fileId` называет файл, уже находящийся в рабочем пространстве, и не учитывается в пределе для встроенных файлов.
threadIdstring
Ответить в существующую цепочку, не длиннее 256 символов. Транспорт пишет из этого In-Reply-To и References, и именно это помещает ответ в переписку, а не рядом с ней.
draftIdstring
Отправить содержимое сохранённого черновика под этим конвертом, не более 256 символов. По сети уходят получатели, тема и заголовки, собранные здесь.
templatearray
Отрисовать сохранённый шаблон на сервере по идентификатору (`tpl_…`) или слагу: `version` закрепляет ревизию, а `props` и `slots` заполняют его. Фиксируется один раз, при принятии элемента, и отклоняется вместе с `html` или `text`, а также вместе с `draftId`, поскольку каждый из них является вторым ответом на вопрос о том, что содержит сообщение.
scheduledAtDateTimeInterface or string
`DateTimeInterface`, момент ISO 8601 или длительность вроде `PT1H`, не раньше чем через секунду и не позже чем через 365 дней. Строка с датой без времени означает полночь UTC в этот день. Элементы планируются независимо, поэтому один пакет может содержать сто разных времён отправки.
cancellableForSecondsint
Окно отмены в секундах для немедленной отправки, от 0 до 900, по умолчанию 0. Любое значение больше 0 отклоняется вместе с `scheduledAt` в том же элементе, поскольку запланированное сообщение и так можно отменить, пока оно не ушло.
trackingarray
`opens` и `clicks`, оба необязательные, и каждый переопределяет настройку только для этого сообщения. Ключ, который вы не указали, следует настройке адреса, с которого отправляется сообщение (или catch-all, который его поймал), а она выключена, если этот адрес её не включил.
tagsarray
Не более 10 меток: ключи от 1 до 64 символов из набора `A-Za-z0-9_-` и значения до 256. Возвращаются вместе с сообщением и никогда не интерпретируются: `emails->list` фильтрует только по `status:`, `from:`, `broadcastId:` и окну расписания, так что метку можно прочитать у сообщения, которое у вас уже есть, но не использовать для поиска.
translatearray
Отправить этот элемент на другом языке. Перевод фиксируется в момент принятия, поэтому уходит именно одобренный текст. Его могут нести не более 10 элементов одного пакета: каждый тратит несколько вызовов модели, а элементы выполняются по порядку, поэтому пакет побольше был бы прерван посреди отправки. Сверх этого весь вызов отклоняется как `too_many_items` для `emails` ещё до отправки.

Ответ: OpenEmail\Result\BatchResult

Результат доступен только для чтения, реализует IteratorAggregate по items и Countable, поэтому foreach ($result as $item) обходит элементы, а count($result) их считает.

itemsarray
По одному массиву на каждое сообщение в том порядке, в каком вы их отправили. Ничего не откатывается, поэтому это запись о том, что случилось с каждым сообщением, а не отчёт о транзакции. API отвечает 207 независимо от того, были ли приняты все сообщения, часть или ни одного, поэтому вызов возвращает результат в любом случае, а ветвиться нужно по `status` каждого элемента.
sentint or null
Сколько элементов было ПРИНЯТО, что не то же самое, сколько ушло. Элемент может быть `ok` и при этом нести `email`, у которого `status` равен `failed` или `partial`, потому что отказ транспорта от сообщения после создания записи является результатом доставки, а не отклонённым запросом. Равно null, только если в ответе не было счётчика.
failedint or null
Сколько элементов несут `error`. Значение больше 0 является списком для разбора, а не поводом отправить пакет заново. Принятые сообщения уже ушли.

Каждый элемент

indexint
Позиция сообщения этого элемента в списке, который вы отправили. Передаётся и как ключ, и как порядок, поэтому код, который фильтрует или сортирует `items`, всё равно может сказать, какое сообщение не прошло.
statusstring
`ok` или `error`. `ok` несёт `email`, `error` несёт `error`, и ни один элемент не несёт оба.
emailarray
Принятое сообщение, только в элементе `ok`, в той же форме, что возвращает одиночная отправка. Его `replayed` равно true, когда производный `Idempotency-Key` совпал с уже существующей отправкой: тогда ничего нового не отправлено, а это исходное сообщение. Оно не несёт ключа `tracking`, потому что о вовлечённости сообщается позже, а в момент принятия сообщать нечего.
errorarray
Почему было отклонено именно это сообщение, только в элементе `error`. Это конверт ошибки API без `docUrl` и `requestId`: они описывают запрос, а запрос в целом прошёл успешно.

Ошибка элемента

typestring
Категория для ветвления: `validation_error`, `permission_error`, `not_found_error`, `conflict_error` и остальные. Этот набор заморожен и не будет расти, в отличие от `code`.
codestring
Конкретный сбой: `from_address_forbidden`, `invalid_email_address`, `too_many_recipients`, `reserved_header`, `message_too_large`, `unknown_parameter`. Набор открытый и пополняемый, поэтому незнакомый код обрабатывайте по его `type`. Здесь это `code`, потому что это декодированный конверт, тогда как исключение несёт то же значение в `errorCode`.
messagestring
Одно предложение, написанное для человека, называющее ошибочное значение, если оно есть. Не стабильный идентификатор. Переключайтесь по `code`.
paramstring
Поле, которое было отклонено, в виде пути через точку внутри ИМЕННО ЭТОГО сообщения: `to.0`, `from`, `attachments`. Отсутствует, когда сбой не называет поля, и никогда не начинается с позиции в пакете: для неё есть `index`.