Como Enviar E-mail via API em Python (Tutorial Rápido)
Pule o texto padrão do SMTP. Este guia mostra como usar o API unificada de e-mail Unipile para enviar e-mail em Python - com exemplos de copiar e colar para Gmail, Outlook e IMAP usando o solicitações biblioteca.
import requisições, os
CHAVE_API = os.environ'UNIPILE_API_KEY']
DSN = os.environ'UNIPILE_DSN']
ID_DA_CONTA = os.environ'ID_DA_CONTA_UNIPILE']
response = requests.postagem(
f'{DSN}/api/v1/emails',
cabeçalhos={'X-API-KEY': CHAVE_API},
dados={
'id_da_conta': ID_DA_CONTA,
'a': '[{"display_name":"Alice","identifier":"alice@acme.com"}]',
'assunto': 'Olá do Python',
'corpo': 'Enviado via Unipile!
'
}
)
print(resposta.json())Exemplo de Python de 5 linhas
Se você já sabe o que é uma API de Envio de E-mail e quero apenas o código Python para a API de e-mail que realmente funciona, aqui está. O tutorial completo segue abaixo.
pip install requests python-dotenvUNIPILE_DSN, UNIPILE_API_KEYe ID_DA_CONTA_UNIPILE para você .env arquivo./api/v1/emailsaccount_id, para, assuntoe corpo. Feito.import requisições, os
from dotenv import carregar_dotenv
carregar_dotenv()
CHAVE_API = os.environ'UNIPILE_API_KEY']
DSN = os.environ'UNIPILE_DSN']
ID_DA_CONTA = os.environ'ID_DA_CONTA_UNIPILE']
resp = requests.postagem(
f'{DSN}/api/v1/emails',
cabeçalhos={'X-API-KEY': CHAVE_API},
dados={
'id_da_conta': ID_DA_CONTA,
'a': '[{"display_name":"Alice","identifier":"alice@acme.com"}]',
'assunto': 'Olá do Python',
'corpo': 'Enviado via Unipile!
'
}
)
print(resp.json()) # {'tracking_id': 'msg_...'}Pré-requisitos e Configuração
Antes de poder usar o fluxo de trabalho do Python da API de e-mail em produção, você precisa de quatro coisas: Python 3.9+, o solicitações biblioteca, uma chave de API com DSN e uma conta de e-mail vinculada.
| Tipos de união e recursos da biblioteca padrão do Python 3.9+. O Python 3.11 LTS é recomendado para produção. Verifique sua versão com python --version.api4.unipile.com:13444Ambos são obrigatórios em cada cabeçalho de requisição.python -m venv .venv && source .venv/bin/activate. Nunca instale pacotes no Python do sistema - isto é especialmente importante para o manuseio de credenciais.pip install requests python-dotenv
# Opcional: suporte a async
pip install aiohttp httpx
# Opcional: lógica de retentativa
pip install tenacitypipenv install requests python-dotenv tenacitypoetry adicionar requests python-dotenv tenacity# Credenciais Unipile - nunca comite este arquivo
UNIPILE_DSN=https://api4.unipile.com:13444
UNIPILE_API_KEY=seu_token_de_acesso_aqui
# O ID da conta de e-mail vinculada
ID_DA_CONTA_UNIPILE=acc_xxxxxxxxxxxxxxxxAdicionar .env para você .gitignore. Carregar com python-dotenv via load_dotenv() no topo do seu script. Em produção, prefira variáveis de ambiente reais injetadas pela sua plataforma de implantação (Heroku, Railway, Docker Compose).
Conectando Sua Primeira Conta de E-mail
Antes de enviar, você precisa vincular uma conta de e-mail ao Unipile. Este é um passo único por conta. Veja o completo guia de integração de API de e-mail unificada para mais informações sobre fluxos de múltiplas contas.
Unipile usa um assistente de autenticação hospedado - seu script Python gera um link de autenticação, o usuário clica nele e completa o OAuth no navegador, então Unipile chama seu webhook com o novo account_id. Nenhuma credencial SMTP é armazenada em seu código para Gmail ou Outlook.
import requisições, os
from dotenv import carregar_dotenv
carregar_dotenv()
CHAVE_API = os.environ'UNIPILE_API_KEY']
DSN = os.environ'UNIPILE_DSN']
# Passo 1: crie um link de autenticação hospedado para o OAuth do Gmail
resp = requests.postagem(
f'{DSN}/api/v1/hosted/contas/vincular',
cabeçalhos={'X-API-KEY': CHAVE_API},
dados={
'tipo': 'Google',
'nome': 'Alice Gmail',
'url_de_sucesso': 'https://seusite.com/oauth/success',
'url_de_falha': 'https://seuarquivo.com/oauth/failure'
}
)
# Passo 2: envie esta URL para o seu usuário
auth_url = resp.json()['url']
print(Redirecione o usuário para: {auth_url}')
# Etapa 3: Unipile POSTs {account_id} para o seu webhook após o OAuth
# Veja /gmail-api-send-email-a-comprehensive-guide-for-developers/ para detalhes do Gmailimport requisições, os
from dotenv import carregar_dotenv
carregar_dotenv()
# Outlook OAuth - abrange Outlook pessoal + Microsoft 365
# Veja /microsoft-graph-api-email-integration-guide/
resp = requests.postagem(
f'{os.environ["UNIPILE_DSN"]}/api/v1/hosted/accounts/link',
cabeçalhos={'X-API-KEY': os.environ['UNIPILE_API_KEY']},
dados={
'tipo': 'MICROSOFT',
'nome': 'Bob Outlook',
'url_de_sucesso': 'https://seusite.com/oauth/success',
'url_de_falha': 'https://seuarquivo.com/oauth/failure'
}
)
print(resp.json()['url'])import requests, os, json
# IMAP: passe as credenciais SMTP/IMAP diretamente (sem necessidade de redirecionamento OAuth)
# Veja /the-developers-guide-to-imap-api-solution/ para detalhes completos sobre a API IMAP
resp = requests.postagem(
f'{os.environ["UNIPILE_DSN"]}/api/v1/accounts',
cabeçalhos={'X-API-KEY': os.environ['UNIPILE_API_KEY']},
json={
'provedor': 'IMAP',
'nome de usuário': 'alice@company.com',
'senha': 'senha_aplicativo_aqui',
'imap_host': 'imap.company.com',
'smtp_host': 'smtp.company.com'
}
)
account_id = resp.json()['id_da_conta']
print(Conta vinculada: {account_id}')Enviando seu primeiro e-mail em Python
O endpoint de envio aceita multipart/form-data. Usar dados= não json=) em requests.post(). O para, cce Cópia oculta os campos são strings codificadas em JSON dentro dos dados do formulário.
import requests, os, json
pedidos.postagem(
f'{os.environ["UNIPILE_DSN"]}/api/v1/emails',
cabeçalhos={'X-API-KEY': os.environ['UNIPILE_API_KEY']},
dados={
'id_da_conta': os.environ['ID_DA_CONTA_UNIPILE'],
'a': json.despejos([{'Nome de exibição': 'Alice', 'identificador': 'alice@acme.com'}]),
'assunto': 'Atualização rápida',
'corpo': 'Oi Alice, só de passagem.'
}
)corpo campo aceita texto puro e HTML. Use tags de formatação HTML.import requests, os, json
response = requests.postagem(
f'{os.environ["UNIPILE_DSN"]}/api/v1/emails',
cabeçalhos={'X-API-KEY': os.environ['UNIPILE_API_KEY']},
dados={
'id_da_conta': os.environ['ID_DA_CONTA_UNIPILE'],
'a'json.despejos([{'identificador': 'alice@acme.com'}]),
'cc'json.despejos([{'identificador': 'manager@acme.com'}]),
'cópia oculta': json.despejos([{'identificador': 'crm@yourapp.com'}]),
'assunto': 'Seu boleto está pronto',
'corpo': 'Fatura #1042
Por favor, encontre sua fatura em anexo.
'
}
)
# 202 Aceito = em fila para entrega
print(response.status_code, response.json())import requests, os, json
def enviar_email(para_email: str, assunto: str, corpo: str) -> dicionário:
"Enviar e-mail via wrapper Python da API de e-mail Unipile."
response = requests.postagem(
f'{os.environ["UNIPILE_DSN"]}/api/v1/emails',
cabeçalhos={'X-API-KEY': os.environ['UNIPILE_API_KEY']},
dados={
'id_da_conta': os.environ['ID_DA_CONTA_UNIPILE'],
'a'json.despejos([{'identificador': to_email}]),
'assunto': assunto,
'corpo'corpo,
},
tempo_limite=30
)
resposta.raise_for_status() # levanta HTTPError em 4xx/5xx
return resposta.json() # {'tracking_id': 'msg_...'}tempo_limite=30 para evitar que trave para sempre em problemas de rede. Use raise_for_status() para propagar erros HTTP como exceções Python.Obtenha sua chave de API, vincule uma conta do Gmail ou Outlook em minutos e execute os exemplos Python deste guia contra caixas de correio reais.
Enviando Anexos em Python
Anexos são enviados como parte dos dados do formulário multipart usando Python's arquivos= parâmetro. Abra o arquivo em modo binário ('rb') - bytes, não strings.
import requests, os, json
# Anexo de arquivo único
com abra('nota fiscal.pdf', 'rb') como f:
resp = requests.postagem(
f'{os.environ["UNIPILE_DSN"]}/api/v1/emails',
cabeçalhos={'X-API-KEY': os.environ['UNIPILE_API_KEY']},
dados={
'id_da_conta': os.environ['ID_DA_CONTA_UNIPILE'],
'a': json.despejos([{'identificador': 'client@example.com'}]),
'assunto': 'Fatura em anexo',
'corpo': 'Por favor, veja a fatura em anexo.
'
},
arquivos={'anexos': ('nota fiscal.pdf', f, 'application/pdf')}
)
# Múltiplos anexos: passe uma lista de tuplas
# arquivos=[('anexos', ('a.pdf', f1, 'application/pdf')),
# ('anexos', ('b.png', f2, 'image/png'))]open('arquivo.pdf', 'rb'), não 'r'. Passando um objeto de arquivo de texto para arquivos= levanta um TypeError. Isso é uma armadilha comum específica do Python ao migrar de smtplib.arquivos=: cada tupla é ('anexos', (nome_arquivo, objeto_arquivo, tipo_conteudo)). O Requests lida com o limite multipart automaticamente.BytesIO objeto direto: de io importe BytesIO; buf = BytesIO(pdf_bytes) então ('report.pdf', buf, 'application/pdf').Respostas, Tópicos e Rastreamento
Para enviar email em nome de um usuário, encadeamento e rastreamento de entrega baseado em webhook, aqui estão os padrões Python que você precisa.
01Encadeamento com in_reply_to
Para responder dentro de um tópico existente, passe o em_resposta_a campo com o id_rastreamento do email ao qual você deseja responder. A Unipile cuida do Referências e Em-Resposta-A cabeçalhos automaticamente.
pedidos.postagem(
f'{DSN}/api/v1/emails',
cabeçalhos={'X-API-KEY': CHAVE_API},
dados={
'id_da_conta': ID_DA_CONTA,
'a': json.despejos([{'identificador': 'alice@acme.com'}]),
'assunto': 'Re: Sua pergunta',
'corpo': 'Em seguimento à sua mensagem.
',
'em_resposta_a': 'id_rastreamento_original'
}
)02Webhooks em Python (exemplo com Flask)
Registre um URL de webhook em seu painel Unipile para receber eventos de entrega (enviado, falhado, aberto). Aqui está um receptor Flask mínimo:
from garrafa import Flask, request, jsonify
import registro
aplicativo = Frasco(__nome__)
registro.basicConfig(nível=logging.INFORMAÇÃO)
@app.rota('/webhook/email', métodos=['POST'])
def email_webhook():
evento = solicitação.obter_json()
tipo_evento = evento.obter('tipo')
tracking_id = evento.obter('id_de_rastreamento')
registro.informações(f'Email do evento: {event_type} para {tracking_id}')
return jsonifyOk=Verdadeiro), 20003Chaves de idempotência
Para evitar reenvios duplicados na tentativa de rede, passe um único Chave-Idempotente cabeçalho. Se a mesma chave for enviada duas vezes, a Unipile retorna a resposta original sem enviar um segundo e-mail.
import uuid, requisições, os, json
chave = str(uuid.uuid4()) # gerar uma vez, armazenar no banco de dados
pedidos.postagem(
f'{os.environ["UNIPILE_DSN"]}/api/v1/emails',
cabeçalhos={
'X-API-KEY'os.environ['UNIPILE_API_KEY'],
'Chave de Idempotência'chave
},
dados={'id_da_conta': os.environ['ID_DA_CONTA_UNIPILE'],
'a': json.despejos([{'identificador': 'alice@acme.com'}]),
'assunto': 'Bem-vindo!', 'corpo': 'Olá!'}
)Tratamento de Erros e Tentativas
O código Python de produção para a API de e-mail precisa de tratamento de exceções adequado, logging estruturado e retentativas automáticas com backoff exponencial usando o tenacidade biblioteca.
| Código HTTP | Significado | Ação |
|---|---|---|
| 202 | Aceito - em fila para entrega | Armazenar rastreamento_id |
| 400 | Requisição inválida (campos inválidos) | Corrigir carga útil, não tentar novamente |
| 401 | Chave de API inválida | Verificar UNIPILE_API_KEY |
| 403 | Conta não autorizada | Reconectar conta |
| 404 | ID da conta não encontrado | Verificar UNIPILE_ACCOUNT_ID |
| 429 | Taxa limitada | Backoff + nova tentativa (ver código) |
| 500 | Erro do servidor | Tentar novamente após 5s de atraso |
import requests, os, json, logging
from tenacidade import (
tentar novamente, parar_após_tentativa,
wait_exponential, retry_if_exception_type
)
registro.basicConfig(nível=logging.INFORMAÇÃO)
logger = logging.getLogger(__nome__)
aula Erro de Limite de Taxa(Exceção):
passar
@tentar novamente(
parar=parar_após_tentativa(4),
esperar=espera_exponencial(multiplicador=1, min=2, max=30),
tentar novamente=tentar_novamente_se_tipo_exceção(Erro de Limite de Taxa)
)
def enviar_com_nova_tentativaPara: str, assunto: str, corpo: str) -> dicionário:
resp = requests.postagem(
f'{os.environ["UNIPILE_DSN"]}/api/v1/emails',
cabeçalhos={'X-API-KEY': os.environ['UNIPILE_API_KEY']},
dados={
'id_da_conta': os.environ['ID_DA_CONTA_UNIPILE'],
'a'json.despejos([{'identificador': para}]),
'assunto': assunto, 'corpo'corpo
},
tempo_limite=30
)
se resp.status_code == 429:
registrador.Aviso('Limite de taxa atingido, recuando...')
levantar Erro de Limite de Taxa()
resp.raise_for_status()
return resp.json()Melhores Práticas de Segurança em Python
Para um guia completo sobre como proteger sua integração de API de e-mail, consulte o Guia de segurança de API de e-mail. Aqui estão os essenciais específicos do Python.
os.environ ou python-dotenv. Nunca coloque UNIPILE_API_KEY como uma string literal em seu código-fonte. Se acidentalmente enviado para o Git, roteie a chave imediatamente em seu painel.venv ou conda. Isso impede ataques de confusão de dependência e torna sua requirements.txt auditable. Fixar versões em produção.UNIPILE_API_KEY válido.id_rastreamento para cada email enviado para habilitar auditorias de entrega. Use o padrão Python registro módulo - nunca imprimir() em produção. Enviar logs para um SIEM para casos de uso com forte conformidade.Armadilhas Comuns Específicas do Python
Estes são os erros mais comuns que desenvolvedores Python cometem ao integrar a API de e-mail. Se você estiver usando Node.js, veja nossa Tutorial de API de envio de e-mail com JavaScript.
json= em vez de dados=multipart/form-data, não JSON. Use sempre requests.post(..., dados={...}). Usando json={...} retornará um erro 400. O para, cce Cópia oculta os campos são strings JSON dentro dos dados do formulário - use json.dumps() para codificar a matriz do destinatário.open('arquivo.pdf', 'rb') - modo binário. Modo texto ('r') levanta um TypeError quando passado para o arquivos= parâmetro. Para conteúdo em memória, use io.BytesIO.solicitações a biblioteca é síncrona. Chamá-la dentro de um async def bloqueia o loop de eventos. Use httpx.AsyncClient ou aiohttp.ClientSession para contextos assíncronos do Python (FastAPI, views assíncronas do Django, scripts asyncio).requests.post() espera para sempre. Uma conexão travada bloqueará sua thread (ou worker do Celery) indefinidamente. Sempre passe tempo_limite=30 (tempo limite de conexão, tempo limite de leitura em segundos).from datetime import datetime, timezone; datetime.now(timezone.utc). Datetimes ingênuas causam erros silenciosos de "off-by-hours" em implantações multirregionais.concurrent.futures.ThreadPoolExecutor) ou descarregar para uma fila Celery.Perguntas frequentes
Perguntas frequentes sobre o uso da API de e-mail em Python com a API unificada de e-mail da Unipile.
Use a API unificada de e-mail da Unipile em vez do smtplib ou de uma conexão SMTP direta. Instale solicitações, obtenha sua chave de API e DSN no painel do Unipile, vincule uma conta Gmail ou Outlook via OAuth, depois envie um POST para /api/v1/emails com o seu account_id, para, assuntoe corpo. Nenhum servidor SMTP, nenhuma porta 587, nenhuma configuração TLS necessária em seu código Python.
Django: chame a API em uma view ou comando de gerenciamento. Para Django assíncrono (3.1+), use httpx.AsyncClient em views assíncronas.
Frasco chame a API em um manipulador de rota do lado do servidor. Nunca a chame de um template Jinja ou de JS do lado do cliente. Use Flask-Celery para descarregar envios de alto volume para workers em segundo plano.
FastAPI: usar httpx.AsyncClient dentro async def endpoints. O síncrono solicitações biblioteca bloqueia o loop de eventos assíncrono - sempre use um cliente HTTP assíncrono no FastAPI.
smtplib conecta diretamente a um servidor SMTP a partir do seu processo Python. Você gerencia credenciais SMTP, configuração TLS e peculiaridades por provedor (senhas de aplicativo do Gmail, autenticação moderna do Outlook). Ele também é apenas síncrono.
A API de e-mail Unipile é uma abstração em nuvem: vincule contas via OAuth (sem credenciais SMTP no seu código para Gmail/Outlook), obtenha uma API HTTP única e consistente para todos os provedores, e a Unipile cuida do transporte, atualização de tokens e tentativas. A contrapartida é que os envios passam pela infraestrutura da Unipile em vez de uma conexão SMTP direta.
Sim, mas você precisa de um cliente HTTP async - o padrão solicitações biblioteca é síncrona e bloqueará seu event loop. Use httpx (recomendado, alternativa assíncrona substituível) ou aiohttp.
import httpx, os, json
async def enviar_email_asyncPara: str, assunto: str, corpo: str):
assíncrono com httpx.AsyncClient() como cliente:
resp = await cliente.postagem(
f'{os.environ["UNIPILE_DSN"]}/api/v1/emails',
cabeçalhos={'X-API-KEY': os.environ['UNIPILE_API_KEY']},
dados={'id_da_conta': os.environ['ID_DA_CONTA_UNIPILE'],
'a': json.despejos([{'identificador': para}]),
'assunto': assunto, 'corpo': corpo}
)
resp.raise_for_status()
return resp.json()Use uma fila de tarefas Celery com um broker Redis ou RabbitMQ. Cada e-mail se torna uma tarefa - o Celery gerencia a concorrência e as tentativas automaticamente. Limite a concorrência por worker para evitar limites de taxa (geralmente 5-10 envios simultâneos por conta vinculada). Para envios de marketing de alto volume (milhões/dia), combine o Unipile para envios transacionais baseados em OAuth com um ESP dedicado para campanhas em massa.
Para casos de uso mais leves, concurrent.futures.ThreadPoolExecutor(max_workers=5) com o solicitações A biblioteca é uma abordagem mais simples que evita a sobrecarga do Celery.
Sim. Crie uma tarefa Celery que chama requests.post() para o endpoint Unipile. Os workers do Celery são processos Python síncronos padrão, então solicitações funciona perfeitamente. Use o embutido do Celery autoretry_for=(requests.exceptions.HTTPError,) com max_tentativas=3 e atraso_padrao_tentativa=5 para nova tentativa automática em falhas transitórias. Combine com Chave-Idempotente cabeçalhos para prevenir envios duplicados em reinícios de worker.
Ainda tem dúvidas? Nossa equipe está aqui para ajudar.