Skip to content
Commencez avec 100 crédits de vérification gratuits
Qualisend
Tous les articles
Ingénierie / 16 juin 2026

Comment valider une adresse e-mail en Ruby

8 minutes read

Qualisend team
Une fenêtre d'éditeur de code intitulée validate.rb avec un logo de gemme ruby rouge et une pastille de résultat « deliverable » verte, au-dessus de la légende syntaxe vers MX vers boîte aux lettres.

Valider une adresse e-mail en Ruby, ce sont trois tâches qui se cachent derrière un seul nom de méthode. La plupart des tutoriels vous tendent une regex, la regardent réussir et déclarent l'adresse valide — mais une correspondance de motif indique seulement que la chaîne a la bonne forme, pas que du courrier l'atteindra un jour. La vraie validation se fait en couches : une vérification de syntaxe peu coûteuse, une recherche DNS des serveurs de messagerie du domaine, et une sonde SMTP de la boîte aux lettres réelle. La bibliothèque standard de Ruby vous offre les deux premières sans aucune gem ; la troisième est un problème de réseau qu'il vaut mieux déléguer. Ce guide construit chaque couche avec du code fonctionnel et montre exactement où la bibliothèque standard s'arrête.

La réponse courte#

Utilisez URI::MailTo::EMAIL_REGEXP de la bibliothèque uri pour la syntaxe, Resolv::DNS de la bibliothèque resolv pour la recherche MX, et une API de vérification pour la vérification SMTP de la boîte aux lettres — la moins coûteuse en premier, en court-circuitant dès qu'une couche est décisive. N'allez pas chercher net/smtp pour sonder les boîtes aux lettres depuis votre propre application : le port 25 sortant est bloqué sur la plupart des hébergeurs, et la réponse dépend de la réputation de votre IP d'envoi et d'un comportement de greylisting que vous n'avez pas envie de réimplémenter. Chaque couche écarte des adresses de manière moins coûteuse que la précédente ; seule la dernière peut valider une adresse. Cette ultime étape est précisément la lacune que la vérification d'e-mails existe pour combler.

Couche 1 : la syntaxe#

La regex a sa place ici et nulle part ailleurs — et en Ruby, vous n'avez même pas à en écrire une. La bibliothèque uri, qui fait partie de la bibliothèque standard, fournit un motif bien testé dans URI::MailTo::EMAIL_REGEXP ; allez le chercher plutôt que de coller quelque chose trouvé sur 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

Deux choses méritent d'être connues à propos de cette constante. Elle est déjà ancrée avec \A et \z, si bien que match? teste la chaîne entière plutôt qu'une sous-chaîne — vous n'avez pas besoin de l'entourer ni de l'ancrer vous-même. Et elle est délibérément permissive : elle suit la définition WHATWG/HTML5 d'un e-mail valide, qui est plus large que la RFC 5322 et, par exemple, accepte volontiers jane@localhost parce qu'elle n'exige pas de point dans le domaine. C'est une fonctionnalité, pas un bug. Le rôle de la première couche est d'attraper les fautes de frappe à la saisie, pas de plaider les RFC — ce qui n'aiderait de toute façon pas. Le garde-fou à 320 caractères est une double sécurité : rien de plus long ne peut être une vraie adresse, et il est moins coûteux de rejeter tôt que de transmettre une chaîne géante en aval. Un succès ici signifie « vaut la peine d'être vérifiée », jamais « valide ».

Couche 2 : le domaine peut-il recevoir du courrier ?#

C'est ici que la bibliothèque standard de Ruby gagne son pain. Un domaine sans route de messagerie ne peut accepter de courrier pour personne, donc une seule recherche MX élimine les domaines morts, les noms d'entreprise mal orthographiés et les TLD inventés. La bibliothèque resolv résout les enregistrements sans aucune 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

Resolv::DNS.open vous remet un résolveur et le ferme à la sortie du bloc, et getresources renvoie un tableau d'enregistrements MX — un tableau vide lorsque le domaine n'en publie aucun ou n'existe pas, ce qui est exactement le cas « pas de route » que vous voulez attraper. Le rescue gère l'autre mode de défaillance : un résolveur qui échoue ou expire. Notez que Resolv::ResolvTimeout n'est pas une sous-classe de Resolv::ResolvError, donc nommez les deux si vous voulez qu'un serveur de noms lent passe proprement plutôt que de faire exploser l'appel.

Quand vous voulez les serveurs de messagerie réels plutôt qu'un oui/non — pour les journaliser ou en inspecter les priorités — chaque enregistrement porte exchange et preference, alors triez par cette dernière :

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

Certains domaines acceptent le courrier sur un enregistrement A avec un MX implicite ; si vous avez besoin d'honorer ce cas particulier, repliez-vous sur getresources(domain, Resolv::DNS::Resource::IN::A) lorsque l'ensemble MX revient vide. Pour l'écrasante majorité des adresses réelles, une vérification MX est le bon filtre.

Couche 3 : la boîte aux lettres existe-t-elle vraiment ?#

Les couches un et deux ne peuvent qu'écarter une adresse. Un domaine peut publier des enregistrements MX parfaits et n'avoir malgré tout aucune boîte aux lettres à l'adresse que vous détenez — noreply-9f2x@gmail.com a une syntaxe valide sur une route de messagerie active, et c'est pourtant une boîte qui n'a jamais été créée. Confirmer qu'une boîte aux lettres précise existe implique la conversation de livraison SMTP : se connecter au serveur de messagerie, émettre RCPT TO, lire la réponse et se déconnecter avant d'envoyer quoi que ce soit. Il y a plus que cela — comment fonctionne la vérification d'e-mails parcourt tout le pipeline, y compris les domaines catch-all qui acceptent toutes les adresses et déjouent une sonde naïve.

La bibliothèque standard de Ruby ouvrira volontiers cette conversation pour vous avec net/smtp. Le problème n'est pas le code ; c'est le réseau. Exécutez une sonde SMTP depuis votre serveur applicatif et trois choses tournent mal : la plupart des fournisseurs cloud bloquent entièrement le port 25 sortant, la réponse dépend de la réputation de l'IP depuis laquelle vous vous connectez, et les serveurs de réception appliquent du greylisting et limitent le débit des expéditeurs inconnus — si bien qu'une sonde qui fonctionne sur votre ordinateur portable échoue discrètement, ou vous fait mettre sur liste de blocage, en production. C'est la couche qu'il vaut la peine de déléguer.

Effectuer la vérification complète avec une API#

Le point de terminaison de vérification de Qualisend exécute tout le pipeline — syntaxe, DNS et la sonde SMTP de la boîte aux lettres — depuis une infrastructure à réputation gérée conçue pour cela, et renvoie un verdict unique. Comme il s'agit d'un simple POST JSON, le net/http de la bibliothèque standard le couvre sans aucune gem. Lisez votre clé depuis l'environnement plutôt que de la coder en dur :

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

Ce point de terminaison est un espace réservé — consultez la référence de l'API pour l'URL de base exacte, la forme de la requête et la façon de créer une clé restreinte. La réponse enveloppe un objet result :

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

Plongez dans l'enveloppe et agissez sur 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:, ... }

net/http garde tout cela sans dépendance, ce qui est pratique dans une tâche d'arrière-plan ou un petit service. Si votre application s'appuie déjà sur Faraday ou HTTParty, la même requête tient en quelques lignes de moins — l'enveloppe que vous relisez est identique.

Une façon complète de valider une adresse e-mail en Ruby#

Enchaînez les trois couches, la moins coûteuse en premier, et arrêtez-vous dès que l'une est décisive :

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

rpartition("@") découpe sur le dernier @, de sorte que vous prenez toujours le vrai domaine, même pour des parties locales curieusement mises entre guillemets. Les deux couches locales ne coûtent rien et attrapent instantanément l'essentiel des déchets ; l'API ne s'exécute que sur les adresses qui valent l'aller-retour réseau. C'est cet ordre — et non le langage — qui rend la validation fiable, et c'est la même structure que vous trouverez dans les versions Node.js et Python de ce guide.

Les utilisateurs de Rails peuvent glisser l'ensemble dans un modèle sous forme de validation personnalisée, afin qu'une mauvaise adresse n'atteigne jamais la base de données :

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

Gardez le chemin synchrone limité aux deux couches locales rapides plus le verdict immédiat de l'API ; tout ce qui est plus lent relève d'une tâche d'arrière-plan. Mais le cœur est du Ruby pur, et il se comporte de la même façon dans une route Sinatra, une tâche rake ou un script ponctuel — c'est le découpage en couches qui fait le travail.

Foire aux questions#

Puis-je valider une adresse e-mail en Ruby sans aucune gem ?#

Pour deux des trois couches, oui. URI::MailTo::EMAIL_REGEXP, issu de la bibliothèque uri, gère la syntaxe, et Resolv::DNS, issu de la bibliothèque resolv, résout les enregistrements MX — les deux sont livrés avec Ruby, donc une vérification syntaxe-plus-domaine ne nécessite aucune installation. La troisième couche, la confirmation de la boîte aux lettres elle-même, implique une sonde SMTP que vous ne devriez pas exécuter depuis votre propre serveur. C'est justement la partie qu'un service de vérification prend en charge à votre place.

URI::MailTo::EMAIL_REGEXP suffit-il à lui seul ?#

C'est la bonne vérification de première couche et un choix bien plus sûr qu'une regex écrite à la main — mais elle valide la forme, pas la délivrabilité. Elle ne résout jamais le DNS ni ne contacte de serveur de messagerie, et elle est permissive par conception (elle accepte jane@localhost), si bien qu'un succès signifie « ressemble à un e-mail », pas « sera délivré ». Associez-la à une recherche MX et à une vérification de la boîte aux lettres avant de faire confiance à l'adresse.

Pourquoi ne pas utiliser net/smtp pour vérifier si une boîte aux lettres existe ?#

net/smtp peut ouvrir la conversation SMTP, mais l'exécuter depuis votre serveur applicatif est peu fiable et risqué : la plupart des hébergeurs bloquent le port 25 sortant, la réponse dépend de la réputation de votre IP d'envoi, et sonder au moindre volume vous fait limiter le débit ou mettre sur liste de blocage. Une API de vérification exécute la sonde depuis une infrastructure à réputation gérée, conçue précisément pour cela.

Dois-je valider les e-mails à l'inscription ou lors du nettoyage d'une liste ?#

Les deux, à des profondeurs différentes. Exécutez la syntaxe et la recherche MX de façon synchrone à l'inscription — elles sont assez rapides pour bloquer la requête et donner un retour instantané — et agissez aussi sur le verdict immédiat de l'API à cet endroit. Réservez le travail plus lent et plus poussé au nettoyage de liste et aux tâches d'arrière-guichet ; le guide d'inscription serverless montre le schéma en direct de bout en bout.


Prêt à ajouter la couche boîte aux lettres ? La référence de l'API propose le point de terminaison /verify, les clés restreintes et des extraits à copier-coller, ou collez une adresse dans le vérificateur d'e-mails gratuit et regardez une chaîne syntaxiquement parfaite revenir comme non délivrable.

Your reputation, protected.

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

Get started