Отправить письмо
`emails.send`: одно сообщение, сейчас или позже.
emails.send
const email = await openemail.emails.send({ from: { email: '[email protected]', name: 'Acme Billing' }, to: ['[email protected]', 'Grace <[email protected]>'], cc: '[email protected]', bcc: [{ email: '[email protected]' }], replyTo: '[email protected]', subject: 'Your September invoice', html: '<p>Invoice attached.</p>', text: 'Invoice attached.', headers: { 'X-Campaign': 'invoices' }, attachments: [{ filename: 'invoice.pdf', content: pdfBytes }], threadId: 'thread_…', scheduledAt: 'PT1H', tags: { order: '4021' }, tracking: { opens: true, clicks: true },})to, cc и bcc принимают одного получателя или многих, а одиночный оборачивается за вас. Каждый может быть голым адресом, Name <addr@host> или { email, name }.
Параметры
fromRecipientInputобязательно- Отправитель. Просто адрес, `Name <addr@host>` или объект. Должен быть одним из адресов, от которых может отправлять этот ключ. Запасного отправителя нет, потому что запасным вариантом был бы адрес рабочего пространства по умолчанию, а он меняется по мере того, как адреса появляются и исчезают.
toRecipientInput | RecipientInput[]обязательно- Один получатель или много; одиночный оборачивается за вас. Не более 50 суммарно по to, cc и bcc.
ccRecipientInput | RecipientInput[]- Засчитывается в лимит 50 получателей.
bccRecipientInput | RecipientInput[]- Никогда не называется в байтах, которые получает кто-либо ещё, потому что на каждого получателя передаётся свой конверт.
replyToRecipientInput- Один адрес, отправляемый как заголовок Reply-To.
subjectstring- Не длиннее 998 символов — предел строки по RFC 5322. По умолчанию пусто.
htmlstring- Требуется одно из html, text, draftId или template. HTML — это то, что видят получатели, когда заданы и html, и text.
textstring- Текстовая часть.
template{ id, version?, props?, slots? }- Отрисовать сохранённый шаблон на сервере. `version` закрепляет ревизию; опустите её, чтобы использовать то, что опубликовано в момент приёма запроса. Неизвестное или отсутствующее свойство — это 422, а не пустое место в сообщении.
draftIdstring- Отправить сохранённый черновик под этим конвертом.
headersRecord<string, string>- `X-*`, `List-*`, Reply-To, Precedence, Auto-Submitted, Importance, Priority и Feedback-Id. Всё, что транспорт ставит сам, отклоняется, а не отбрасывается тихо.
attachmentsAttachmentInput[]- `{ filename, content, contentType? }` либо `{ fileId }`, называющий файл, уже находящийся в рабочем пространстве. Передайте байты в content, и они будут закодированы в base64 за вас. 20 файлов, причём встроенные ограничены 5 МБ в сумме после декодирования. Сохранённый файл может быть больше и уходит ссылкой на скачивание.
attachmentDeliveryAttachmentDeliveryMode- `mime`, `link` или `auto`. `auto` отправляет файлы ссылками на скачивание, когда они переваливают за 2 МБ на домене с активным доменом файлов, и внутри сообщения в остальных случаях. Если не указано, применяется настройка почтового ящика, а она по умолчанию `auto`.
threadIdstring- Ответить в существующую цепочку. Транспорт пишет In-Reply-To и References.
scheduledAtDate | string- Date, момент в ISO-8601 или длительность вроде `PT1H`. Не дальше года вперёд, никогда в прошлое. Нельзя сочетать с cancellableForSeconds.
cancellableForSecondsnumber- От 0 до 900. Окно отмены у немедленной отправки: механизм отмены из редактора, вынесенный наружу, а не зашитый.
tagsRecord<string, string>- До 10 меток, возвращаются обратно и доступны для фильтрации. Никогда не интерпретируются.
signatureboolean- Несёт ли это сообщение подпись адреса, с которого отправляется, — собственную подпись этого адреса либо ту, что задана для всех адресов. По умолчанию true, потому что подпись принадлежит адресу, а не тому клиенту, который отправил сообщение. Ставьте `false` для почты, которую программа отправляет от чьего-то имени: квитанции, сброса пароля или дайджеста — ни одному из них не нужна человеческая подпись внизу.
tracking{ opens?, clicks? }- Добавлять ли пиксель открытия и переписывать ли ссылки в этом сообщении. Включено, если владелец рабочего пространства не отключил трекинг для адреса, с которого идёт отправка, или для всех адресов, и любое указанное здесь поле решает судьбу одного сообщения независимо от настройки адреса.
translate{ to, from?, subject?, includeOriginal? }- Отправить на языке получателя. `to` принимает код, английское название или собственное название языка; `subject` и `includeOriginal` по умолчанию true. Разрешается при приёме запроса, так что запланированное сообщение несёт одобренные слова. Отклоняется вместе с `draftId`.
Ответ
idstring- Идентификатор отправки, `msg_…`. Используйте его для `get`, `cancel`, `reschedule` и `getTracking`.
statusEmailStatus- queued, scheduled, sending, sent, partial, cancelled или failed. Читайте это, а не сам факт, что промис разрешился. `partial` — самостоятельное состояние: у части получателей сообщение уже есть и его нельзя «разотправить», так что повтор будет ошибкой, а сообщение о неудаче — ложью.
mode'live' | 'test'- Каким видом ключа отправлено. Тестовая отправка записывается и никогда не передаётся.
fromstring- Адрес, который на самом деле был авторизован и поставлен на провод, а это не всегда тот, который запрашивали.
subjectstring | null- Как отправлено.
messageIdstring | null- Message-ID по RFC 5322. Null до появления MIME. Сервис отправки переписывает заголовок на выходе, так что ни один отбой или отчёт о доставке не несёт этого значения. С событием возвращается `id`.
threadIdstring | null- Цепочка, в которую оно попало.
transportstring | null- Как сообщение ушло. Null до отправки.
attemptsnumber- Сколько раз отправка была предпринята.
lastErrorstring | null- Почему последняя попытка не удалась, дословно.
scheduledAtstring | null- Момент в ISO, когда оно должно уйти.
cancellableUntilstring | null- Пока текущий момент раньше этого, отмена ещё работает.
sentAtstring | null- Момент в ISO, когда оно ушло.
tagsRecord<string, string>- То, что вы отправили, возвращённое обратно.
sourceEmailSource- composer, api, mcp, ai или queue: какая поверхность запросила. `api` — это данный клиент.
createdAtstring- Момент в ISO, когда была записана запись.
replayedboolean- True, когда Idempotency-Key совпал с уже существовавшей отправкой. Нового ничего не отправлено, и это исходное сообщение.
translationEmailTranslationResource | undefined- Присутствует только у переведённого сообщения и только там, где несётся весь сохранённый запрос: в этом ответе и в `get`. `{ language, languageName, detectedSourceLanguage, subject, includeOriginal }`, всё кодами, а не строками языков. В строке списка его не бывает никогда, так что его отсутствие там не говорит ничего ни в ту, ни в другую сторону.
На языке получателя
translate пишет сообщение на чужом языке перед его уходом. Тело, а также тема, если вы это не отключите, переводятся в момент приёма запроса, и что получилось, то и уходит: перевод, который не удалось получить, отклоняет отправку, а не отправляет письмо на том языке, на котором вы его написали.
const email = await openemail.emails.send({ from: '[email protected]', to: '[email protected]', subject: 'Your September invoice', html: '<p>Invoice attached. Payment is due on the 14th.</p>', translate: { to: 'de' },}) console.log(email.translation)// { language: 'de', languageName: 'German', detectedSourceLanguage: 'en', subject: true, includeOriginal: true }Никто это не прочитал перед отправкой. emails.translate — тот же круг, остановленный на шаг раньше. Покажите результат человеку, дайте ему поправить и отправьте одобренное вообще без translate в вызове. Передача его снова перевела бы текст второй раз и выбросила бы их правки.
const preview = await openemail.emails.translate({ subject: 'Your September invoice', html: '<p>Invoice attached. Payment is due on the 14th.</p>', to: 'de',}) console.log(preview.language.native, preview.detectedSourceLanguage) const approved = await showToSomebody(preview) await openemail.emails.send({ from: '[email protected]', to: '[email protected]', subject: approved.subject, html: approved.html,})import { LANGUAGES, isRtlLanguage, languageByCode, openemail, resolveLanguage } from '@openemail/sdk' LANGUAGES.length // 200 const current = await openemail.languages.list() resolveLanguage('Deutsch')?.code // 'de'resolveLanguage('zh-TW')?.code // 'zh-Hant'languageByCode('DE')?.native // 'Deutsch'isRtlLanguage('ar') // trueТаблица входит в пакет, в порядке для списка выбора, так что список можно заполнить ещё до первого запроса. languages.list() разрешается в те же строки с провода, что и обычный массив, — для того, кто предпочитает текущие языки тем, с которыми вышла эта версия. resolveLanguage принимает код, английское название, эндоним или псевдоним (zh-TW — псевдоним кода, которого больше нет в списке), languageByCode сопоставляет точный код без учёта регистра, а шестнадцать строк — справа налево. Ищите по native, label и code вместе, показывайте native первым и храните код.
emails.translate не повторяется автоматически. Он тратит вызовы модели и ничего не пишет, так что делать идемпотентным нечего, а повтор после неотвеченного запроса лишь купил бы тот же ответ дважды.
- Язык, который ни во что не разрешается, — это
validation_errorнаtranslate.to, до того как что-либо отправлено. translation_too_longсвыше 30 000 символов,translation_not_configured, когда в установке не настроен ИИ, иtranslation_failed, когда провайдер не ответил. Ни одна из них не отправляет сообщение непереведённым в качестве запасного варианта.- Работает с
template: переводится ОТРИСОВАННЫЙ результат, так что одно сохранённое тело обслуживает все языки, на которых читают ваши клиенты. Шаблон, отрисовывающий целый документ, сохраняет свой doctype, блоки<style>и правила@font-face: к модели уходит только тело, а остальное возвращается вокруг него. Его<title>остаётся нетронутым — его всё равно нигде не показывают. - Повтор не стоит ничего сверху. Перевод не входит в отпечаток идемпотентности (в него входит запрос, включая
translate), так что повтор неотвеченной отправки с тем жеIdempotency-Keyвоспроизводит уже существующее сообщение, а не переводит и отправляет второе. - Переведённое сообщение в состоянии queued или scheduled заморожено против изменений формулировок.
emails.rescheduleвсё ещё может его перенести; изменить сказанное — значит отменить и отправить заново.
Вложения
content на проводе в base64. Передайте байты, и они будут закодированы за вас.
attachments: [ { filename: 'invoice.pdf', content: pdfBytes, contentType: 'application/pdf' },]toBase64 экспортируется, если он понадобится вам в другом месте. Он работает по частям, чего btoa(String.fromCharCode(...bytes)) не делает. Тот падает на всём, что больше примерно 100 кБ, и падает на настоящем файле, а не на том, на котором вы тестировали.