Skip to content
Comece com 100 créditos de verificação grátis
Qualisend
Todos os artigos
Engenharia / 16 de junho de 2026

Como validar um endereço de e-mail em Ruby

8 minutes read

Qualisend team
Uma janela de editor de código intitulada validate.rb com uma marca de gema ruby vermelha e uma pílula verde de resultado entregável, acima da legenda sintaxe para MX para caixa postal.

Validar um endereço de e-mail em Ruby são três tarefas escondidas atrás de um único nome de método. A maioria dos tutoriais te entrega um regex, vê ele passar e declara o endereço válido — mas uma correspondência de padrão só te diz que a string está com o formato certo, não que o e-mail um dia vai chegar até ela. A validação de verdade é feita em camadas: uma verificação barata de sintaxe, uma consulta DNS aos servidores de e-mail do domínio e uma sondagem SMTP da própria caixa postal. A biblioteca padrão do Ruby te dá as duas primeiras sem nenhuma gem; a terceira é um problema de rede que vale a pena repassar. Este guia monta cada camada com código funcional e mostra exatamente onde a biblioteca padrão para.

A resposta curta#

Use o URI::MailTo::EMAIL_REGEXP da biblioteca uri para a sintaxe, o Resolv::DNS da biblioteca resolv para a consulta de MX e uma API de verificação para a checagem SMTP da caixa postal — do mais barato ao mais caro, interrompendo assim que uma camada for decisiva. Não recorra ao net/smtp para sondar caixas postais a partir do seu próprio app: a porta 25 de saída fica bloqueada na maioria dos provedores, e a resposta depende da reputação do seu IP de envio e de um comportamento de greylisting que você não vai querer reimplementar. Cada camada descarta endereços de forma mais barata que a anterior; só a última consegue aprovar um endereço. Esse último passo é a lacuna que a verificação de e-mail existe para fechar.

Camada 1: sintaxe#

O regex pertence a este lugar e a nenhum outro — e em Ruby você nem precisa escrever um. A biblioteca uri, parte da biblioteca padrão, traz um padrão bem testado em URI::MailTo::EMAIL_REGEXP, então recorra a ele em vez de colar algo do Stack Overflow:

require "uri"

def valid_syntax?(email)
  email.is_a?(String) &&
    email.length <= 320 &&
    email.match?(URI::MailTo::EMAIL_REGEXP)
end
valid_syntax?("jane@example.com")  # => true
valid_syntax?("not-an-email")      # => false
valid_syntax?("a@@b.com")          # => false

Duas coisas valem a pena saber sobre essa constante. Ela já vem ancorada com \A e \z, então match? testa a string inteira em vez de um trecho — você não precisa envolvê-la nem ancorá-la por conta própria. E ela é deliberadamente permissiva: segue a definição de e-mail válido da WHATWG/HTML5, que é mais flexível que a RFC 5322 e, por exemplo, aceita alegremente jane@localhost porque não exige um ponto no domínio. Isso é um recurso, não um defeito. A função da primeira camada é pegar erros de digitação na entrada, não julgar as RFCs — o que nem ajudaria de qualquer forma. A proteção de 320 caracteres é uma dupla garantia: nada mais longo pode ser um endereço real, e é mais barato rejeitar cedo do que repassar uma string gigante adiante. Uma aprovação aqui significa "vale a pena checar", nunca "válido".

Camada 2: o domínio consegue receber e-mail?#

É aqui que a biblioteca padrão do Ruby mostra seu valor. Um domínio sem rota de e-mail não consegue aceitar mensagens para ninguém, então uma única consulta de MX elimina domínios mortos, nomes de empresas escritos errado e TLDs inventados. A biblioteca resolv resolve os registros sem nenhuma gem:

require "resolv"

def mail_route?(domain)
  records = Resolv::DNS.open do |dns|
    dns.getresources(domain, Resolv::DNS::Resource::IN::MX)
  end
  !records.empty?
rescue Resolv::ResolvError, Resolv::ResolvTimeout
  false
end
mail_route?("gmail.com")               # => true
mail_route?("company-that-folded.com") # => false

O Resolv::DNS.open te entrega um resolvedor e o fecha quando o bloco termina, e o getresources retorna um array de registros MX — um array vazio quando o domínio não publica nenhum ou não existe, que é exatamente o caso de "sem rota" que você quer pegar. O rescue trata o outro modo de falha: um resolvedor que dá erro ou estoura o tempo limite. Note que Resolv::ResolvTimeout não é uma subclasse de Resolv::ResolvError, então nomeie os dois se você quiser que um servidor de nomes lento passe de forma limpa em vez de derrubar a chamada.

Quando você quiser os servidores de e-mail em si em vez de um sim/não — para registrá-los ou inspecionar as prioridades — cada registro carrega exchange e preference, então ordene por este último:

def mail_hosts(domain)
  records = Resolv::DNS.open do |dns|
    dns.getresources(domain, Resolv::DNS::Resource::IN::MX)
  end
  records.sort_by(&:preference).map { |mx| mx.exchange.to_s }
  # => ["gmail-smtp-in.l.google.com", "alt1.gmail-smtp-in.l.google.com", ...]
rescue Resolv::ResolvError, Resolv::ResolvTimeout
  []
end

Alguns domínios aceitam e-mail em um registro A com um MX implícito; se você precisar honrar esse caso extremo, recorra a getresources(domain, Resolv::DNS::Resource::IN::A) quando o conjunto de MX voltar vazio. Para a esmagadora maioria dos endereços reais, uma checagem de MX é o filtro certo.

Camada 3: a caixa postal existe de fato?#

As camadas um e dois só conseguem descartar um endereço. Um domínio pode publicar registros MX perfeitos e ainda assim não ter caixa postal no endereço que você tem em mãos — noreply-9f2x@gmail.com é sintaxe válida em uma rota de e-mail ativa, e mesmo assim é uma caixa que nunca foi criada. Confirmar que uma caixa postal específica existe significa a conversa de entrega SMTP: conectar ao servidor de e-mail, emitir RCPT TO, ler a resposta e desconectar antes de enviar qualquer coisa. Há mais coisa nisso do que parece — como funciona a verificação de e-mail percorre o pipeline completo, incluindo os domínios catch-all, que aceitam todo endereço e derrotam uma sondagem ingênua.

A biblioteca padrão do Ruby vai abrir alegremente essa conversa para você com o net/smtp. O problema não é o código; é a rede. Rode uma sondagem SMTP a partir do seu servidor de aplicação e três coisas dão errado: a maioria dos provedores de nuvem bloqueia inteiramente a porta 25 de saída, a resposta depende da reputação do IP de onde você conecta, e os servidores receptores fazem greylisting e limitam a taxa de remetentes desconhecidos — então uma sondagem que funciona no seu notebook falha silenciosamente, ou te coloca em blocklist, em produção. Esta é a camada que vale a pena delegar.

Fazendo a checagem completa com uma API#

O endpoint de verificação da Qualisend roda o pipeline inteiro — sintaxe, DNS e a sondagem SMTP da caixa postal — a partir de uma infraestrutura com reputação gerenciada, criada para isso, e retorna um único veredito. Como é um simples POST em JSON, o net/http da biblioteca padrão dá conta sem nenhuma gem. Leia sua chave a partir do ambiente em vez de deixá-la fixa no código:

require "net/http"
require "json"
require "uri"

VERIFY_URL = URI("https://api.qualisend.com/v1/verify")

def verify(email)
  request = Net::HTTP::Post.new(VERIFY_URL)
  request["Authorization"] = "Bearer #{ENV.fetch('QUALISEND_API_KEY')}"
  request["Content-Type"]  = "application/json"
  request.body = JSON.generate(email: email)

  response = Net::HTTP.start(VERIFY_URL.hostname, VERIFY_URL.port, use_ssl: true) do |http|
    http.request(request)
  end

  raise "Qualisend responded #{response.code}" unless response.is_a?(Net::HTTPSuccess)

  JSON.parse(response.body, symbolize_names: true)
end

Esse endpoint é um espaço reservado — confira a referência da API para a URL base exata, o formato da requisição e como gerar uma chave com escopo. A resposta envolve um objeto result:

{
  "result": {
    "status": "deliverable",
    "score": 95,
    "reason": null,
    "sub_flags": { "role": false, "disposable": false, "catch_all": false }
  }
}

Alcance dentro do envelope e aja sobre o status:

body   = verify("jane@example.com")
result = body[:result]

result[:status]    # "deliverable" | "risky" | "undeliverable" | "unknown"
result[:score]     # confidence, 0–100
result[:reason]    # machine-readable reason, or nil
result[:sub_flags] # { role:, disposable:, catch_all:, ... }

O net/http mantém isso livre de dependências, o que é conveniente em uma tarefa em segundo plano ou em um serviço pequeno. Se o seu app já se apoia no Faraday ou no HTTParty, a mesma requisição fica algumas linhas mais curta — o envelope que você lê de volta é idêntico.

Uma forma completa de validar um endereço de e-mail em Ruby#

Encadeie as três camadas do mais barato para o mais caro e pare no momento em que uma delas for decisiva:

def validate_email(email)
  return { status: "undeliverable", reason: "invalid_email" } unless valid_syntax?(email)

  domain = email.rpartition("@").last
  return { status: "undeliverable", reason: "invalid_domain" } unless mail_route?(domain)

  verify(email)[:result] # deliverable | risky | undeliverable | unknown
end

O rpartition("@") divide no último @, então você sempre pega o domínio real mesmo com partes locais entre aspas de forma estranha. As duas camadas locais não custam nada e pegam a maior parte do lixo na hora; a API roda só nos endereços que valem a ida e volta pela rede. Essa ordenação — e não a linguagem — é o que torna a validação confiável, e é o mesmo formato que você vai encontrar nas versões em Node.js e Python deste guia.

Quem usa Rails pode encaixar tudo isso em um model como uma validação personalizada, para que um endereço ruim nunca chegue ao banco de dados:

class User < ApplicationRecord
  validate :email_must_be_deliverable

  private

  def email_must_be_deliverable
    return if email.blank?

    verdict = validate_email(email)
    errors.add(:email, "doesn't look deliverable") if verdict[:status] == "undeliverable"
  end
end

Mantenha no caminho síncrono apenas as duas camadas locais rápidas mais o veredito imediato da API; qualquer coisa mais lenta pertence a uma tarefa em segundo plano. Mas o núcleo é Ruby puro, e ele se comporta da mesma forma em uma rota do Sinatra, em uma tarefa rake ou em um script avulso — a estratégia em camadas é o que faz o trabalho.

Perguntas frequentes#

Consigo validar um endereço de e-mail em Ruby sem nenhuma gem?#

Em duas das três camadas, sim. O URI::MailTo::EMAIL_REGEXP da biblioteca uri cuida da sintaxe, e o Resolv::DNS da biblioteca resolv resolve os registros MX — ambos já vêm com o Ruby, então uma verificação de sintaxe mais domínio não exige nada instalado. A terceira camada, confirmar a própria caixa postal, exige uma sondagem SMTP que você não deveria executar a partir do seu próprio servidor. É justamente essa parte que um serviço de verificação resolve para você.

O URI::MailTo::EMAIL_REGEXP é bom o bastante sozinho?#

É a verificação certa para a primeira camada e uma aposta muito melhor do que um regex feito à mão — mas ele valida o formato, não a entregabilidade. Ele nunca resolve DNS nem contata um servidor de e-mail, e é permissivo por design (aceita jane@localhost), então uma aprovação significa "parece um e-mail", não "vai ser entregue". Combine-o com uma consulta de MX e uma verificação da caixa postal antes de confiar no endereço.

Por que não usar net/smtp para checar se uma caixa postal existe?#

O net/smtp consegue abrir a conversa SMTP, mas executá-lo a partir do seu servidor de aplicação é pouco confiável e arriscado: a maioria dos provedores bloqueia a porta 25 de saída, a resposta depende da reputação do seu IP de envio, e sondar em qualquer volume faz você levar limite de taxa ou entrar em blocklist. Uma API de verificação executa a sondagem a partir de uma infraestrutura com reputação gerenciada, criada exatamente para isso.

Devo validar e-mails no cadastro ou ao limpar uma lista?#

Nos dois, com profundidades diferentes. Rode a sintaxe e a consulta de MX de forma síncrona no cadastro — elas são rápidas o suficiente para bloquear a requisição e dar retorno imediato — e aja também ali sobre o veredito imediato da API. Reserve o trabalho mais profundo e mais lento para a limpeza de listas e tarefas de bastidores; o guia de cadastro serverless mostra o padrão ao vivo de ponta a ponta.


Pronto para adicionar a camada da caixa postal? A referência da API tem o endpoint /verify, chaves com escopo e trechos de copiar e colar, ou cole um endereço no verificador de e-mail gratuito e veja uma string sintaticamente perfeita voltar como não entregável.

Your reputation, protected.

Clean your first list in minutes. 100 free credits, no card required.

Get started