Resposta rápida: crie um EmailMessage, defina remetente, destinatário e assunto, adicione uma versão texto e outra HTML, abra smtplib.SMTP, execute starttls() com um contexto SSL padrão, autentique e chame send_message(). Host, porta, usuário e senha devem vir do ambiente.
Esse fluxo é apropriado para alertas internos, confirmações transacionais e relatórios com destinatários autorizados. Não use o exemplo para mensagens não solicitadas. Provedores impõem limites e podem exigir senha de aplicativo, token ou autenticação específica. Consulte a documentação oficial de smtplib, o módulo email do Python e as recomendações de SSL/TLS.
Configurar sem expor credenciais
Defina as variáveis na plataforma de execução, arquivo local ignorado pelo Git ou gestor de segredos. Não publique valores reais, não os inclua na imagem do contêiner e não imprima o ambiente inteiro ao depurar.
export SMTP_HOST=smtp.example.com
export SMTP_PORT=587
export [email protected]
export SMTP_PASSWORD=uma-senha-de-aplicativo
export [email protected]
No PowerShell, a sintaxe para a sessão atual é $env:SMTP_HOST = "smtp.example.com". Em produção, prefira o mecanismo de segredos do serviço. A organização se beneficia de um ambiente virtual Python e de configuração separada do código.
Enviar texto e HTML na mesma mensagem
EmailMessage cuida dos cabeçalhos e da estrutura MIME. Adicione primeiro o texto simples e depois o HTML como alternativa. O cliente escolhe o formato compatível. Valores dinâmicos precisam ser escapados antes de entrar no HTML; o exemplo usa html.escape.
import html
import os
import smtplib
import ssl
from email.message import EmailMessage
def enviar_boas_vindas(destinatario: str, nome: str) -> None:
host = os.environ["SMTP_HOST"]
porta = int(os.environ.get("SMTP_PORT", "587"))
usuario = os.environ["SMTP_USER"]
senha = os.environ["SMTP_PASSWORD"]
remetente = os.environ.get("SMTP_FROM", usuario)
nome_seguro = html.escape(nome)
mensagem = EmailMessage()
mensagem["From"] = remetente
mensagem["To"] = destinatario
mensagem["Subject"] = "Seu acesso foi criado"
mensagem.set_content(
f"Olá, {nome}!\n\nSeu acesso foi criado. "
"Entre pelo endereço oficial da aplicação.\n"
)
mensagem.add_alternative(
f"""\
<html><body>
<p>Olá, <strong>{nome_seguro}</strong>!</p>
<p>Seu acesso foi criado. Entre pelo endereço oficial da aplicação.</p>
</body></html>
""",
subtype="html",
)
contexto = ssl.create_default_context()
with smtplib.SMTP(host, porta, timeout=30) as servidor:
servidor.ehlo()
servidor.starttls(context=contexto)
servidor.ehlo()
servidor.login(usuario, senha)
servidor.send_message(mensagem)
create_default_context() habilita validação de certificado e padrões seguros do Python. Não desative a verificação para “resolver” erros. Corrija certificados, nome do host ou cadeia de confiança. Os dois ehlo() atualizam as capacidades anunciadas antes e depois do TLS.
Anexar arquivos com limites
Valide existência, tamanho e tipo permitido. SMTP aumenta o conteúdo por causa da codificação, e o servidor pode rejeitar mensagens grandes. O exemplo limita cada anexo a 10 MB e usa mimetypes; ajuste o limite às regras do provedor.
import mimetypes
from pathlib import Path
from email.message import EmailMessage
LIMITE_ANEXO = 10 * 1024 * 1024
def anexar(mensagem: EmailMessage, caminho: Path) -> None:
caminho = caminho.resolve()
if not caminho.is_file():
raise FileNotFoundError(caminho)
if caminho.stat().st_size > LIMITE_ANEXO:
raise ValueError(f"Anexo excede 10 MB: {caminho.name}")
tipo, _ = mimetypes.guess_type(caminho.name)
tipo_principal, subtipo = (tipo or "application/octet-stream").split("/", 1)
mensagem.add_attachment(
caminho.read_bytes(),
maintype=tipo_principal,
subtype=subtipo,
filename=caminho.name,
)
## Depois de montar a mensagem:
## anexar(mensagem, Path("relatorio.pdf"))
Não aceite um caminho arbitrário de usuário: restrinja-o a uma pasta conhecida e confirme que o caminho resolvido continua dentro dela. Para aprofundar a API, veja caminhos com pathlib. Também evite anexar executáveis e arquivos cujo conteúdo não foi verificado.
Projeto prático: relatório diário
Crie uma função que recebe métricas já calculadas, renderiza texto e HTML, anexa um PDF opcional e envia para uma lista permitida. Mantenha a obtenção dos dados separada do transporte SMTP. Isso permite testar a mensagem sem conexão externa e integrar o trabalho a um fluxo de automação de tarefas com Python.
def criar_relatorio(destinatario: str, total: int) -> EmailMessage:
if "\n" in destinatario or "\r" in destinatario:
raise ValueError("Destinatário inválido")
msg = EmailMessage()
msg["From"] = os.environ["SMTP_FROM"]
msg["To"] = destinatario
msg["Subject"] = "Resumo diário"
msg.set_content(f"Resumo diário\nItens processados: {total}\n")
msg.add_alternative(
f"<p>Resumo diário</p><p>Itens processados: <strong>{total}</strong></p>",
subtype="html",
)
return msg
Para desenvolvimento, use um servidor SMTP de teste que capture mensagens sem entregá-las. Verifique assunto, destinatários, texto, HTML e anexos. Em testes unitários, substitua a conexão por um objeto falso; o guia de testes unitários com Python mostra como isolar dependências.
Tratar erros sem esconder a causa
Falhas de rede podem ser temporárias; autenticação e destinatário recusado normalmente exigem correção. Capture exceções específicas, registre apenas contexto não sensível e decida se haverá nova tentativa. Não repita indefinidamente: use poucas tentativas com espera crescente e uma chave que evite duplicar notificações.
import logging
import smtplib
import socket
logger = logging.getLogger(__name__)
try:
enviar_boas_vindas("[email protected]", "Ana")
except smtplib.SMTPAuthenticationError:
logger.error("Autenticação SMTP recusada")
raise
except smtplib.SMTPRecipientsRefused as erro:
logger.warning("Destinatário recusado pelo servidor")
raise
except (smtplib.SMTPException, TimeoutError, socket.timeout, OSError):
logger.exception("Falha ao comunicar com o servidor SMTP")
raise
Não registre senha, corpo, anexos ou endereço completo se forem dados pessoais. Um identificador interno costuma bastar. Aplique as ideias de tratamento de erros e logging em Python. Em filas, marque sucesso somente após o servidor aceitar a mensagem, lembrando que aceitação não garante leitura nem entrega final.
SMTP_SSL quando a conexão já começa cifrada
Se o provedor especificar TLS implícito, geralmente na porta 465, use SMTP_SSL em vez de chamar starttls(). Siga a configuração documentada pelo serviço; não escolha somente pela porta.
contexto = ssl.create_default_context()
with smtplib.SMTP_SSL(host, 465, context=contexto, timeout=30) as servidor:
servidor.login(usuario, senha)
servidor.send_message(mensagem)
Valide endereços e impeça injeção de cabeçalhos
Um endereço com formato plausível não é necessariamente entregável, mas a validação básica evita cabeçalhos malformados e erros evidentes. Nunca concatene um valor não confiável em texto bruto de cabeçalho. EmailMessage oferece cabeçalhos estruturados, mas a entrada da aplicação ainda deve recusar retorno de carro e quebra de linha. Analise endereços com as funções de email.utils, aplique as regras do produto e confirme a propriedade por uma mensagem de verificação quando a identidade for importante. Não tente provar a existência da caixa fazendo consultas SMTP especulativas ao servidor do destinatário; esse método é pouco confiável e pode parecer tráfego abusivo.
Trate To, Cc e Bcc de forma explícita. Destinatários em Bcc recebem a mensagem sem aparecer nos cabeçalhos visíveis transmitidos, mas os endereços continuam sendo processados pela aplicação e pelo provedor. Para avisos enviados a pessoas sem relação entre si, prefira mensagens individuais a uma lista compartilhada. Limite o total de destinatários por operação e exija uma lista autorizada para relatórios internos sensíveis.
Entenda o que acontece depois da aceitação SMTP
O sucesso de send_message() significa que o servidor SMTP contatado aceitou a responsabilidade pela mensagem. Isso não prova chegada à caixa de entrada, exibição ao usuário ou leitura. Uma devolução posterior pode indicar caixa inexistente, rejeição por política ou falha temporária. Integrações de produção precisam de um mecanismo do provedor para processar devoluções e reclamações, além de um identificador interno que relacione o evento ao aviso original sem guardar o corpo completo nos logs.
Novas tentativas exigem atenção porque um timeout pode ocorrer depois que o servidor remoto aceitou a mensagem. Use uma chave estável de entrega da aplicação, persista o estado ao redor da fila e torne repetições detectáveis. O cabeçalho padrão Message-ID ajuda sistemas de e-mail a identificar mensagens, mas não oferece sozinho uma garantia universal de idempotência. Repita somente falhas temporárias, com atraso exponencial limitado, e envie tarefas esgotadas para uma fila de revisão.
Autenticação e reputação do domínio
As credenciais SMTP autorizam a conexão, enquanto a autenticação do domínio ajuda os destinatários a avaliar a mensagem. Configure SPF, DKIM e DMARC com o provedor e o administrador de DNS. Alinhe o remetente visível a um domínio que o serviço está autorizado a usar. Esses controles não podem ser implementados apenas no script Python e não garantem chegada à caixa principal, mas sua ausência costuma prejudicar confiança e entrega.
Para comunicação comercial ou em massa, use um serviço preparado para registrar consentimento, manter listas de supressão, processar descadastro, aplicar limites e receber reclamações. Um laço direto com smtplib serve para trabalho transacional controlado, não para contornar essas responsabilidades operacionais e legais.
Erros comuns e checklist
- 535 Authentication failed: confira usuário, segredo, política de autenticação e necessidade de senha de aplicativo.
- TLS ou certificado: valide host, relógio do sistema e cadeia; não desligue a verificação.
- Mensagem no spam: configure domínio, SPF, DKIM e DMARC com o provedor; o código sozinho não garante reputação.
- Anexo recusado: reduza tamanho, confira extensão e limite total da mensagem.
- HTML quebrado: mantenha texto alternativo, escape dados e teste em clientes diferentes.
Antes de publicar: credenciais fora do código; TLS verificado; timeout definido; remetente permitido; destinatários validados; consentimento e descadastro quando aplicáveis; limites de anexos; texto alternativo; logs minimizados; testes sem entrega real; tentativas limitadas; monitoramento de falhas. Assim, uma automação simples se torna previsível sem confundir aceitação SMTP com entrega garantida.