Планирование и отмена
`scheduledAt`, `emails->reschedule`, `emails->update` и `emails->cancel`.
Отправка позже
$message = [ 'from' => '[email protected]', 'to' => '[email protected]', 'subject' => 'Your September invoice', 'text' => 'Invoice attached.',]; $client->emails->send([...$message, 'scheduledAt' => 'PT1H']);$client->emails->send([...$message, 'scheduledAt' => new \DateTimeImmutable('2027-01-01 09:00', new \DateTimeZone('UTC'))]);$client->emails->send([...$message, 'scheduledAt' => '2027-01-01T09:00:00.000Z']);DateTimeInterface, момент ISO 8601 в виде строки или длительность вроде PT1H или P2D. Не дальше чем на год вперёд и никогда в прошлом. Если распаковать сообщение, собранное ранее, в новый массив с scheduledAt рядом, поле добавится, а оригинал останется нетронутым.
DateTimeInterface отправляется как момент в UTC, в каком бы часовом поясе он ни был создан, поэтому 09:00 в Europe/London уходит как тот момент, который оно обозначает. Строка с датой без времени, например 2027-01-01, читается как полночь UTC в этот день, поэтому передавайте момент времени, когда важен час.
Перенос и остановка
$message = [ 'from' => '[email protected]', 'to' => '[email protected]', 'subject' => 'Your September invoice', 'text' => 'Invoice attached.',]; $queued = $client->emails->send([...$message, 'scheduledAt' => 'PT1H']); $client->emails->reschedule($queued['id'], new \DateTimeImmutable('+1 day'));$client->emails->cancel($queued['id']);Остановить можно только сообщения в состоянии queued и scheduled. Для всего, что продвинулось дальше, выбрасывается ConflictException, потому что часть уже лежит в чьём-то почтовом ящике. Отмена уже отменённого сообщения проходит успешно и ничего не меняет.
Чтобы найти, что ждёт отправки в определённом окне, запросите список с status: ['scheduled', 'queued'], scheduledFrom: и scheduledTo:, как это делает календарь приложения.
Изменение до отправки
emails->update меняет сообщение, которое ещё не ушло: когда оно уйдёт (через scheduledAt), что в нём сказано (через subject, html и text), от имени какого адреса оно уйдёт (через from) и кому (через to, cc и bcc). Передавайте любые из них вместе, а поле, которое вы не указали, сохраняет своё значение. Список получателей заменяет сохранённый целиком. Именно это делает редактирование запланированного сообщения в календаре приложения.
$updated = $client->emails->update('msg_3f9a1c07d2b84e6a9c5b1f20', [ 'subject' => 'Your September invoice, corrected', 'to' => ['[email protected]', '[email protected]'], 'scheduledAt' => new \DateTimeImmutable('2026-10-05 08:00', new \DateTimeZone('UTC')),]); echo $updated['status'], ' ', $updated['subject'], ' ', $updated['scheduledAt'], PHP_EOL;from проверяется так же, как при отправке, поэтому это должен быть адрес, от имени которого ключу разрешено отправлять. Сообщение, переведённое при принятии, сохраняет одобренный текст, поэтому новый subject, html или text для него даёт 409 translation_locked, а сообщение, зашифрованное до планирования, сохраняет и текст, и получателей. Такие сообщения вместо этого отмените и отправьте заново.
Окно отмены вместо этого
$message = [ 'from' => '[email protected]', 'to' => '[email protected]', 'subject' => 'Your September invoice', 'text' => 'Invoice attached.',]; $held = $client->emails->send([...$message, 'cancellableForSeconds' => 30]); echo $held['status'], ' ', $held['cancellableUntil'], PHP_EOL;Запланированное сообщение и так можно отменить до его ухода, поэтому эти два нельзя сочетать, и сервер это отклоняет. Используйте это для окна отмены у немедленного сообщения.
Параметры: планирование
scheduledAtDateTimeInterface or string- Когда отправить, в `emails->send`: `DateTimeInterface`, момент ISO 8601 в виде строки или длительность вроде `PT1H` или `P2D`. `DateTimeInterface` отправляется как момент в UTC, строка как есть, а строка с датой без времени означает полночь UTC. Не раньше чем через одну секунду и не позже чем через 365 дней, а выход за любую границу даёт `validation_error` для `scheduledAt`. Естественный язык не принимается, потому что неверно разобранное «в следующий вторник» отправит сообщение в момент, который уже не вернуть.
cancellableForSecondsint- Окно отмены отправки для НЕМЕДЛЕННОЙ отправки: int от 0 до 900, по умолчанию 0. Любое значение больше 0 отклоняется вместе с `scheduledAt`, которое и так можно отменить, пока сообщение не ушло, а сообщение, задержанное таким образом, находится в состоянии `queued`, а не `scheduled`. Это тот же механизм отложенной отправки с короткой задержкой.
$idstringобязательно- Идентификатор `msg_` и первый аргумент `emails->cancel`, `emails->reschedule` и `emails->update`. Им нужна область `emails:send`, а не собственная, и они ищут идентификатор в рабочем пространстве самого ключа, поэтому идентификатор из другого пространства даёт `not_found_error` точно так же, как идентификатор, которого никогда не существовало.
$scheduledAtDateTimeInterface or stringобязательно- Новое время, второй аргумент `emails->reschedule`, читается по тем же правилам и в том же окне в один год. Это единственное, что меняет `reschedule`, и клиент больше ничего не отправляет. Длительность отсчитывается от момента, когда её прочитал СЕРВЕР, поэтому повторённый перенос сработает чуть позже, чем сработал бы первый: позже, но никогда не раньше.
apiKeystring- Именованный аргумент в любом из трёх вызовов. Действует с этим ключом вместо ключа клиента.
Ответ
cancel, reschedule и update каждый возвращают всё сообщение как массив с ключами по именам API в camelCase.
objectstring- Всегда `email`. Эти вызовы отвечают всем сообщением, а не подтверждением, поэтому, чтобы увидеть изменения, ничего не нужно запрашивать заново. `emails->send` возвращает ту же форму плюс `replayed`.
idstring- Дескриптор `msg_`. Стабилен на протяжении всей жизни сообщения, и именно этот идентификатор принимает любой другой вызов для него.
statusstring- `cancelled` после отмены и `scheduled` после переноса, в том числе для сообщения, которое было лишь в состоянии `queued` за окном отмены: перенос превращает его в настоящее расписание. Переносить или останавливать можно только сообщения в состоянии `queued` и `scheduled`. Для всего, что продвинулось дальше, выбрасывается `ConflictException`, у которого `errorCode` равен `email_not_cancellable`, потому что часть уже лежит в чьём-то почтовом ящике.
scheduledAtstring or null- Момент ISO 8601, когда сообщение должно быть отправлено. Задаётся и для окна отмены, и для отправки с `scheduledAt`, поскольку это один механизм, и равен null при обычной немедленной отправке.
cancellableUntilstring or null- Когда отмена перестаёт работать. На обоих отложенных путях это тот же момент, что и `scheduledAt`. null при немедленной отправке, которая уже ушла к моменту возврата из вызова.
sentAtstring or null- Когда сообщение действительно ушло. null, пока оно ждёт, и null навсегда у отменённого.
messageIdstring or null- Message-ID по RFC 5322, null, пока не существует MIME, поэтому у сообщения, с которым могут работать эти вызовы, он всегда null. Его нельзя использовать для обращения к API, и с ним не возвращается более поздний отказ: сервис отправки переписывает заголовок на выходе.
threadIdstring or null- Цепочка, к которой относится это сообщение. Берётся из запроса и перезаписывается тем, что сообщит транспорт после отправки. null, если это не ответ.
transportstring or null- Каким путём ушли байты. null до отправки, поэтому null у каждого сообщения, которое может вернуть отмена или перенос. Отправка в тестовом режиме записывает `test`, а может появиться и транспорт, которого этот пакет пока не называет, поэтому считайте незнакомое значение информацией, а не ошибкой.
attemptsint- Сколько раз отправка захватывала эту запись. Растёт с каждым захватом, а не с каждой успешной отправкой, и равно 0 для всего, что ещё ждёт.
lastErrorstring or null- Последний сбой, записанный для сообщения, null, пока ничего не сломалось. Отложенная отправка, задание которой не удалось поставить в очередь, записывается здесь как `Could not schedule: …` и переводится в `failed`. Это единственный способ, которым запланированное сообщение перестаёт быть отменяемым без чьей-либо просьбы.
fromstring- Адрес, от имени которого сообщению было разрешено уйти: отправленный `from`, сохранённый голым и в нижнем регистре. Отображаемое имя здесь отбрасывается, потому что фильтр `from:` в `emails->list` сравнивает на равенство.