Saltar para a documentação
Python

Enviar um email

`emails.send`: uma mensagem, agora ou mais tarde.

emails.send

send_email.py
from openemail import openemail email = 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': pdf_bytes}],    'threadId': 'thread_…',    'scheduledAt': 'PT1H',    'tags': {'order': '4021'},    'tracking': {'opens': True, 'clicks': True},})

to, cc e bcc aceitam um ou vários destinatários, e um único é envolvido num array automaticamente. Cada um pode ser um endereço simples, Name <addr@host> ou {'email': ..., 'name': ...}.

Parâmetros

fromRecipientInputobrigatório
O remetente. Um endereço simples, `Name <addr@host>`, ou um dicionário. Tem de ser um a partir do qual esta chave possa enviar. Não há remetente alternativo, por isso um envio indica sempre o endereço de onde sai.
toRecipientInput | list[RecipientInput]obrigatório
Um ou vários destinatários; um único é envolvido num array automaticamente. No máximo 50 no total de to, cc e bcc.
ccRecipientInput | list[RecipientInput]
Conta para o limite de 50 destinatários.
bccRecipientInput | list[RecipientInput]
Nunca aparece nos bytes que qualquer outra pessoa recebe, porque é transmitido um envelope por destinatário.
replyToRecipientInput
Um único endereço, enviado como cabeçalho Reply-To.
subjectstr
No máximo 998 caracteres, o limite de linha do RFC 5322. Vazio por predefinição.
htmlstr
É obrigatório um de html, text, draftId ou template. O HTML é o que os destinatários veem quando html e text são ambos fornecidos.
textstr
A parte em texto simples.
templateEmailSendTemplate
Renderizar um modelo guardado no servidor. `version` fixa a versão; omita-o para usar a que estiver publicada quando o pedido for aceite. Uma prop desconhecida ou em falta dá 422 em vez de um espaço em branco na mensagem.
draftIdstr
Enviar um rascunho guardado com este envelope.
headersdict[str, str]
`X-*`, `List-*`, Reply-To, Precedence, Auto-Submitted, Importance, Priority e Feedback-Id. Tudo o que o transporte define por si próprio é recusado em vez de ser descartado silenciosamente.
attachmentslist[AttachmentInput]
`{'filename': ..., 'content': ...}` com um `'contentType'` opcional, ou `{'fileId': ...}`, que refere um ficheiro já existente no espaço de trabalho, como um de `files.upload`. Passe bytes como conteúdo e são codificados em base64 automaticamente. 20 ficheiros, com os ficheiros inline limitados a 5 MB no total depois de descodificados. Um ficheiro guardado pode ser maior e segue como ligação de transferência.
attachmentDeliveryAttachmentDeliveryMode
`mime`, `link` ou `auto`. `auto` envia os ficheiros como ligações de transferência quando ultrapassam 2 MB num domínio com um domínio de ficheiros ativo, e dentro da mensagem nos restantes casos. Se for omitido, aplica-se a definição da caixa de correio, cuja predefinição é `auto`.
threadIdstr
Responder numa conversa existente. O transporte escreve In-Reply-To e References.
scheduledAtdatetime | str
Um `datetime`, um instante ISO-8601 ou uma duração como `PT1H`. Até um ano à frente, nunca no passado. Não pode ser combinado com cancellableForSeconds.
cancellableForSecondsint
De 0 a 900. Uma janela para anular num envio imediato: o mecanismo de anulação do editor de mensagens, exposto em vez de fixo no código.
tagsdict[str, str]
Até 10 etiquetas, devolvidas e filtráveis. Nunca interpretadas.
signaturebool
Se esta mensagem leva a assinatura do endereço de onde é enviada: a dele, senão a do catch-all para um endereço que um catch-all apanhou, senão o rodapé do OpenEmail, a menos que esse endereço o tenha desligado. Se for omitido, um corpo `html` sai exatamente como foi escrito, sem assinatura, e um corpo só `text` leva-a. Defina `False` no correio que um programa envia em nome de alguém, como um recibo, uma reposição de palavra-passe ou um resumo, nenhum dos quais quer a assinatura de uma pessoa por baixo.
trackingTrackingRequest
Indica se deve ser adicionado um píxel de abertura e se as ligações desta mensagem devem ser reescritas. Inativo a menos que o rastreio tenha sido ativado para o endereço a partir do qual é enviada (ou para o catch-all que o apanhou), e qualquer um dos campos indicados aqui decide para essa mensagem, independentemente da definição do endereço.
translateSendTranslateOptions
Enviar na língua do destinatário. `to` aceita um código, um nome em inglês ou o nome da língua na própria língua; `subject` e `includeOriginal` são ambos true por predefinição. Resolvido quando o pedido é aceite, pelo que uma mensagem agendada leva as palavras que foram aprovadas. Recusado em conjunto com `draftId`.

Resposta

idstr
O id do envio, `msg_…`. Use-o para `get`, `cancel`, `reschedule` e `get_tracking`.
statusEmailStatus
queued, scheduled, sending, sent, partial, bounced, cancelled ou failed. Leia este campo em vez de se basear no facto de a chamada ter terminado. `partial` é um estado próprio: alguns destinatários já a têm e não é possível anular o envio, pelo que tentar de novo é errado e reportar falha é falso.
modeApiKeyMode
Que tipo de chave o enviou. Um envio de teste é registado e nunca transmitido.
fromstr
O endereço efetivamente autorizado e colocado na linha, que nem sempre é o que foi pedido.
subjectstr | None
Tal como foi enviado.
messageIdstr | None
O Message-ID RFC 5322. Null até o MIME existir. O serviço de envio reescreve o cabeçalho à saída, por isso nenhuma devolução nem relatório de entrega transporta este valor. É em `id` que um evento regressa.
threadIdstr | None
A thread em que ficou.
transportEmailTransport | str | None
Como a mensagem saiu. Null até ao despacho.
attemptsint
Quantas vezes o despacho foi tentado.
lastErrorstr | None
Porque falhou a última tentativa, literalmente.
scheduledAtstr | None
Instante ISO em que deve sair.
cancellableUntilstr | None
Enquanto o momento atual for anterior a este, cancel continua a funcionar.
sentAtstr | None
Instante ISO em que saiu.
tagsdict[str, str]
O que enviou, devolvido tal e qual.
sourceEmailSource | str
composer, api, mcp, ai, oauth ou form: que superfície o pediu. `api` é este cliente com uma chave de API, e `oauth` é este cliente com um token de acesso.
createdAtstr
Instante ISO em que o registo foi escrito.
replayedbool
True quando uma Idempotency-Key correspondeu a um envio que já existia. Nada de novo foi enviado, e esta é a mensagem original.
translationNotRequired[EmailTranslationResource]
Presente apenas numa mensagem que foi traduzida, e apenas onde todo o pedido guardado é transportado: esta resposta e `get`. Um dicionário com `language`, `languageName`, `detectedSourceLanguage`, `subject` e `includeOriginal`, todos códigos em vez de linhas de idioma. Uma linha de listagem nunca o tem, por isso a sua ausência aí não diz nada num sentido nem no outro. Leia-o com `email.get('translation')`.

Na língua do destinatário

translate escreve a mensagem na língua de outra pessoa antes de ela partir. O corpo, e o assunto a menos que o desative, é traduzido quando a API aceita o pedido, e o que saiu é o que vai sair: uma tradução que não pôde ser produzida recusa o envio em vez de o publicar na língua em que o escreveu.

translate.py
from openemail import openemail email = 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'},}) print(email.get('translation'))

Ninguém leu aquilo antes de partir. emails.translate é a mesma ida e volta parada um passo antes. Mostre-a a uma pessoa, deixe-a alterá-la e depois envie o que ela aprovou sem qualquer translate na chamada. Passá-lo outra vez traduziria uma segunda vez e deitaria fora as edições dela.

preview_translation.py
from openemail import openemail preview = openemail.emails.translate({    'subject': 'Your September invoice',    'html': '<p>Invoice attached. Payment is due on the 14th.</p>',    'to': 'de',}) print(preview['language']['native'], preview['detectedSourceLanguage'])print(preview['html']) approved_subject = input(f"Subject [{preview['subject']}]: ") or preview['subject'] or '' openemail.emails.send({    'from': '[email protected]',    'to': '[email protected]',    'subject': approved_subject,    'html': preview['html'] or '',})
render_picker.py
from openemail import LANGUAGES, is_rtl_language, language_by_code, openemail, resolve_language current = openemail.languages.list() german = resolve_language('Deutsch')traditional = resolve_language('zh-TW')upper = language_by_code('DE') assert len(LANGUAGES) == 200assert german is not None and german['code'] == 'de'assert traditional is not None and traditional['code'] == 'zh-Hant'assert upper is not None and upper['native'] == 'Deutsch'assert is_rtl_language('ar')

A tabela vem incluída, pela ordem do seletor, para que um seletor possa ser preenchido antes do primeiro pedido. languages.list() devolve as mesmas linhas vindas da rede sob a forma de uma lista simples, para quem prefira as atuais às que esta versão trouxe. resolve_language aceita um código, um nome em inglês, um endónimo ou um alias (zh-TW é um alias de um código que já não é listado), language_by_code faz corresponder um código exato sem distinguir maiúsculas de minúsculas, e dezasseis das linhas são da direita para a esquerda. Pesquise native, label e code em conjunto, mostre native primeiro e guarde o código.

emails.translate não é repetido automaticamente. Gasta chamadas ao modelo e não escreve nada, por isso não há nada para tornar idempotente e uma repetição após um pedido sem resposta só compraria a mesma resposta duas vezes.

  • Um idioma que não resolve para nada é um validation_error em translate.to, antes de seja o que for ser enviado.
  • translation_too_long acima de 30 000 caracteres, translation_not_configured quando a instalação não tem IA configurada, um 429 ai_quota_exceeded quando o espaço de trabalho já gastou as ações de IA de hoje (é reposta à meia-noite UTC e não vale a pena repetir), translation_failed quando o fornecedor não respondeu. Nenhum deles envia a mensagem por traduzir como alternativa.
  • Funciona com template: é o resultado RENDERIZADO que é traduzido, por isso um corpo guardado serve todas as línguas em que os seus clientes leem. Um template que renderiza um documento inteiro mantém o seu doctype, os seus blocos <style> e as suas regras @font-face: só o corpo vai para o modelo e o resto é reposto à volta dele. O seu <title> fica intacto, e nada o mostra de qualquer forma.
  • Uma repetição não custa nada a mais. A tradução não faz parte da impressão digital de idempotência (o pedido faz, translate incluído), por isso repetir um envio sem resposta com a mesma Idempotency-Key reproduz a mensagem que já existe em vez de traduzir e enviar uma segunda.
  • Uma mensagem traduzida que esteja em fila ou agendada fica congelada contra alterações de texto. emails.reschedule continua a movê-la; mudar o que diz significa cancelar e enviar de novo.

Anexos

content vai em base64 na rede. Passe bytes e são codificados por si.

attachment.py
from pathlib import Path from openemail.types import AttachmentInput attachments: list[AttachmentInput] = [    {        'filename': 'invoice.pdf',        'content': Path('invoice.pdf').read_bytes(),        'contentType': 'application/pdf',    },]

to_base64 é exportado se precisar dele noutro sítio. Um str em content é enviado tal como está, por isso já tem de estar em base64.

Referência