guia · SMTP com Python 3
Como uma equipe de produto deve implementar SMTP com Python 3 com segurança?
Implemente o SMTP com Python 3 por trás de um worker autorizado no servidor, e não em código do navegador ou controlado pelo usuário. Monte as mensagens com EmailMessage, mantenha os destinatários do envelope separados dos cabeçalhos visíveis, crie um contexto SSL verificado, defina timeouts de conexão finitos e use SMTP_SSL para TLS desde o início da conexão ou SMTP.starttls() seguido de EHLO para um upgrade explícito. Carregue as credenciais de um gerenciador de segredos, chame send_message(), inspecione os resultados de destinatários recusados e persista o resultado exato da tentativa. Tente novamente apenas falhas transitórias, com backoff limitado, e nunca trate a aceitação SMTP como prova de chegada à caixa de entrada.
Defina uma única operação de e-mail autorizada
Comece por um evento de produto aprovado, como verificação de conta, um recibo, um alerta solicitado ou uma notificação de segurança. Armazene um job de saída durável antes de abrir uma conexão SMTP. Esse job deve conter uma chave de evento de negócio estável, tenant, classe de mensagem, revisão do template, remetente e destinatários de envelope aprovados, identidade From visível, base de consentimento ou necessidade e o resultado atual da supressão. Entradas do navegador, do app móvel, do template e do usuário não podem escolher hosts SMTP, credenciais, remetentes de envelope, cabeçalhos arbitrários ou destinatários sem restrição. Autorize quem chama e o tenant, valide os endereços, limite a quantidade de destinatários e de anexos e evite injeção de quebras de linha. Reivindique cada job uma única vez e mantenha um histórico de tentativas somente de acréscimo. O smtplib do Python é um cliente de protocolo; ele não oferece idempotência de negócio, isolamento de tenants, consentimento, supressão nem uma fila durável. Esses controles pertencem à aplicação ao redor dele.
Monte mensagens estruturadas com EmailMessage
Use email.message.EmailMessage em vez de concatenar strings de cabeçalho e MIME. Defina From, To, Subject e um cabeçalho estável de correlação da aplicação a partir de valores validados; depois, chame set_content para o texto simples, add_alternative para uma parte HTML quando necessário e add_attachment apenas para tipos e tamanhos de arquivo explicitamente suportados. Gere o texto e o HTML a partir da mesma revisão aprovada do template. Faça o escape dos valores não confiáveis conforme o contexto de saída e evite renderizar HTML bruto do usuário. Não coloque segredos, tokens de acesso, dados pessoais desnecessários ou chaves internas do banco de dados em cabeçalhos, assuntos, campos de tracking ou nomes de anexos. O pacote email serializa de acordo com sua política e pode gerar delimitadores MIME durante o flattening; por isso, assine ou gere o hash da representação serializada final se controles de integridade posteriores dependerem dos bytes exatos. Mantenha o envelope SMTP separado: os cabeçalhos To e Cc visíveis se comunicam com os leitores, enquanto a lista de destinatários do transporte controla os comandos RCPT TO.
Escolha entre TLS implícito e STARTTLS de forma deliberada
Use SMTP_SSL quando o servidor exigir TLS desde o início da conexão. Use SMTP para uma conexão em texto claro somente quando o fluxo documentado do servidor exigir um upgrade imediato com STARTTLS. A documentação do smtplib do Python diz que starttls coloca os comandos SMTP seguintes dentro do TLS e que o cliente deve chamar ehlo novamente depois. Nunca autentique antes do upgrade TLS exigido. Crie o contexto com ssl.create_default_context, para que a validação do certificado e a checagem do hostname usem os padrões seguros de cliente, e passe o hostname esperado do servidor pela conexão normal da biblioteca. Trate a falta de suporte a STARTTLS, a falha de certificado, a divergência de hostname ou a falha na negociação TLS como parada obrigatória quando a criptografia for exigida. Não desative a verificação nem use um contexto não verificado para fazer a produção funcionar. O TLS por salto protege a conexão SMTP, não o conteúdo armazenado da mensagem, o processamento no provedor, o armazenamento no receptor nem a caixa postal final.
Mantenha as credenciais no servidor e com escopo definido
Carregue o usuário, a senha ou o token SMTP em tempo de execução a partir de um serviço gerenciado de segredos. Nunca os coloque em controle de versão, camadas do Docker, configuração commitada no Git, URLs, argumentos de linha de comando, saída de debug, ferramentas de analytics, relatórios de exceção, snapshots de teste, notebooks, tickets ou prompts. Prefira uma credencial com escopo para um ambiente, domínio de remetente ou carga de trabalho permitida a um segredo administrativo de toda a conta. Separe a produção do desenvolvimento e do CI. Torne a rotação uma rotina: provisione um substituto, atualize o worker, execute um teste de entrega controlado, confirme as evidências de autenticação e de resultado e então revogue a credencial antiga. Restrinja o acesso ao segredo ao processo de envio e audite as leituras administrativas. O método login do Python tenta os mecanismos de autenticação anunciados pelo servidor; a aplicação ainda precisa decidir se o servidor, a segurança da conexão, a conta e o mecanismo são aceitáveis. Falhas de autenticação repetidas devem pausar o grupo afetado e disparar uma investigação, em vez de novas tentativas rápidas de senha.
Use timeouts explícitos e um tempo de vida limitado para as conexões
Passe um timeout finito para SMTP ou SMTP_SSL, para que a conexão e as operações bloqueantes não ocupem um worker indefinidamente. Aplique um prazo externo para o job e uma política de cancelamento, porque um timeout de socket não é um controle completo da idade na fila. Não mantenha um objeto SMTP compartilhado entre tarefas concorrentes, a menos que o acesso seja serializado e que seu estado seja comprovadamente seguro. Um design simples abre uma conexão para um lote limitado, cumprimenta o servidor, estabelece o TLS se necessário, autentica, envia um pequeno número de mensagens, chama quit e descarta a conexão após erros ou ao atingir o limite de idade. A reutilização pode reduzir o overhead, mas aumenta a ambiguidade após desconexões do servidor, timeouts ou estado parcial. Limite as mensagens por conexão e reconecte de forma deliberada. Monitore a latência de conexão, a negociação TLS, a autenticação, a latência dos comandos, as desconexões do servidor e a idade dos jobs, sem registrar em log credenciais ou conteúdo das mensagens. O servidor SMTP pode impor limites que mudam independentemente do Python.
Envie uma mensagem e guarde os resultados de destinatários parciais
SMTP.sendmail usa from_addr e to_addrs para o envelope de transporte e não reescreve os cabeçalhos da mensagem. SMTP.send_message serializa uma EmailMessage e deriva valores padrão, a menos que valores explícitos de envelope sejam fornecidos. Em código de produção, passe explicitamente o remetente do envelope e a lista de destinatários aprovados, para que o tratamento do Bcc e a autorização do tenant permaneçam inequívocos. O Python documenta que sendmail retorna normalmente quando pelo menos um destinatário foi aceito e retorna um dicionário com cada destinatário recusado. Portanto, a ausência de exceção não significa sucesso para todos os destinatários. Armazene separadamente os escopos de destinatários aceitos e recusados, incluindo o código de status e o diagnóstico sanitizado. Não tente novamente para destinatários aceitos quando apenas alguns foram recusados. Trate cada destinatário como um resultado autorizado de forma independente, mantendo a tentativa de mensagem compartilhada. Uma exceção posterior na etapa DATA é diferente de uma recusa em RCPT e precisa de sua própria classificação.
Classifique as exceções por etapa e permanência
Trate as exceções do smtplib de forma explícita e preserve os códigos SMTP e as mensagens sanitizadas do servidor. SMTPConnectError e timeout podem ser transitórios, mas também podem revelar um host, porta ou firewall errados ou uma indisponibilidade. SMTPNotSupportedError após STARTTLS ou SMTPUTF8 deve interromper uma configuração que exige o recurso. SMTPAuthenticationError exige investigação da credencial, da conta, do mecanismo e do TLS, e não novas tentativas às cegas. SMTPSenderRefused e SMTPRecipientsRefused exigem decisões com escopo por identidade ou por destinatário. SMTPDataError descreve uma resposta inesperada ao DATA e pode representar conteúdo, política, cota ou comportamento temporário do receptor, dependendo do status estendido. Classifique respostas 4xx como candidatas a novas tentativas limitadas e 5xx como permanentes para aquela tentativa, respeitando a documentação específica do provedor. Use backoff exponencial, jitter, tetos de tentativas e de idade na fila e um estado de dead-letter. Nunca tente novamente após supressão, reclamação, descadastro, autorização revogada ou evidência de destinatário inválido.
Reconcilie resultados de submissão ambíguos
Um timeout ou desconexão de rede depois que o cliente transmitiu os dados da mensagem, mas antes de observar a resposta final do servidor, é ambíguo. O servidor pode ter assumido a responsabilidade mesmo que o Python tenha lançado uma exceção. Não crie imediatamente um novo envio lógico. Marque a tentativa como desconhecida, mantenha seus identificadores estáveis de evento e de rastreamento e consulte os logs do provedor ou os eventos de entrega posteriores, quando disponíveis. Se o serviço SMTP não oferecer idempotência nem correlação pesquisável, defina uma decisão de produto com base na classe da mensagem, na idade, no dano de uma duplicata e na experiência do usuário. Alertas de segurança e mensagens de redefinição de senha têm riscos de duplicação diferentes de recibos ou avisos financeiros. Preserve no registro a tentativa original e qualquer vínculo com novas tentativas. Nunca afirme entrega exatamente uma vez, porque o SMTP não oferece isso de ponta a ponta. Teste esse ramo com uma fixture de servidor controlada que derrube a conexão em cada etapa do protocolo, inclusive antes e depois da aceitação do DATA.
Separe a aceitação SMTP da entrega e do engajamento
Uma chamada bem-sucedida a send_message significa que pelo menos um destinatário foi aceito na etapa SMTP observada, segundo a semântica documentada do Python. Ela não comprova que todos os destinatários foram aceitos, que o servidor de destino reteve a mensagem depois, que a mensagem chegou a uma pasta de caixa de entrada ou que uma pessoa a leu. Modele como evidências separadas a submissão ao provedor, a aceitação pelo servidor do destinatário, a falha temporária ou permanente, o bounce posterior, a reclamação, o descadastro, a pasta na caixa postal e o engajamento. Ingira eventos autenticados do provedor quando disponíveis, elimine duplicatas e guarde o momento da ocorrência separado do momento do processamento. Aplique supressões por bounces permanentes, reclamações e descadastros imediatamente antes dos envios seguintes. Aberturas e cliques não são prova de transporte e podem ser afetados por tecnologias de privacidade. Mantenha métricas agregadas com privacidade minimizada por coorte segura entre tenants, revisão de template, domínio de remetente, classe de status e período. Crie alertas para picos de recusa, resultados desconhecidos, idade da fila, falhas de TLS, falhas de autenticação e dispersão incomum de destinatários.
Teste localmente sem enviar e-mails reais de clientes
Teste em unidade a construção de mensagens, a rejeição de injeção de cabeçalhos, a autorização de destinatários, a remoção de Bcc, alternativas de texto simples e HTML, o tratamento de Unicode, os limites de anexos e as verificações de supressão. Use um servidor SMTP de teste local controlado ou uma fixture de protocolo para simular falhas de saudação, ausência de STARTTLS, falha de certificado, erros de autenticação, aceitação parcial de RCPT, respostas 4xx e 5xx a DATA, desconexões e respostas atrasadas. Não use serviços de depuração sem autenticação descontinuados para segredos ou conteúdo de clientes semelhantes aos de produção. Testes de integração devem usar contas dedicadas e destinatários controlados, com cotas e limpeza explícitas. Verifique a mensagem bruta recebida, os resultados de autenticação, os cabeçalhos visíveis, o comportamento de resposta e a correlação de eventos. Execute varredura de segredos em fixtures e logs.
Como o SendHQ se encaixa
O SendHQ documenta uma API de e-mail com escopo por workspace para envio com domínio verificado, eventos de entrega e supressões. Este guia aborda o cliente SMTP da biblioteca padrão do Python; use a documentação do SendHQ para seus métodos atuais de integração e contrato de API.
Perguntas frequentes
As credenciais SMTP do Python devem ficar no código do cliente?
Não. Mantenha-as em um gerenciador de segredos no servidor, com escopo restrito de ambiente e carga de trabalho, acesso auditado, rotação rotineira e sem registro em logs.
Quando o Python deve usar SMTP_SSL?
Use SMTP_SSL quando o TLS for exigido desde o início da conexão. Use SMTP com starttls apenas em um fluxo documentado de upgrade explícito que falhe de forma fechada.
É preciso chamar EHLO novamente depois de starttls?
Sim. A documentação do smtplib do Python diz para chamar ehlo novamente depois de starttls, para que os recursos do servidor sejam redescobertos dentro da conexão protegida.
send_message significa que todos os destinatários foram aceitos?
Não. O Python pode retornar normalmente quando pelo menos um destinatário foi aceito e retorna os destinatários recusados separadamente. Persista e trate os resultados de cada destinatário de forma independente.
O que deve acontecer após um SMTPAuthenticationError?
Pause a configuração afetada e inspecione o TLS, o servidor, a conta, o segredo e os mecanismos anunciados. Novas tentativas de credenciais às cegas podem agravar bloqueios ou sinais de comprometimento.
Todo SMTPDataError deve gerar nova tentativa?
Não. Preserve o status e o diagnóstico exatos e então diferencie condições temporárias 4xx de falhas permanentes 5xx de política, conteúdo, cota ou configuração.
A aceitação SMTP determina a chegada à caixa de entrada?
Não. É uma evidência de transporte com escopo limitado. Relays posteriores, filtragem do receptor, bounces, regras da caixa postal, pasta final e engajamento humano continuam sendo resultados separados.
Este guia abrange integração específica do SendHQ?
Não. Ele abrange o cliente SMTP da biblioteca padrão do Python. Consulte a documentação do SendHQ para conhecer seus métodos atuais de integração e contrato de API.
Fontes
- Python 3 smtplib documentation — Python Software Foundation (em inglês)
- Python 3 EmailMessage documentation — Python Software Foundation (em inglês)
- Python 3 ssl documentation — Python Software Foundation (em inglês)
- RFC 5321: Simple Mail Transfer Protocol — RFC Editor (em inglês)
- RFC 3207: SMTP Service Extension for Secure SMTP over TLS — RFC Editor (em inglês)
- RFC 4954: SMTP Service Extension for Authentication — RFC Editor (em inglês)