Przejdź do dokumentacji
API

Wyślij wiadomość

POST /emails: jedna wiadomość, teraz albo później.

POSTapi.openemail.uk/emails

Uruchamia prawdziwe wywołanie na twojej przestrzeni roboczej, twoim własnym kluczem.

Żądanie

from jest wymagane. W przeciwieństwie do edytora wiadomości nie ma nadawcy zastępczego, bo tym zastępczym nadawcą jest domyślny adres obszaru roboczego, który zmienia się niepostrzeżenie, gdy adresy pojawiają się i znikają.

PoleWymaganeUwagi
fromtakSam adres albo Name <addr>. Musi być adresem, z którego klucz może wysyłać.
totakŁącznie do 50 odbiorców w to, cc i bcc.
cc, bccnieOdbiorcy bcc nigdy nie są wymieniani w bajtach, które otrzymuje ktokolwiek inny.
subjectnieDomyślnie pusty.
html, textjedno zOba naraz są w porządku. To HTML widzą odbiorcy.
templatejedno z{ id, version?, props?, slots? }. Zapisana treść, po id albo po slugu. Odrzucane razem z html, text lub draftId. Zobacz Wysyłka z szablonem.
replyToniePojedynczy adres.
headersnieX-*, List-*, Reply-To, Precedence, Auto-Submitted, Importance, Priority, Feedback-Id.
attachmentsnie{ filename, content, contentType } w base64, łącznie 5 MB, albo { fileId } wskazujące plik już obecny w przestrzeni roboczej. 20 plików.
attachmentDeliveryniemime, link albo auto. auto linkuje pliki, gdy przekroczą 2 MB, na domenie z aktywną domeną plików. Domyślnie ustawienie skrzynki.
threadIdnieOdpowiedź w istniejącym wątku.
draftIdnieWysyłka istniejącej wersji roboczej.
scheduledAtnieMoment ISO albo czas trwania. Zobacz Planowanie.
cancellableForSecondsnieOkno cofnięcia od 0 do 900 sekund przy wysyłce natychmiastowej. Odrzucane razem ze scheduledAt, którą i tak można anulować do chwili wysłania. Zobacz Planowanie.
signatureniefalse pomija podpis w tej wiadomości. W przeciwnym razie niesie ona podpis adresu, z którego jest wysyłana — własny podpis tego adresu albo ten ustawiony dla Wszystkich adresów.
tagsnieDo 10 własnych etykiet. Zwracane z powrotem, nigdy interpretowane.
trackingnie{ opens?, clicks? }. Każde z nich nadpisuje ustawienie dla tej wiadomości; pomiń pole, a ta połowa spadnie do ustawienia adresu, z którego wysyłasz, a dalej do Wszystkich adresów — i jest włączona, chyba że któreś z nich ją wyłączyło.
translatenie{ to, from?, subject?, includeOriginal? }. Wysyła wiadomość w języku odbiorcy. Rozstrzygane w chwili przyjęcia żądania, odrzucane razem z draftId.

Nieznane pola są odrzucane, a nie ignorowane, więc źle zapisana nazwa daje 422 teraz, zamiast niespodzianki później. Nagłówki, które podważyłyby autoryzację nadawcy (From, Sender, Bcc, Message-ID, Return-Path i inne), są odrzucane z reserved_header.

Odpowiedź

200, gdy wiadomość już poszła, 202, gdy coś jeszcze musi się z nią wydarzyć. Wywołujący, który rozgałęzia się na kodzie stanu, ma rację w obu przypadkach.

200 OK
{  "object": "email",  "id": "msg_c5f21cc6bfec4e848caf905b",  "status": "sent",  "mode": "live",  "from": "[email protected]",  "subject": "Your September invoice",  "messageId": "<2598…@acme.com>",  "transport": "ses",  "sentAt": "2026-08-29T08:19:08.000Z",  "source": "api",  "replayed": false}

id to trwały uchwyt, który zachowujesz, i ten, na który wraca zdarzenie dostarczenia, ponieważ webhook o odbiciu nazywa go emailId. messageId to Message-ID zgodny z RFC 5322 i jest null, dopóki nie powstanie MIME. Nie koreluj po nim: usługa wysyłkowa przepisuje ten nagłówek w drodze na zewnątrz, więc wartość podana tutaj nie pojawia się w żadnym raporcie o odbiciu ani dostarczeniu, a dopasowanie po niej nigdy nie zadziała.

W języku odbiorcy

translate zapisuje wiadomość w cudzym języku, zanim ona wyjdzie. Treść, a jeśli tego nie wyłączysz — także temat, są tłumaczone w momencie, w którym żądanie zostaje PRZYJĘTE. To ta sama reguła, którą stosuje template, i jest nośna z tych samych powodów: zaplanowana wiadomość niesie słowa, które zostały zatwierdzone, a nie to, co model wyprodukuje we wtorek, a tłumaczenie, którego nie udało się uzyskać, odrzuca wysyłkę, zanim powstanie jakikolwiek wiersz. Nic nie zostaje dostarczone w języku, którego nadawca nie wybrał.

translate

tostringwymagane
Język, w którym ma powstać tekst: kod BCP-47 (`de`), nazwa angielska („German”) albo własna nazwa języka („Deutsch”), od 2 do 60 znaków. Wszystkie trzy formy są normalizowane do kodu z tabeli, zanim wydarzy się cokolwiek innego, więc stanowią jedno żądanie — co ma znaczenie, bo odcisk Idempotency-Key liczony jest z przetworzonego żądania. Aliasy również się rozwiązują: `zh-TW` staje się `zh-Hant`. Taki, który nie rozwiązuje się do niczego, daje 422 na `translate.to`.
fromstring
To, w czym napisałeś tekst, w dowolnej z tych samych trzech form. Czysta optymalizacja. Pominięte — treść zostaje odczytana, a język ustalony, co kosztuje jedno krótkie wywołanie modelu. Warto je podać na ścieżce o dużym wolumenie i warto podać je wtedy, gdy treść to głównie nazwiska, liczby i odnośniki: wykrywanie raczej wstrzymuje się od odpowiedzi, niż zgaduje, a nieustalone źródło nie kosztuje cię nic poza nazwą języka w podpisie nad twoim oryginałem. To nie jest `from` z najwyższego poziomu, które jest adresem.
subjectboolean
Przetłumacz także temat wiadomości. Domyślnie true; false wysyła temat dokładnie tak, jak go napisałeś.
includeOriginalboolean
Umieść to, co faktycznie napisałeś, pod tłumaczeniem, za separatorem i z podpisem w języku odbiorcy. Domyślnie true i warto to zostawić włączone. To jedyna rzecz, która pozwala czytającemu sprawdzić zdanie brzmiące dziwnie, zamiast prosić go o zaufanie modelowi, którego wyniku żadne z was nie widzi.
curl
curl -X POST "$OE/emails" -H "$AUTH" -H "Content-Type: application/json" \  -d '{    "from": "[email protected]",    "to": ["[email protected]"],    "subject": "Your September invoice",    "html": "<p>Invoice attached. Payment is due on the 14th.</p>",    "translate": { "to": "de" }  }'
200 OK
{  "object": "email",  "id": "msg_c5f21cc6bfec4e848caf905b",  "status": "sent",  "from": "[email protected]",  "subject": "Ihre Rechnung für September",  "translation": {    "language": "de",    "languageName": "German",    "detectedSourceLanguage": "en",    "subject": true,    "includeOriginal": true  }}

translation jest dodatkowe i pojawia się tylko przy wiadomości, która została przetłumaczona: w tej odpowiedzi i w GET /emails/{id}, nigdy w wierszu listy, ponieważ lista nie pobiera zapisanego żądania, a jej milczenie w tym miejscu nie mówi nic w żadną stronę. Niesie kody, a nie całe wiersze języków: jest zapisem tego, co zrobiono, a endonim mieszka w GET /languages. subject w odpowiedzi jest tym przetłumaczonym, więc konsola nigdy nie wypisze wiadomości pod ciągiem, którego odbiorca nie widział.

  • Działa z template i to jest przypadek użyteczny: tłumaczony jest WYRENDEROWANY wynik, więc jedna zapisana treść obsługuje każdy język, w którym czytają twoi klienci. Szablon renderujący cały dokument jest najpierw rozbierany: do modelu trafia tylko to, co znajduje się wewnątrz <body>, a doctype, bloki <style> i reguły @font-face są z powrotem doklejane wokół odpowiedzi. Dlatego też limit 30 000 znaków mierzy prozę, a nie dokument: dwuwierszowa wiadomość owinięta w firmowy arkusz stylów jest dwuwierszową wiadomością.
  • Jedyną częścią szablonu pozostawioną bez tłumaczenia jest jego <title>, którego nie wyświetla żaden klient poczty. <Preview> z react-email renderuje się do treści i jest tłumaczone razem z resztą.
  • Odrzucane razem z draftId: 422 na translate, z komunikatem „A draft is sent as it was written; translate a body or send a draft, not both”. Szkic napisał człowiek i jest wysyłany w takiej postaci, w jakiej go zostawił.
  • Celowo nie wchodzi w skład odcisku idempotencji. Haszowane jest żądanie, które wysłałeś, wraz z translate; to, co wyprodukował model, już nie. Dlatego ponowienie nieodpowiedzianej wysyłki z tym samym Idempotency-Key odtwarza oryginał. Wraca wiadomość, która już istnieje, bez drugiej wysyłki i bez drugiego tłumaczenia. Haszowanie samego brzmienia sprawiłoby, że uczciwe ponowienie za każdym razem dawałoby inny odcisk — a właśnie tak ta sama wiadomość wychodzi dwa razy.
  • Przetłumaczona wiadomość, która czeka w kolejce lub jest zaplanowana, jest zamrożona wobec zmian brzmienia. Przesuń ją albo anuluj; zmiana tego, co mówi, oznacza anulowanie i ponowną wysyłkę, przy kimś, kto potrafi przeczytać nowe słowa.
  • Język pisany od prawej do lewej jest produkowany od prawej do lewej: tłumaczenie owinięte w dir="rtl", twój oryginał poniżej zorientowany po swojemu. Atrybut przechodzi przez wychodzący sanitizer, który dopuszcza dir dokładnie z tego powodu, więc wiadomość w transmisji niesie kierunek pokazany w podglądzie.
KodStatusKiedy
`invalid_parameter`422translate.to albo translate.from wskazuje język, którego nie potrafimy umiejscowić. Komunikat podaje, które trzy formy są akceptowane, i kieruje do GET /languages.
`unknown_language`422Ta sama awaria wychwycona o krok później, przez usługę, a nie przez schemat. Zabezpieczenie, na translate.to.
`translation_too_long`422Powyżej 30 000 znaków po którejkolwiek stronie wywołania modelu. Odmowa zamiast obcięcia: połowa przetłumaczonej wiadomości nie ma szwu, który pokazywałby, gdzie się urwała, a czytający działa na podstawie tej połowy, którą dostał.
`translation_not_configured`409Przestrzeń robocza nie ma klucza AI, a platformowe AI jest wyłączone. 409, a nie 503, ponieważ ponowienie zawiedzie identycznie. Nic nie zostało wysłane. Wyślij bez translate, jeśli chciałeś wysłać tekst tak, jak został napisany.
`translation_failed`503Dostawca nie odpowiedział albo odpowiedział czymś nieużywalnym. Nic nie zostało wysłane; wiadomość nigdy nie jest awaryjnie nadawana bez tłumaczenia. Ten błąd jest po naszej stronie i warto go ponowić.
`unknown_parameter`422Nierozpoznany klucz wewnątrz translate, które jest obiektem ścisłym, jak reszta żądania.

Wysyłka z kodu nie ma nikogo, kto najpierw przeczyta tłumaczenie. POST /emails/translate to ta sama podróż w obie strony zatrzymana o krok wcześniej, po to, by pokazać człowiekowi, co za chwilę wyśle. Potem wyślij to, co zatwierdził, jako zwykłe html/subject, bez translate w żądaniu w ogóle.