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

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

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

emails.send_batch

send_batch.py
import sys from openemail import openemailfrom openemail.types import EmailSend invoices = {'[email protected]': 'INV-4021', '[email protected]': 'INV-4022'} messages: list[EmailSend] = [    {'from': '[email protected]', 'to': to, 'subject': f'Invoice {number}', 'text': 'Attached.'}    for to, number in invoices.items()] result = openemail.emails.send_batch(messages) print(result['sent'], 'sent,', result['failed'], 'failed') for item in result['items']:    if item['status'] == 'error':        print(item['index'], item['error']['code'], item['error']['message'], file=sys.stderr)    else:        print(item['index'], item['email']['id'])

items содержит по одной записи на каждый вход, по порядку, каждая либо ok со своим сообщением, либо error с конвертом, которым это сообщение было бы отклонено. Ничего не откатывается, так что failed > 0 даёт список, с которым надо работать, а не повод переслать пакет заново.

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

Параметры: emails.send_batch

emailsSequence[EmailSend]обязательно
От одного до 100 сообщений, сериализуемых как `{ "emails": [...] }` и принимаемых по одному в заданном порядке. Пустой список, больше 100 либо больше 10 элементов с `translate` отклоняют весь вызов с `validation_error` на `emails`. То же делают отсутствующая область доступа `emails:send` и некорректный `idempotency_key`, и всё это до отправки хотя бы одного сообщения.
idempotency_keystr
Дедуплицирует пакет между процессами. Клиент в любом случае прикрепляет свежесозданный ключ при каждом вызове, так что его собственные повторы никогда не отправляют дважды, а сервер расширяет полученный ключ по каждому элементу как `key/0`, `key/1` и так далее (через слеш, символ, которого не может быть в вашем собственном ключе), так что один ключ на сотню сообщений не может схлопнуть их в первое.
emails[].fromRecipientInputобязательно
Отправитель: голый адрес, `Name <addr@host>` или словарь. Отправителя по умолчанию нет, и ключу должен быть разрешён этот адрес; отказ проваливает только этот элемент, как `permission_error` с кодом `from_address_forbidden`.
emails[].toRecipientInput | list[RecipientInput]обязательно
Хотя бы один получатель, а одиночного клиент сам оборачивает в список. Не более 50 адресов суммарно по `to`, `cc` и `bcc`, считая на сообщение, а не на весь пакет.
emails[].ccRecipientInput | list[RecipientInput]
По умолчанию отсутствует и засчитывается в те же 50 адресов, что `to` и `bcc`.
emails[].bccRecipientInput | list[RecipientInput]
По умолчанию отсутствует и засчитывается в те же 50 адресов. `Bcc` входит в число имён, которые `headers` задавать не может, так что это единственный способ отправить скрытую копию. Форма заголовка свела бы на нет отдельный конверт для каждого получателя, который и делает адрес скрытым.
emails[].replyToRecipientInput
Куда идут ответы. Применяется после `headers`, так что он перезаписывает `Reply-To`, который вы задали и там, а не добавляет второй.
emails[].subjectstr
Не длиннее 998 символов (предел строки по RFC 5322), по умолчанию пустая строка. Пустая тема переходит к собственной теме шаблона, если `template` её даёт.
emails[].htmlstr
Часть HTML, не длиннее миллиона символов, и именно её видят получатели, когда заданы оба тела. Требуется одно из `html`, `text`, `template` или `draftId`, и элемент без них проваливается как `validation_error` на `html`.
emails[].textstr
Текстовая часть, не длиннее миллиона символов. Можно передать обе, но каждый транспорт на этом пути собирает одно тело из одной строки, так что при наличии `html` побеждает он.
emails[].headersdict[str, str]
Только `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, потому что вторая строка становится вторым заголовком.
emails[].attachmentslist[AttachmentInput]
Не более 20 файлов на сообщение, причём встроенные файлы в сумме не больше 5 МБ после декодирования, считая на сообщение, а не на пакет. `content` передаётся в base64; передайте байты, и клиент закодирует их сам. Запись `{'fileId': ...}` называет файл, уже находящийся в рабочем пространстве, и в лимит встроенных файлов не засчитывается.
emails[].threadIdstr
Ответить в существующую цепочку, не длиннее 256 символов. Транспорт пишет из этого In-Reply-To и References, и именно это помещает ответ в переписку, а не рядом с ней.
emails[].draftIdstr
Отправить содержимое сохранённого черновика под этим конвертом, не длиннее 256 символов. На провод уходят получатели, тема и заголовки, собранные здесь.
emails[].templateEmailSendTemplate
Отрисовать сохранённый шаблон на сервере по идентификатору (`tpl_…`) или slug, где `version` закрепляет ревизию, а `props`/`slots` её заполняют. Разрешается один раз, при приёме элемента, и отклоняется вместе с `html`/`text` и вместе с `draftId`, поскольку каждое из них даёт второй ответ на вопрос, что содержит сообщение.
emails[].scheduledAtdatetime | str
`datetime`, момент в ISO-8601 или длительность вроде `PT1H`; не меньше секунды в будущем и не дальше 365 дней. Элементы планируются независимо, так что один пакет может содержать сотню разных моментов отправки.
emails[].cancellableForSecondsint
Окно отмены в секундах для немедленной отправки, целое от 0 до 900, по умолчанию 0. Любое значение выше 0 отклоняется вместе с `scheduledAt` в том же элементе, поскольку запланированное сообщение и так можно отменить до его ухода.
emails[].trackingTrackingRequest
`opens` и `clicks`, каждое независимо необязательно и каждое переопределяет настройку только для этого сообщения. Опущенный переключатель следует адресу, с которого отправляется сообщение (или catch-all, который его поймал), и он выключен, если этот адрес его не включил.
emails[].tagsdict[str, str]
Не более 10 меток: ключи от 1 до 64 символов из набора `A-Za-z0-9_-` и значения до 256. Возвращаются вместе с сообщением и никогда не интерпретируются: `emails.list` фильтрует по `status`, `from_`, `broadcast_id`, `scheduled_from` и `scheduled_to` и ни по чему больше, так что метку читают у уже имеющегося сообщения, а не используют как способ его найти.
emails[].translateSendTranslateOptions
Отправить этот элемент на другом языке, разрешается в момент приёма, чтобы ушли именно те слова, которые были одобрены. Не более 10 элементов одного пакета могут его нести: каждый тратит несколько вызовов модели, а элементы обрабатываются по порядку, так что более крупный пакет был бы прерван на середине отправки. Сверх этого весь вызов отклоняется как `too_many_items` на `emails`, до того как что-либо отправлено.

Ответ: BatchResultResource

itemslist[BatchItemResource]
По одной записи на каждый вход, в том порядке, в котором вы их отправили. Ничего не откатывается, так что это протокол того, что случилось с каждым сообщением, а не отчёт о транзакции. API отвечает 207 независимо от того, приняты ли все сообщения, часть или ни одного, так что вызов в любом случае возвращает результат, а ветвиться нужно по `status` каждого элемента.
sentint
Сколько элементов было ПРИНЯТО, что не то же самое, сколько ушло. Элемент может быть `ok` и при этом нести `email.status` со значением `failed` или `partial`, потому что, если транспорт отказывает в сообщении после появления строки, это исход доставки, а не отклонённый запрос.
failedint
Сколько записей несут `error`. `failed > 0` даёт список, с которым надо работать, а не повод переслать пакет заново. Принятые сообщения уже ушли.
items[].indexint
Позиция, которую сообщение этой записи занимало в отправленном вами списке. Передаётся и как поле, и как порядок, чтобы код, фильтрующий или сортирующий `items`, всё равно мог сказать, какой вход провалился.
items[].statusLiteral['ok', 'error']
Дискриминант объединения: `ok` несёт `email`, `error` несёт `error`, и ни одна запись не несёт оба.
items[].emailSentEmailResource
Принятое сообщение (только в записи `ok`) в той же форме, что возвращает одиночная отправка. Оно не несёт ключа `tracking`, потому что вовлечённость сообщается позже, и в момент приёма сообщать нечего.
items[].email.replayedbool
True, когда выведенный `Idempotency-Key` совпал с уже существовавшей отправкой, так что нового ничего не отправлено и это исходное сообщение.
items[].errorBatchItemResourceErrorError
Почему именно это сообщение было отклонено (только в записи `error`). Это конверт ошибки API минус `docUrl` и `requestId`: они описывают запрос, а запрос в целом удался.
items[].error.typestr
Категория, по которой клиент может ветвиться: `validation_error`, `permission_error`, `not_found_error`, `conflict_error` и остальные. Набор заморожен и расти не будет, в отличие от `code`.
items[].error.codestr
Конкретный сбой: `from_address_forbidden`, `invalid_email_address`, `too_many_recipients`, `reserved_header`, `message_too_large`, `unknown_parameter`. Набор открытый и пополняемый, так что неизвестный вам код трактуйте как его `type`.
items[].error.messagestr
Одно предложение, написанное для человека, называющее ошибочное значение, если оно есть. Не стабильный идентификатор. Переключайтесь по `code`.
items[].error.paramNotRequired[str]
Поле, которое было отклонено, как путь через точку ВНУТРИ ТОГО сообщения: `to.0`, `from`, `attachments`. Отсутствует, когда сбой не называет поля, и никогда не снабжается позицией в пакете, для которой есть `index`.

Справочник