Networking and security

SMTP: how email travels, and how to run it securely.

What the protocol is, the conversation between servers, the ports (25, 587, 465), TLS and authentication, SPF, DKIM and DMARC, open-source servers to practise with, and a Docker lab with real output.

O que é o SMTPWhat SMTP is

SMTP (Simple Mail Transfer Protocol) é o protocolo que empurra uma mensagem de um servidor para outro até ela chegar ao servidor de quem recebe. Ele só entrega; quem lê a caixa de entrada depois usa outro protocolo (IMAP ou POP3). Pense no correio: o SMTP é o caminhão que leva a carta de agência em agência; o IMAP é você abrindo a caixa postal.SMTP (Simple Mail Transfer Protocol) is the protocol that pushes a message from one server to another until it reaches the recipient's server. It only delivers; reading the inbox later uses another protocol (IMAP or POP3). Think of the postal service: SMTP is the truck carrying the letter from office to office; IMAP is you opening your mailbox.

É um protocolo de texto, nascido em 1982 e hoje definido na RFC 5321. Foi desenhado quando todo mundo se conhecia, então não tinha autenticação nem criptografia. Quase tudo o que é "segurança de e-mail" é uma camada colocada por cima depois (seção 3).It is a text protocol, born in 1982 and now defined in RFC 5321. It was designed when everyone knew each other, so it had no authentication and no encryption. Almost everything called "email security" is a layer added on top later (section 3).

MUAMUA app da AnaAna's app MSAMSA porta 587/465port 587/465 MTA origemSending MTA fila e reenvioqueue, retry MTA destinoReceiving MTA porta 25port 25 MDAMDA guardastores CaixaMailbox do BrunoBruno's SMTP SMTP SMTP locallocal DNS: registro MXDNS: MX record quem recebe example.org?who receives example.org? IMAP / POP3 (leitura)IMAP / POP3 (reading) MUA = app de e-mail · MSA = recebe do usuário · MTA = transfere entre servidores · MDA = entrega na caixaMUA = mail app · MSA = takes mail from the user · MTA = relays between servers · MDA = delivers to the mailbox Postfix, Exim e OpenSMTPD fazem MSA + MTA; Dovecot faz MDA + IMAPPostfix, Exim and OpenSMTPD do MSA + MTA; Dovecot does MDA + IMAP
O SMTP aparece três vezes no caminho; o último trecho (caixa até o leitor) já é outro protocolo.SMTP shows up three times on the path; the last stretch (mailbox to reader) is already another protocol.

O passo "quem recebe example.org?" é uma consulta DNS ao registro MX:The "who receives example.org?" step is a DNS query for the MX record:

$ dig +short MX gmail.com
10 alt1.gmail-smtp-in.l.google.com.
5  gmail-smtp-in.l.google.com.
# o número menor tem prioridade; se falhar, o MTA tenta o próximo e depois tenta de novo mais tarde
Nota pessoal, não verificada nas fontes: os hosts MX do exemplo acima são de memória e mudam; rode o dig você mesmo. O resto do texto vem das RFCs e docs citadas ao fim de cada seção.Personal note, not verified in the sources: the MX hosts in the example above are from memory and change; run dig yourself. The rest of the text comes from the RFCs and docs cited at the end of each section.

1. A conversa, comando a comando1. The conversation, command by command

Cliente e servidor trocam linhas de texto. O servidor responde com um código de três dígitos: 2xx deu certo, 3xx continue, 4xx erro temporário (tente de novo depois), 5xx erro permanente (desista). Esta é uma sessão real contra o Mailpit (seção 5); as linhas numeradas 2xx, 354 e 221 são do servidor, o resto eu digitei:Client and server exchange lines of text. The server answers with a three-digit code: 2xx worked, 3xx keep going, 4xx temporary error (try again later), 5xx permanent error (give up). This is a real session against Mailpit (section 5); the numbered lines (2xx, 354, 221) are from the server, the rest I typed:

S: 220 633819653d9a Mailpit ESMTP Service ready
C: EHLO laptop.local
S: 250-633819653d9a greets laptop.local
S: 250-SIZE 52428800
S: 250-8BITMIME
S: 250 SMTPUTF8
C: MAIL FROM:<ana@example.com>
S: 250 2.1.0 Ok
C: RCPT TO:<bruno@example.org>
S: 250 2.1.5 Ok
C: DATA
S: 354 Start mail input; end with <CR><LF>.<CR><LF>
C: From: Ana <ana@example.com>
C: To: Bruno <bruno@example.org>
C: Subject: Oi
C:
C: Primeira mensagem.
C: .
S: 250 2.0.0 Ok: queued as 4UAGUIw2T5z4DNJwPRVPgh
C: QUIT
S: 221 2.0.0 633819653d9a Mailpit ESMTP Service closing transmission channel
ComandoCommandO que fazWhat it does
EHLO nomeApresenta o cliente e pede a lista de extensões (STARTTLS, AUTH, SIZE...). O antigo HELO não lista nada.Introduces the client and asks for the list of extensions (STARTTLS, AUTH, SIZE...). The old HELO lists nothing.
STARTTLSPede para trocar a conexão aberta por uma criptografada (seção 3).Asks to upgrade the open connection to an encrypted one (section 3).
AUTHO cliente prova quem é (usuário e senha, ou token).The client proves who it is (user and password, or token).
MAIL FROMRemetente do envelope; para onde voltam os erros (bounces).The envelope sender; where errors (bounces) go back.
RCPT TODestinatário do envelope; um por comando, quantos precisar.Envelope recipient; one per command, as many as needed.
DATAComeça o conteúdo (cabeçalhos, linha em branco, corpo) e termina com uma linha só com .Starts the content (headers, blank line, body) and ends with a line holding only .
RSET / QUITCancela a transação atual / encerra a sessão.Cancels the current transaction / ends the session.
Envelope não é cabeçalho. O MAIL FROM do envelope e o From: que o leitor vê são coisas separadas, e o servidor não exige que combinem. É por isso que dá para forjar o remetente de um e-mail, e é exatamente o furo que SPF, DKIM e DMARC tentam fechar (seção 3).Envelope is not header. The envelope MAIL FROM and the From: the reader sees are separate things, and the server does not require them to match. That is why a sender can be forged, and it is exactly the hole SPF, DKIM and DMARC try to close (section 3).

Os códigos que você mais vai ver ao depurar:The codes you will see most when debugging:

CódigoCodeSignificadoMeaning
250OkOk
421 / 451Temporário: servidor ocupado, ou greylisting. O MTA reenvia sozinho.Temporary: server busy, or greylisting. The MTA retries by itself.
530Autenticação ou TLS exigidos (veja o laboratório)Authentication or TLS required (see the lab)
535Credenciais inválidasInvalid credentials
550Caixa inexistente ou mensagem recusada por política (SPF/DMARC, spam)Mailbox does not exist, or message rejected by policy (SPF/DMARC, spam)
Fontes · verificadas em 2026-10-27Sources · checked on 2026-10-27

2. Portas: 25, 587, 4652. Ports: 25, 587, 465

O SMTP usa portas diferentes para papéis diferentes. Separar os papéis é a primeira decisão de segurança: quem envia mensagem como usuário deve se autenticar; quem só recebe de outros servidores, não.SMTP uses different ports for different roles. Separating the roles is the first security decision: someone sending as a user must authenticate; a server only receiving from other servers does not.

PortaPortUsoUseCriptografiaEncryptionAutenticaçãoAuthentication
25Entre servidores (MTA para MTA)Server to server (MTA to MTA)STARTTLS oportunistaOpportunistic STARTTLSNormalmente nenhumaUsually none
587Submissão: app/usuário envia (RFC 6409)Submission: app/user sends (RFC 6409)STARTTLS (exigido)STARTTLS (required)ObrigatóriaRequired
465Submissão com TLS implícito (RFC 8314)Submission with implicit TLS (RFC 8314)TLS desde o primeiro byteTLS from the first byteObrigatóriaRequired
2525Não oficial; provedores o oferecem quando a 587 é bloqueadaUnofficial; providers offer it when 587 is blockedComo a 587Like 587ObrigatóriaRequired
1025Convenção de ferramentas de teste (Mailpit, MailHog)Convention of test tools (Mailpit, MailHog)OpcionalOptionalOpcionalOptional
Para a sua aplicação enviar e-mail: use a 465 (TLS implícito) ou a 587 (STARTTLS), sempre com usuário e senha, nunca a 25. A RFC 8314 recomenda o TLS implícito, e a 587 com STARTTLS continua amplamente usada e aceita.For your application to send email: use 465 (implicit TLS) or 587 (STARTTLS), always with a username and password, never 25. RFC 8314 recommends implicit TLS, and 587 with STARTTLS remains widely used and accepted.

A porta 25 de saída costuma ser bloqueada por provedores de nuvem e residenciais para conter spam; se o seu servidor na nuvem não consegue enviar, isso é o primeiro suspeito. Por isso quem hospeda um app costuma enviar por um serviço de relay (SES, Postmark, Mailgun, Resend) na 587/465 em vez de operar o próprio MTA.Outbound port 25 is often blocked by cloud and residential providers to curb spam; if your cloud server cannot send, that is the first suspect. That is why people hosting an app usually send through a relay service (SES, Postmark, Mailgun, Resend) on 587/465 instead of running their own MTA.

3. Como fazer com segurança3. Doing it securely

A sua pergunta tinha duas partes: "entra SSL?" e "tem um equivalente?". Entra, e o nome certo hoje é TLS (o SSL é o antecessor, já descontinuado). Mas TLS resolve só uma de quatro perguntas. O SMTP precisa de quatro camadas, cada uma com um problema diferente:Your question had two parts: "does SSL apply?" and "is there an equivalent?". It does, and the right name today is TLS (SSL is the deprecated predecessor). But TLS answers only one of four questions. SMTP needs four layers, each solving a different problem:

PerguntaQuestionCamadaLayerFerramentasTools
Alguém no caminho lê a mensagem?Can someone on the path read the message?TransporteTransportTLS: STARTTLS ou TLS implícito, MTA-STS, DANETLS: STARTTLS or implicit TLS, MTA-STS, DANE
Quem pode enviar pelo meu servidor?Who may send through my server?AcessoAccessAUTH só dentro do TLS, senhas de app, OAuth2, limites e fail2banAUTH only inside TLS, app passwords, OAuth2, limits and fail2ban
Este e-mail é mesmo do domínio que diz ser?Is this email really from the domain it claims?IdentidadeIdentitySPF, DKIM, DMARCSPF, DKIM, DMARC
Meu servidor virou um relay de spam?Did my server become a spam relay?OperaçãoOperationSem open relay, PTR, monitoramento, TLS-RPT/DMARC reportsNo open relay, PTR, monitoring, TLS-RPT/DMARC reports

3.1 Transporte: STARTTLS ou TLS implícito3.1 Transport: STARTTLS or implicit TLS

Há dois jeitos de criptografar. No TLS implícito (porta 465) o TLS começa antes de qualquer palavra do SMTP. No STARTTLS (587, 25) a conexão começa em texto puro, o servidor anuncia 250-STARTTLS no EHLO, o cliente pede STARTTLS e só então a conexão é trocada por uma criptografada.There are two ways to encrypt. With implicit TLS (port 465) TLS starts before any SMTP word. With STARTTLS (587, 25) the connection starts in plain text, the server announces 250-STARTTLS in EHLO, the client sends STARTTLS and only then is the connection upgraded to an encrypted one.

STARTTLS (587 / 25)STARTTLS (587 / 25) clienteclientservidorserver EHLO 250-STARTTLS STARTTLS 220 Ready handshake TLS; depois tudo cifradoTLS handshake; then all encrypted o início da conversa é texto purothe start of the talk is plain text TLS implícito (465)Implicit TLS (465) clienteclientservidorserver handshake TLS primeiroTLS handshake first nada em texto puronothing in plain text EHLO (cifrado) 250-AUTH ... menos um passo, menos um ponto de ataqueone step fewer, one attack point fewer
O STARTTLS é um upgrade combinado em texto puro; o TLS implícito não tem esse momento exposto.STARTTLS is an upgrade negotiated in plain text; implicit TLS has no such exposed moment.
O ataque de downgrade. Como o STARTTLS é negociado em texto puro, quem está no meio da rede pode apagar a linha 250-STARTTLS da resposta. Um cliente que trata o TLS como "se possível" segue em texto puro sem avisar. A defesa: o cliente exige TLS e aborta se ele faltar. É o que o Mailpit faz com MP_SMTP_REQUIRE_STARTTLS, e o que o laboratório mostra com o erro 530 Must issue a STARTTLS command first.The downgrade attack. Because STARTTLS is negotiated in plain text, someone in the middle of the network can strip the 250-STARTTLS line from the reply. A client that treats TLS as "if possible" carries on in plain text without warning. The defence: the client requires TLS and aborts if it is missing. That is what Mailpit does with MP_SMTP_REQUIRE_STARTTLS, and what the lab shows with the 530 Must issue a STARTTLS command first error.

Entre servidores (porta 25) o problema é maior: não há como exigir TLS de qualquer destino, porque muitos servidores antigos ainda não o oferecem. Por isso o TLS ali é oportunista (criptografa se der, e aceita qualquer certificado). Duas tecnologias fecham essa brecha para o seu domínio:Between servers (port 25) the problem is bigger: you cannot require TLS from every destination, because many old servers still do not offer it. So TLS there is opportunistic (encrypt if possible, accept any certificate). Two technologies close that gap for your domain:

  • MTA-STS (RFC 8461): você publica uma política em HTTPS (https://mta-sts.seudominio.com/.well-known/mta-sts.txt) e um registro DNS _mta-sts. Quem envia para você passa a exigir TLS válido para os seus MX. Não depende de DNSSEC.MTA-STS (RFC 8461): you publish a policy over HTTPS (https://mta-sts.yourdomain.com/.well-known/mta-sts.txt) and a _mta-sts DNS record. Senders to you then require valid TLS for your MX hosts. It does not depend on DNSSEC.
  • DANE (RFC 7672): você publica o certificado (registros TLSA) no DNS, assinado com DNSSEC. Mais forte, mas exige DNSSEC.DANE (RFC 7672): you publish the certificate (TLSA records) in DNS, signed with DNSSEC. Stronger, but requires DNSSEC.
  • TLS-RPT (RFC 8460): relatórios diários dos remetentes dizendo onde o TLS falhou com você.TLS-RPT (RFC 8460): daily reports from senders saying where TLS failed with you.
# política MTA-STS (arquivo servido em HTTPS)
version: STSv1
mode: enforce          # comece com "testing"
mx: mail.seudominio.com
max_age: 604800

# DNS
_mta-sts.seudominio.com. TXT "v=STSv1; id=20261027T000000"
_smtp._tls.seudominio.com. TXT "v=TLSRPTv1; rua=mailto:tls@seudominio.com"

3.2 Acesso: autenticação3.2 Access: authentication

Na 587/465 o cliente prova quem é com o comando AUTH (RFC 4954). Os mecanismos comuns, PLAIN e LOGIN, mandam a senha só codificada em base64, ou seja, legível. Por isso a regra é: o servidor só oferece AUTH depois do TLS. O laboratório confere: o EHLO do Mailpit, logo após o TLS, anuncia AUTH LOGIN PLAIN.On 587/465 the client proves who it is with the AUTH command (RFC 4954). The common mechanisms, PLAIN and LOGIN, send the password only base64-encoded, which means readable. So the rule is: the server only offers AUTH after TLS. The lab checks it: Mailpit's EHLO, right after TLS, announces AUTH LOGIN PLAIN.

  • Uma credencial por aplicação (senha de app, usuário SMTP dedicado), para revogar uma sem derrubar as outras.One credential per application (app password, dedicated SMTP user), so you can revoke one without breaking the others.
  • OAuth2 (XOAUTH2, OAUTHBEARER) onde o provedor suporta: o app usa um token de curta duração em vez da senha da conta.OAuth2 (XOAUTH2, OAUTHBEARER) where the provider supports it: the app uses a short-lived token instead of the account password.
  • Limite de taxa e bloqueio de tentativas falhas (fail2ban, postscreen), porque a porta 587 é alvo constante de força bruta.Rate limits and blocking of failed attempts (fail2ban, postscreen), because port 587 is a constant brute-force target.
  • Credenciais fora do código: variável de ambiente ou gerenciador de segredos, como nas outras notas.Credentials out of the code: environment variable or secrets manager, as in the other notes.

3.3 Identidade: SPF, DKIM e DMARC3.3 Identity: SPF, DKIM and DMARC

Lembra do envelope forjável? Três registros no DNS do seu domínio permitem que o destinatário verifique de quem a mensagem realmente veio:Remember the forgeable envelope? Three records in your domain's DNS let the recipient verify who a message really came from:

RespondeAnswersComoHow
SPF
RFC 7208
"Este IP pode enviar por este domínio?""May this IP send for this domain?"Registro TXT com a lista de IPs/serviços autorizados. Confere o domínio do MAIL FROM (envelope).A TXT record listing authorized IPs/services. Checks the MAIL FROM (envelope) domain.
DKIM
RFC 6376
"A mensagem foi alterada no caminho? Quem a assinou?""Was the message altered in transit? Who signed it?"O servidor assina cabeçalhos e corpo com uma chave privada; a chave pública fica no DNS (seletor._domainkey). Vai no cabeçalho DKIM-Signature. É a mesma ideia de assinatura digital da nota de criptografia.The server signs headers and body with a private key; the public key lives in DNS (selector._domainkey). It goes in the DKIM-Signature header. It is the same digital-signature idea from the cryptography note.
DMARC"O que fazer se SPF e DKIM falharem para o From: que o leitor vê?""What to do if SPF and DKIM fail for the From: the reader sees?"Registro _dmarc com a política (none, quarantine, reject). Exige que o domínio autenticado alinhe com o do From: e envia relatórios.A _dmarc record with the policy (none, quarantine, reject). Requires the authenticated domain to align with the From: domain and sends reports.
MensagemMessage chega ao MTAreaches the MTA SPFSPF IP autorizado?authorized IP? DKIMDKIM assinatura válida?valid signature? DMARCDMARC um passou Eone passed AND alinha com From:?aligns with From:? Caixa de entradaInbox Spam / rejeitadaSpam / rejected passapass falhafail o DMARC é quem usa os dois resultados e aplica a política do dono do domínioDMARC is what uses both results and applies the domain owner's policy SPF e DKIM sozinhos não dizem o que fazer; o DMARC dizSPF and DKIM alone do not say what to do; DMARC does
Quem recebe consulta o DNS do domínio remetente: três perguntas, uma decisão.The receiver queries the sender domain's DNS: three questions, one decision.
# SPF: só o MX do domínio e o relay do provedor podem enviar; o resto falha ("-all")
seudominio.com.               TXT "v=spf1 mx include:_spf.provedor.com -all"

# DKIM: chave pública do seletor "2026a"
2026a._domainkey.seudominio.com. TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqh..."

# DMARC: comece monitorando, depois suba para quarantine e reject
_dmarc.seudominio.com.        TXT "v=DMARC1; p=none; rua=mailto:dmarc@seudominio.com"

O caminho seguro é gradual: publique SPF e DKIM, ponha o DMARC em p=none e leia os relatórios agregados por algumas semanas para descobrir todos os serviços que enviam em nome do domínio (marketing, suporte, sistema de cobrança), depois suba para quarantine e por fim reject. Subir direto para reject derruba o e-mail legítimo que você esqueceu. Desde 2024 Google e Yahoo exigem SPF, DKIM e DMARC de quem envia em volume para contas deles, então isto deixou de ser opcional para app com usuários reais.The safe path is gradual: publish SPF and DKIM, set DMARC to p=none and read the aggregate reports for a few weeks to discover every service sending on the domain's behalf (marketing, support, billing), then move to quarantine and finally reject. Jumping straight to reject kills legitimate email you forgot about. Since 2024 Google and Yahoo require SPF, DKIM and DMARC from bulk senders to their accounts, so this stopped being optional for an app with real users.

Nota pessoal, não verificada nas fontes primárias: a DMARC original é a RFC 7489 (informativa). Pesquisando, encontrei só blogs de fornecedores dizendo que em maio de 2026 a IETF publicou a revisão (DMARCbis) como RFC 9989, 9990 e 9991, substituindo a 7489 e removendo tags como pct. Os registros v=DMARC1 continuam iguais; confira a RFC 9989 no rfc-editor.org antes de depender de detalhes. A exigência do Google/Yahoo é de memória (fevereiro de 2024, para remetentes de mais de 5 mil mensagens por dia).Personal note, not verified in primary sources: the original DMARC is RFC 7489 (informational). Searching, I found only vendor blogs saying that in May 2026 the IETF published the revision (DMARCbis) as RFC 9989, 9990 and 9991, replacing 7489 and removing tags such as pct. v=DMARC1 records stay the same; check RFC 9989 on rfc-editor.org before relying on details. The Google/Yahoo requirement is from memory (February 2024, for senders of more than 5,000 messages a day).

3.4 Operação: não vire um relay aberto3.4 Operation: do not become an open relay

Um open relay é um servidor que aceita e reenvia e-mail de qualquer pessoa para qualquer lugar. Spammers o encontram em horas, e o seu IP vai para listas de bloqueio. A regra é simples: o servidor só aceita mensagens (a) para domínios que ele hospeda, ou (b) de clientes autenticados (ou de redes de confiança explícitas, como um IP interno). Em Postfix, é o papel de smtpd_relay_restrictions:An open relay is a server that accepts and forwards email from anyone to anywhere. Spammers find it within hours, and your IP lands on blocklists. The rule is simple: the server only accepts messages (a) for domains it hosts, or (b) from authenticated clients (or explicit trusted networks, such as an internal IP). In Postfix, that is the job of smtpd_relay_restrictions:

# /etc/postfix/main.cf (trecho)
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
smtpd_tls_security_level = may          # porta 25: oportunista
smtpd_tls_auth_only = yes               # AUTH só dentro do TLS
disable_vrfy_command = yes              # não revela quais caixas existem
  • DNS reverso (PTR) do IP de saída apontando para o nome do seu servidor, e o nome resolvendo de volta ao IP. Sem isso, muitos destinos recusam.Reverse DNS (PTR) for the outbound IP pointing to your server's name, and the name resolving back to the IP. Without it, many destinations refuse.
  • Certificado válido (Let's Encrypt) com renovação automática, para o nome que o MX anuncia.A valid certificate (Let's Encrypt) with automatic renewal, for the name the MX announces.
  • Monitore a fila (mailq) e os logs: uma fila crescendo de repente é sinal de conta comprometida enviando spam.Monitor the queue (mailq) and logs: a suddenly growing queue is a sign of a compromised account sending spam.
  • Mantenha atualizado: servidores de e-mail ficam expostos à internet e já tiveram falhas graves (por exemplo, a smuggling de SMTP de 2023, em que terminadores de DATA ambíguos permitiam forjar mensagens).Keep it patched: mail servers are exposed to the internet and have had serious flaws (for example, the 2023 SMTP smuggling, where ambiguous DATA terminators let attackers forge messages).
Recomendação prática, e opinião minha: para um app, não opere o seu próprio MTA de saída. Use um serviço de relay (SES, Postmark, Mailgun, Resend), que já cuida de reputação de IP, PTR e filas, e configure só SPF, DKIM e DMARC no seu domínio. Operar o servidor completo é ótimo para aprender (seção 4) e razoável para caixa de e-mail própria, mas entregabilidade é um trabalho contínuo.Practical recommendation, and my opinion: for an app, do not run your own outbound MTA. Use a relay service (SES, Postmark, Mailgun, Resend), which already handles IP reputation, PTR and queues, and configure only SPF, DKIM and DMARC on your domain. Running the full server is great for learning (section 4) and reasonable for your own mailbox, but deliverability is ongoing work.
TLS não é ponta a ponta. Mesmo com tudo acima, cada servidor do caminho lê a mensagem em claro. Para o conteúdo ficar secreto até o leitor é preciso criptografar a própria mensagem (S/MIME ou OpenPGP), o que está fora do escopo desta nota.TLS is not end to end. Even with everything above, every server on the path reads the message in the clear. To keep the content secret until the reader you must encrypt the message itself (S/MIME or OpenPGP), which is outside this note's scope.

4. Projetos open-source para praticar4. Open-source projects to practise with

Há dois grupos com objetivos diferentes. As caixas de areia capturam e mostram o que o seu código enviou, sem entregar nada a ninguém; são para desenvolver e testar. Os servidores de verdade entregam e-mail de verdade; são para aprender a operar.There are two groups with different goals. Sandboxes capture and show what your code sent, delivering nothing to anyone; they are for developing and testing. Real servers deliver real email; they are for learning to operate.

ProjetoProjectTipoTypePara quêWhat for
MailpitSandboxSandboxServidor SMTP de teste (porta 1025) com interface web (8025) e API REST; aceita STARTTLS, TLS e autenticação opcionais. Comece por aqui (seção 5).Test SMTP server (port 1025) with a web UI (8025) and REST API; optional STARTTLS, TLS and authentication. Start here (section 5).
MailHogSandboxSandboxO clássico, com as mesmas portas. A página do repositório mostra 2014-2017 e não indica manutenção ativa; hoje o Mailpit é o substituto mais usado.The classic, with the same ports. The repository page shows 2014-2017 and no sign of active maintenance; Mailpit is now the most-used replacement.
docker-mailserverServidor completoFull serverPostfix (SMTP) + Dovecot (IMAP) + Rspamd, ClamAV, OpenDKIM, OpenDMARC, Fail2ban em um contêiner; configuração em arquivos; Let's Encrypt, certificado manual ou autoassinado. O melhor para ver as seções 2 e 3 funcionando juntas.Postfix (SMTP) + Dovecot (IMAP) + Rspamd, ClamAV, OpenDKIM, OpenDMARC, Fail2ban in one container; file-based config; Let's Encrypt, manual or self-signed certificate. The best to see sections 2 and 3 working together.
StalwartServidor completoFull serverEscrito em Rust: SMTP, IMAP, JMAP, POP3 e também CalDAV/CardDAV, em um binário só. Licença AGPL-3.0 (com uma edição Enterprise). Bom para ver uma implementação moderna.Written in Rust: SMTP, IMAP, JMAP, POP3 and also CalDAV/CardDAV, in a single binary. AGPL-3.0 license (with an Enterprise edition). Good for seeing a modern implementation.
PostfixMTAMTAO MTA mais comum; o main.cf da seção 3.4 é dele. Estudar o Postfix puro ensina o que o docker-mailserver esconde.The most common MTA; the main.cf in section 3.4 is its. Studying plain Postfix teaches what docker-mailserver hides.
OutrosOthersMTAExim, OpenSMTPD, Haraka (em Node.js, extensível por plugins), Maddy (um binário em Go), Mailu e Mail-in-a-Box (pacotes prontos). Citados de memória; confira o estado de cada um antes de escolher.Exim, OpenSMTPD, Haraka (Node.js, extensible by plugins), Maddy (a single Go binary), Mailu and Mail-in-a-Box (ready-made bundles). Mentioned from memory; check each one's status before choosing.
Servidor de verdade em casa raramente entrega. Para o Gmail aceitar seu e-mail você precisa de IP fixo, PTR, porta 25 liberada, SPF, DKIM e DMARC, e ainda assim um IP novo parte com má reputação. Pratique tudo em localhost e com domínios de teste; só ponha um servidor na internet depois de ter fechado o relay (seção 3.4).A real server at home rarely delivers. For Gmail to accept your email you need a fixed IP, PTR, port 25 open, SPF, DKIM and DMARC, and even then a new IP starts with a bad reputation. Practise everything on localhost and with test domains; only put a server on the internet after closing the relay (section 3.4).
Fontes · verificadas em 2026-10-27Sources · checked on 2026-10-27

5. Laboratório em Docker: Mailpit com TLS e autenticação5. Docker lab: Mailpit with TLS and authentication

Vamos subir um servidor SMTP que exige STARTTLS e senha, enviar um e-mail por um script Python e ver cada tentativa insegura ser recusada. Tudo em 127.0.0.1; nada sai do seu computador. Testado com Docker e a imagem axllent/mailpit em 27/10/2026.We will start an SMTP server that requires STARTTLS and a password, send an email from a Python script and watch every insecure attempt get refused. Everything on 127.0.0.1; nothing leaves your computer. Tested with Docker and the axllent/mailpit image on 2026-10-27.

Parte A: a conversa em texto puroPart A: the conversation in plain text

docker run -d --rm --name mp -p 127.0.0.1:1025:1025 -p 127.0.0.1:8025:8025 axllent/mailpit

# fale SMTP "na mão" (a sessão da seção 1)
printf 'EHLO laptop.local\r\nMAIL FROM:<ana@example.com>\r\nRCPT TO:<bruno@example.org>\r\nDATA\r\nSubject: Oi\r\n\r\nOla.\r\n.\r\nQUIT\r\n' | nc localhost 1025

# abra http://localhost:8025 e veja a mensagem na interface
docker rm -f mp

Parte B: exigir TLS e senhaPart B: requiring TLS and a password

mkdir smtp-lab && cd smtp-lab && mkdir certs

# certificado autoassinado, só para o laboratório
openssl req -x509 -newkey rsa:2048 -nodes -days 30 -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost" -keyout certs/key.pem -out certs/cert.pem

# arquivo de senhas (bcrypt): usuário "ana", senha "segredo"
docker run --rm httpd:2.4-alpine htpasswd -nbB ana segredo > auth.txt

compose.yaml:compose.yaml:

services:
  mailpit:
    image: axllent/mailpit
    ports:
      - "127.0.0.1:8025:8025"   # interface web
      - "127.0.0.1:1025:1025"   # SMTP
    volumes:
      - ./certs:/certs:ro
      - ./auth.txt:/auth.txt:ro
    environment:
      MP_SMTP_TLS_CERT: /certs/cert.pem
      MP_SMTP_TLS_KEY: /certs/key.pem
      MP_SMTP_REQUIRE_STARTTLS: "true"
      MP_SMTP_AUTH_FILE: /auth.txt

send.py (cliente seguro: confia só no nosso certificado, faz STARTTLS e depois login):send.py (secure client: trusts only our certificate, does STARTTLS and then login):

import smtplib, ssl, sys
from email.message import EmailMessage

msg = EmailMessage()
msg["From"] = "ana@example.com"
msg["To"] = "bruno@example.org"
msg["Subject"] = "Olá pelo SMTP"
msg.set_content("Corpo da mensagem.\n")

ctx = ssl.create_default_context(cafile="certs/cert.pem")   # confia só no nosso certificado
with smtplib.SMTP("localhost", 1025) as s:
    s.starttls(context=ctx)
    s.login("ana", sys.argv[1])
    s.send_message(msg)
print("enviado")

Suba e rode os quatro testes. Esta é a saída real:Start it and run the four tests. This is the real output:

docker compose up -d
# [smtpd] starting on [::]:1025 (STARTTLS required)

# 1. sem TLS: recusado
python3 -c "import smtplib; smtplib.SMTP('localhost',1025).sendmail('a@x.com',['b@y.org'],'Subject: x\r\n\r\nhi')"
# SMTPSenderRefused: (530, b'5.7.0 Must issue a STARTTLS command first')

# 2. TLS, senha errada: recusado
python3 send.py errada
# SMTPAuthenticationError: (535, b'5.7.8 Authentication credentials invalid')

# 3. TLS e senha certa: aceito
python3 send.py segredo
# enviado

# 4. inspecionar o TLS e o que o servidor anuncia depois dele
echo QUIT | openssl s_client -starttls smtp -connect localhost:1025 -CAfile certs/cert.pem
# Verification: OK
# New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256
# Verify return code: 0 (ok)

# a mensagem chegou
curl -s localhost:8025/api/v1/messages | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['total'], d['messages'][0]['Subject'])"
# 1 Olá pelo SMTP

O teste 1 é a defesa contra o downgrade da seção 3.1 em ação, o 2 mostra o 535 e o 3 mostra o caminho feliz. Sem o cafile, o Python recusaria o certificado autoassinado: é a verificação de certificado funcionando, e o motivo de nunca desligá-la (CERT_NONE) para "fazer funcionar".Test 1 is the section 3.1 downgrade defence in action, 2 shows the 535 and 3 shows the happy path. Without the cafile, Python would refuse the self-signed certificate: that is certificate verification working, and the reason never to turn it off (CERT_NONE) just to "make it work".

Só no laboratório. O certificado autoassinado e a senha "segredo" são para teste. Em produção use um certificado de uma CA (Let's Encrypt) e segredos de verdade. Note também que o Mailpit avisa, no log, que a interface web (8025) fica sem autenticação; por isso o compose.yaml publica as portas só em 127.0.0.1.Lab only. The self-signed certificate and the "segredo" password are for testing. In production use a CA certificate (Let's Encrypt) and real secrets. Also note Mailpit warns in its log that the web UI (8025) has no authentication; that is why compose.yaml publishes ports only on 127.0.0.1.

Limpar e exercíciosCleanup and exercises

docker compose down -v
  1. Faça um EHLO na mão antes e depois do TLS (openssl s_client -starttls smtp -connect localhost:1025 -crlf). Em qual dos dois aparece AUTH LOGIN PLAIN? Por quê?Do an EHLO by hand before and after TLS (openssl s_client -starttls smtp -connect localhost:1025 -crlf). In which of the two does AUTH LOGIN PLAIN appear? Why?
  2. No send.py, troque o cafile por ssl.create_default_context() e rode. Que erro aparece? Leia a mensagem.In send.py, replace the cafile with ssl.create_default_context() and run it. What error appears? Read the message.
  3. Mande um e-mail com From: presidente@example.com no cabeçalho e outro MAIL FROM no envelope. O Mailpit aceita? O que isso prova sobre a seção 1?Send an email with From: presidente@example.com in the header and a different envelope MAIL FROM. Does Mailpit accept it? What does that prove about section 1?
  4. Suba o docker-mailserver seguindo a documentação dele, crie uma conta e repita o openssl s_client na 587 e na 465. Compare o que muda entre STARTTLS e TLS implícito (-connect localhost:465 sem -starttls).Start docker-mailserver following its documentation, create an account and repeat openssl s_client on 587 and on 465. Compare what changes between STARTTLS and implicit TLS (-connect localhost:465 without -starttls).
  5. Use dig TXT no SPF, no DMARC e no seletor DKIM de um domínio que você usa e leia os registros com o que aprendeu na seção 3.3.Use dig TXT on the SPF, DMARC and a DKIM selector of a domain you use and read the records with what you learned in section 3.3.

6. Checklist6. Checklist

  • Aplicação envia pela 587 (STARTTLS) ou 465 (TLS implícito), autenticada, com TLS obrigatório e certificado verificado.The application sends via 587 (STARTTLS) or 465 (implicit TLS), authenticated, with TLS required and the certificate verified.
  • Uma credencial por aplicação, fora do código, revogável.One credential per application, out of the code, revocable.
  • Servidor oferece AUTH só depois do TLS; relay fechado (reject_unauth_destination); VRFY desligado.Server offers AUTH only after TLS; relay closed (reject_unauth_destination); VRFY off.
  • SPF, DKIM e DMARC publicados; DMARC começa em p=none e sobe com base nos relatórios.SPF, DKIM and DMARC published; DMARC starts at p=none and rises based on the reports.
  • MTA-STS (e TLS-RPT) no domínio que recebe e-mail; DANE se você já tem DNSSEC.MTA-STS (and TLS-RPT) on the domain that receives email; DANE if you already have DNSSEC.
  • PTR do IP de saída, certificado válido com renovação, fila e logs monitorados, software atualizado.PTR for the outbound IP, valid certificate with renewal, queue and logs monitored, software patched.
  • Em desenvolvimento, use uma caixa de areia (Mailpit); nunca aponte testes para um relay real com endereços de clientes.In development, use a sandbox (Mailpit); never point tests at a real relay with customers' addresses.
  • Para um app, prefira um serviço de relay a operar o próprio MTA.For an app, prefer a relay service to running your own MTA.
Limites desta nota. As RFCs e as documentações dos projetos foram consultadas em 27/10/2026. O laboratório da seção 5 foi executado de verdade (Mailpit, Docker, Python, OpenSSL); o docker-mailserver, o Stalwart, o MTA-STS, o DKIM e o Postfix foram descritos pela documentação e não foram executados por mim. A lista de "outros" projetos e os exemplos de DNS são de memória.Limits of this note. The RFCs and project documentation were consulted on 2026-10-27. The section 5 lab was actually run (Mailpit, Docker, Python, OpenSSL); docker-mailserver, Stalwart, MTA-STS, DKIM and Postfix were described from the documentation and not run by me. The "others" project list and the DNS examples are from memory.