Планирование и отмена
`scheduledAt`, `emails.reschedule` и `emails.cancel`.
Отправка позже
from datetime import datetime, timezone from openemail import openemailfrom openemail.types import EmailSend message: EmailSend = {'from': sender, 'to': recipient, 'subject': subject, 'text': text} openemail.emails.send({**message, 'scheduledAt': 'PT1H'})openemail.emails.send({**message, 'scheduledAt': datetime(2027, 1, 1, 9, 0, tzinfo=timezone.utc)})openemail.emails.send({**message, 'scheduledAt': '2027-01-01T09:00:00.000Z'})datetime, момент в ISO-8601 или длительность вроде PT1H / P2D. Не дальше года вперёд и никогда в прошлое.
datetime отправляется в UTC, а наивный сначала читается как местное время этой машины, так же как его читает astimezone, поэтому передавайте объект с часовым поясом, когда пояс важен. timedelta не принимается: прибавьте его к datetime.now(timezone.utc) или отправьте длительность вроде PT1H.
Перенос и остановка
from datetime import datetime, timedelta, timezone from openemail import openemailfrom openemail.types import EmailSend message: EmailSend = {'from': sender, 'to': recipient, 'subject': subject, 'text': text} queued = openemail.emails.send({**message, 'scheduledAt': 'PT1H'}) openemail.emails.reschedule(queued['id'], datetime.now(timezone.utc) + timedelta(days=1))openemail.emails.cancel(queued['id'])Остановить можно только сообщения в состоянии queued и scheduled; всё, что прошло дальше, даёт conflict_error, потому что часть его уже лежит у кого-то в почтовом ящике. Отмена уже отменённого сообщения удаётся и ничего не меняет.
Окно отмены вместо этого
from openemail import openemailfrom openemail.types import EmailSend message: EmailSend = {'from': sender, 'to': recipient, 'subject': subject, 'text': text} openemail.emails.send({**message, 'cancellableForSeconds': 30})Запланированное сообщение и так можно отменить до его ухода, поэтому эти два нельзя сочетать, и сервер это отклоняет. Используйте это для окна отмены у немедленного сообщения.
Параметры: планирование
scheduledAtdatetime | str- Когда отправлять, в `emails.send`: `datetime`, момент в ISO-8601 или длительность вроде `PT1H` или `P2D`, которую клиент приводит к строке для провода. Не меньше секунды в будущем и не дальше 365 дней, причём выход за любую границу даёт `validation_error` на `scheduledAt`; естественный язык не принимается, потому что неверно разобранное «в следующий вторник» отправит сообщение в момент, который уже не отозвать.
cancellableForSecondsint- Окно отмены у НЕМЕДЛЕННОЙ отправки: целое от 0 до 900, по умолчанию 0. Любое значение выше 0 отклоняется вместе с `scheduledAt`, которое и так отменяемо до ухода, а удержанное так сообщение стоит в `queued`, а не в `scheduled`. Это тот же механизм отсрочки с короткой задержкой.
idstrобязательно- Идентификатор `msg_…`, первый аргумент и у `emails.cancel`, и у `emails.reschedule`. Обоим нужна `emails:send`, а не собственная область доступа, и оба разрешаются внутри рабочего пространства самого ключа, так что идентификатор из чужого даёт `not_found_error`, ровно как идентификатор, которого никогда не было.
scheduled_atdatetime | strобязательно- Новое время, второй аргумент `emails.reschedule`, разбираемое по тем же правилам и в том же годовом окне, и единственное, что изменит лежащий в основе `PATCH /emails/{id}`. Длительность отсчитывается от момента, когда её читает СЕРВЕР, так что повторённый перенос приземляется чуть позже, чем приземлился бы первый: позже, но никогда не раньше.
Ответ: EmailResource
objectLiteral['email']- Всегда `email`. Оба вызова отвечают целым сообщением, а не подтверждением, так что ничего не нужно запрашивать заново, чтобы увидеть, что изменилось; `emails.send` возвращает ту же форму плюс `replayed`.
idstr- Ручка `msg_…`. Стабильна всё время жизни сообщения и принимается каждым другим вызовом по нему.
statusEmailStatus- `cancelled` после отмены и `scheduled` после переноса, в том числе для сообщения, которое было лишь `queued` за окном отмены. Перенос превращает его в настоящее расписание. Перенести или остановить можно только сообщения в состоянии `queued` и `scheduled`; всё, что прошло дальше, даёт `conflict_error` с кодом `email_not_cancellable`, потому что часть его уже лежит у кого-то в почтовом ящике.
scheduledAtstr | None- Момент в ISO, когда сообщение должно быть отправлено. Задаётся и для окна отмены, и для отправки с `scheduledAt`, поскольку это один механизм, и null у обычной немедленной отправки.
cancellableUntilstr | None- Когда отмена перестаёт работать. Это тот же момент, что и `scheduledAt`, на обоих отложенных путях. Null у немедленной отправки, которая к моменту возврата из вызова уже ушла.
sentAtstr | None- Когда сообщение действительно ушло. Null, пока оно ждёт, и навсегда null у отменённого.
messageIdstr | None- Message-ID по RFC 5322, null до появления MIME, а значит всегда null у сообщения, с которым эти два вызова могут что-то сделать. Не то, чем следует обращаться к API, и не то, с чем вернётся позднейший отбой: сервис отправки переписывает заголовок на выходе.
threadIdstr | None- Цепочка, которой принадлежит сообщение, взятая из запроса и переписанная тем, что сообщит транспорт после отправки. Null, когда это не ответ.
transportEmailTransport | str | None- Как ушли байты, и null до отправки, а значит null у каждого сообщения, которое может вернуть отмена или перенос. Отправка в тестовом режиме записывает `test`, а объединение остаётся открытым, чтобы транспорт, которого этот SDK ещё не называет, не был ломающим изменением.
attemptsint- Сколько раз отправка забирала эту строку. Увеличивается при захвате, а не при успешной отправке, и равно 0 для всего, что ещё ждёт.
lastErrorstr | None- Последний сбой, записанный для сообщения, null, пока ничего не падало. Отложенная отправка, задание для которой не удалось поставить в очередь, записывается здесь как `Could not schedule: …` и переводится в `failed`, и это единственный способ, которым запланированное сообщение перестаёт быть отменяемым без чьей-либо просьбы.
fromstr- Адрес, под которым сообщение было авторизовано уйти: отправленный `from`, хранимый голым и в нижнем регистре. Любое отображаемое имя здесь отбрасывается, потому что фильтр `from_` у `emails.list` сравнивает на равенство.