Configuração
Três formas de construir um cliente, todas as opções e o que ele recusa antes de um pedido ser enviado.
Opções
import os from openemail import AsyncOpenEmail, OpenEmail, init, openemail init(os.environ['OPENEMAIL_API_KEY'])openemail.me.ping() billing = OpenEmail(os.environ['BILLING_API_KEY']) pinned = OpenEmail('oe_live_...', base_url='https://api.openemail.uk') background = AsyncOpenEmail()| Ponto de entrada | O que obtém |
|---|---|
| init(...) | Configura o cliente partilhado e devolve-o. A partir daí, openemail é esse cliente em todos os módulos, e tudo o que omitir é lido do ambiente. |
| openemail | O cliente partilhado. Se for usado antes de init, constrói-se a si próprio a partir de OPENEMAIL_API_KEY e OPENEMAIL_BASE_URL na primeira chamada. |
| OpenEmail(...) | Um cliente separado, para uma segunda chave ao lado da partilhada, ou para construir a instância que o seu próprio módulo exporta. Tudo o que omitir é lido do ambiente, tal como faz init, e create_client é a mesma classe com outro nome. |
| AsyncOpenEmail(...) | Um cliente assíncrono separado, com as mesmas opções e cada método chamado com await. O openemail partilhado é síncrono, por isso este tem de ser você a construí-lo. |
| get_client() e reset_client() | get_client devolve o próprio cliente partilhado, construindo-o a partir do ambiente quando init não foi executado. reset_client esquece-o, por isso a próxima utilização constrói um novo. |
import httpxfrom openemail import init init( 'oe_live_...', base_url='https://api.openemail.uk', timeout=30, max_retries=2, http_client=httpx.Client(proxy='http://proxy.internal:3128'), headers={'X-Team': 'billing'}, user_agent='billing-service/1.4', disable_update_notice=True,)| Opção | Predefinição | Notas |
|---|---|---|
| api_key | OPENEMAIL_API_KEY | Lida do ambiente por init e OpenEmail. Tem de começar por oe_live_ ou oe_test_. |
| access_token | OPENEMAIL_ACCESS_TOKEN | Um token de acesso OAuth, ou uma função que devolva um, em vez de api_key. Consulte Tokens de acesso OAuth mais abaixo. |
| base_url | https://api.openemail.uk | Ou OPENEMAIL_BASE_URL. Uma barra final é removida, e init e OpenEmail acrescentam https:// antes de um host simples, ou http:// antes de um host nesta máquina: localhost, um endereço 127.x.x.x ou ::1. Uma credencial nunca é enviada por http simples para outro host, e 0.0.0.0 ou [::] lança uma exceção quando o cliente é criado, porque são endereços onde um servidor escuta, não endereços para onde enviar pedidos. |
| timeout | 30 | Em segundos, por tentativa, não por chamada. Abrange a leitura do corpo, não só dos cabeçalhos. 0 desativa-o. files.upload permite pelo menos 600 segundos, a não ser que a chamada passe o seu próprio timeout. |
| max_retries | 2 | Tentativas adicionais após a primeira, em chamadas que é seguro repetir. Define-se no cliente, não por chamada. |
| http_client | um novo httpx.Client | Passe o seu para um proxy, as suas próprias definições de TLS, um transporte montado ou um duplo de teste: um httpx.Client a OpenEmail, um httpx.AsyncClient a AsyncOpenEmail. Fechar o cliente deixa aberto aquele que passou. |
| headers | {} | Enviados em todos os pedidos. |
| user_agent | openemail-python/<version> | Enviados em todos os pedidos. |
| disable_update_notice | False | Ignora a verificação, feita uma vez por processo, de uma versão mais recente no PyPI. A verificação só é executada quando a saída vai para um terminal, e OPENEMAIL_DISABLE_UPDATE_NOTICE também a desativa. |
O que recusa antes de enviar
Estes casos lançam ValueError, ou TypeError onde a tabela o indica, antes de qualquer pedido ser enviado, em vez de surgirem como uma falha confusa no seu primeiro envio. A mensagem indica o que estava errado e o que passar em vez disso.
| Recusado | Porquê |
|---|---|
| Nenhuma chave | Nem api_key nem OPENEMAIL_API_KEY foram definidos, pelo que não há nada com que autenticar. |
| Um cookie de sessão ou um token de sessão | Só oe_live_ e oe_test_ autenticam aqui, e a API também o diz. A verificação é apenas de prefixo, pelo que uma chave revogada só é rejeitada quando o pedido é enviado. |
| Um base_url que não é um URL http ou https | Não é possível obter mais nada, por isso o cliente recusa-o quando é construído, em vez de falhar no primeiro pedido. |
| Um base_url em 0.0.0.0 ou [::] | É um endereço onde um servidor escuta, não um endereço para onde enviar pedidos. Em vez disso, a mensagem indica 127.0.0.1 ou [::1] com a mesma porta. |
| Uma credencial por http simples | Recusada na chamada, antes de o pedido sair, a não ser que o servidor esteja nesta máquina. Qualquer pessoa na rede a poderia ler. |
| Um id vazio ou composto só por pontos em qualquer método | Lançado quando o método é chamado. Um segmento de caminho feito de pontos é removido por qualquer parser de URL, pelo que o pedido chegaria a um endpoint diferente. |
| Um http_client do tipo errado | Um TypeError quando o cliente é construído. OpenEmail aceita um httpx.Client, e AsyncOpenEmail um httpx.AsyncClient. |
| Um valor do corpo que o JSON não consegue representar | Um TypeError que indica o seu tipo. Os tipos JSON passam tal como estão, e um datetime, um date ou um set são convertidos automaticamente. |
Não existe uma opção test_mode nem vai existir. O esquema da chave faz parte da credencial e não é uma mera indicação, pelo que o modo é uma propriedade da chave. client.mode lê o prefixo e não decide nada.
Um cliente, várias chaves
Construa o cliente uma vez e partilhe-o. Uma nova instância por pedido deita fora o pool de ligações e a configuração sem qualquer ganho, e nenhum do estado que contém é específico de quem chama.
É seguro partilhar um cliente entre threads. close() ou o fim de um bloco with fecha o pool de ligações que ele abriu, e um http_client que tenha passado continua aberto para ser você a fechá-lo.
No caso que, de outro modo, obrigaria a uma instância por chave, como uma tarefa que envia em nome de vários espaços de trabalho, passe api_key na chamada. Substitui o cabeçalho Authorization para esse pedido e não deixa nada no cliente.
from openemail.types import EmailSend workspace_key = 'oe_live_...' message: EmailSend = {'from': sender, 'to': recipient, 'subject': subject, 'text': text} client.emails.send(message) client.emails.send(message, api_key=workspace_key) client.threads.list(folder='inbox', api_key=workspace_key)client.webhooks.list(api_key=workspace_key)Todos os métodos fora de temp_mail recebem-na como argumento nomeado, ao lado de timeout. É verificada antes de o pedido ser enviado, pela mesma regra que o construtor usa, pelo que um erro de escrita lança um ValueError que refere api_key= on this call em vez de um 401 sobre uma credencial que depois tem de ir procurar. Uma chamada repetida mantém a chave que lhe foi dada.
timeout é em segundos e substitui o timeout do cliente nessa única chamada, em cada uma das suas tentativas.
client.mode descreve a chave com que o cliente foi CONSTRUÍDO e não acompanha uma substituição. Quando um cliente serve várias chaves, não há um modo único a indicar, por isso leia-o a partir da chave que passou.
Um endpoint que nenhum método envolve
client.raw.request envia um pedido com a credencial, o URL base, o timeout e a política de repetições do cliente aplicados, e devolve o JSON analisado. Aceita method, query, body, api_key e timeout. Repete um GET e envia tudo o resto uma única vez, a não ser que passe repeatable=True. idempotent=True acrescenta um Idempotency-Key, o que passar como idempotency_key ou um novo.
result = client.raw.request('/something-new', method='POST', body={'name': 'Invoices'})O caminho tem de começar por uma única /. Qualquer outra coisa, como //host/x, lança uma exceção antes de o pedido ser enviado, e o mesmo acontece com um caminho cujo URL final sai da origem do URL base, por isso a credencial que transporta nunca chega a outro host.
Caixas de entrada descartáveis
create_temp_mail() constrói um cliente que não transporta nenhuma chave de API, e create_async_temp_mail() é o seu equivalente assíncrono. Cria caixas de entrada de forma anónima, e cada leitura envia o token da caixa de entrada que create devolveu, ou o mais recente que extend devolveu, seja por chamada como inbox_token ou uma única vez como create_temp_mail(inbox_token=...).
from openemail import create_temp_mail temp_mail = create_temp_mail() inbox = temp_mail.create()messages = temp_mail.list_messages(inbox['id'], inbox_token=inbox['token']) print(messages['items'], messages['expiresAt'])Tokens de acesso OAuth
Ainda não disponívelUma aplicação que alguém ligou por OAuth, como uma ferramenta de linha de comandos ou um agente, tem um token de acesso em vez de uma chave de API. Passe-o como access_token, seja o próprio token, seja uma função que o devolve, que em AsyncOpenEmail pode ser async. A função é chamada uma vez por cada chamada, e as repetições dessa chamada reutilizam o que ela devolveu, por isso renove o token dentro dela quando estiver perto de expirar e o cliente nunca terá de ser reconstruído.
from openemail import OpenEmail client = OpenEmail(access_token=session.fresh_access_token) me = client.me.get() if me['object'] == 'oauth_token': print(me['clientId'], me['expiresAt'])| Caso | O que acontece |
|---|---|
| api_key e access_token juntos, ou nenhum | O construtor lança ValueError. Sem nenhum dos dois, a mensagem indica OPENEMAIL_API_KEY e OPENEMAIL_ACCESS_TOKEN. |
| Um valor que não é um token | Um token tem de 1 a 512 caracteres e não começa por oe_, a verificação que is_access_token faz. Uma string que falha lança uma exceção no construtor, e uma função que devolve um valor assim faz falhar a chamada antes de enviar o que quer que seja. |
| OPENEMAIL_ACCESS_TOKEN | Lido por init, OpenEmail e pelo openemail partilhado quando não passa nenhuma das duas credenciais e OPENEMAIL_API_KEY não está definida, por isso uma chave no ambiente tem prioridade. |
| Uma função que lança uma exceção | A chamada lança esse mesmo erro, sem alterações, e nada é enviado. |
| Uma função em OpenEmail que devolve um objeto awaitable | Um ValueError, porque o cliente síncrono não consegue esperar por ele. Em AsyncOpenEmail a função pode ser async. |
| Um api_key por chamada | Substitui o token nesse único pedido, e a função não é chamada. |
| mode | Sempre live com um token. |
| create_temp_mail() | Não envia nenhuma credencial, seja o que for que o ambiente contenha. |
| me.get() e me.ping() | Para um token, get responde com object igual a oauth_token, id e roleId a None, o clientId da aplicação ligada e expiresAt, o momento em que expira a aprovação que a pessoa deu à aplicação. ping responde com kind igual a oauth, keyId a None e o clientId. KeyResource e PingResource são uniões, por isso verifique object ou kind antes de ler clientId ou expiresAt. |
Um token atua em nome de uma pessoa e lê o correio dela como ela o pode ler, por isso mantenha-o num servidor, tal como uma chave.
Códigos de verificação
Ainda não disponívelAntes de uma alteração sensível, como apagar um domínio ou alterar um webhook, a API pede a um token de acesso o código de verificação que a aplicação web pediria à pessoa. A chamada lança um OpenEmailApiError cujo is_step_up_required é True, e nada foi alterado. Peça um código, verifique o que a pessoa lhe der e depois faça a chamada de novo. A uma chave de API nunca é pedido.
from openemail import OpenEmailApiError try: client.domains.delete(domain_id)except OpenEmailApiError as error: if not error.is_step_up_required: raise challenge = client.security.begin_step_up() if challenge['method'] == 'email': prompt = f'Enter the code we emailed to {challenge.get("sentTo")}: ' else: prompt = 'Enter the code from your authenticator app, or a backup code: ' client.security.verify_step_up({'code': input(prompt)}) client.domains.delete(domain_id)| Método | O que faz |
|---|---|
| security.step_up_status() | Se a aplicação está verificada neste momento (elevated, elevatedUntil), como é verificado o próximo código (method, email ou totp) e minutes, a duração da janela. Não envia nada e não indica uma pausa. |
| security.begin_step_up(body=None) | Abre um desafio. Com email segue um código de seis dígitos para o endereço com que a pessoa inicia sessão, e sentTo mostra-o mascarado. Com totp a pessoa lê um na sua aplicação de autenticação ou usa um código de recuperação. Um desafio que ainda esteja aberto e tenha tentativas é reutilizado, a não ser que passe {'resend': True}, e um bloqueado ou expirado é substituído por uma chamada simples. Cada aplicação pode abrir 5 por hora e 20 em 24 horas para cada pessoa, e o seguinte lança um 429 step_up_throttled. |
| security.verify_step_up({'code': code}) | Verifica o código e desbloqueia as alterações sensíveis para esta aplicação durante 60 minutos, até elevatedUntil, por REST e através das ferramentas MCP que fazem as mesmas alterações. Depois de 10 códigos errados em 24 horas desta aplicação, ou 20 de todas as aplicações da pessoa juntas, esta chamada e begin_step_up lançam um 429 step_up_locked com uma mensagem que diz quando a verificação é retomada. |
O cliente nunca pede um código nem repete a chamada por si só, e nem begin_step_up nem verify_step_up são repetidos automaticamente, porque uma repetição depois de uma resposta perdida poderia enviar um segundo email ou gastar uma segunda tentativa. Não precisam de âmbito, e uma chave de API que chame um deles recebe 400 step_up_not_applicable. STEP_UP_ERROR_CODES indica todas as formas como uma verificação pode falhar, e a página de erros da API diz o que fazer em cada caso.