Eine E-Mail-Adresse in Ruby zu validieren sind drei Aufgaben, die sich hinter einem einzigen Methodennamen verstecken. Die meisten Tutorials reichen Ihnen eine Regex, sehen zu, wie sie durchläuft, und erklären die Adresse für gültig — aber ein Musterabgleich sagt Ihnen nur, dass die Zeichenkette die richtige Form hat, nicht dass jemals Post bei ihr ankommt. Echte Validierung ist geschichtet: eine günstige Syntaxprüfung, ein DNS-Lookup nach den Mailservern der Domain und ein SMTP-Test des tatsächlichen Postfachs. Rubys Standardbibliothek liefert Ihnen die ersten beiden ganz ohne Gems; die dritte ist ein Netzwerkproblem, das man besser abgibt. Dieser Leitfaden baut jede Schicht mit funktionierendem Code auf und zeigt genau, wo die Standardbibliothek aufhört.
Die kurze Antwort#
Verwenden Sie URI::MailTo::EMAIL_REGEXP aus der uri-Bibliothek für die Syntax,
Resolv::DNS aus der resolv-Bibliothek für den MX-Lookup und eine
Verifizierungs-API für die SMTP-Postfachprüfung — das Günstigste zuerst, mit
Kurzschluss, sobald eine Schicht entscheidend ist. Greifen Sie nicht zu
net/smtp, um Postfächer von Ihrer eigenen App aus zu testen: Der ausgehende
Port 25 ist auf den meisten Hostern blockiert, und die Antwort hängt von der
Reputation Ihrer sendenden IP und einem Greylisting-Verhalten ab, das Sie nicht
selbst nachbauen wollen. Jede Schicht schließt Adressen günstiger aus als die
vorige; nur die letzte kann eine Adresse einschließen. Genau diese letzte Lücke
existiert die E-Mail-Verifizierung, um sie zu
schließen.
Schicht 1: Syntax#
Regex gehört hierher und nirgendwo sonst — und in Ruby müssen Sie nicht einmal
selbst eine schreiben. Die uri-Bibliothek, Teil der Standardbibliothek, liefert
ein gut getestetes Muster in URI::MailTo::EMAIL_REGEXP mit, greifen Sie also
dazu, statt etwas von Stack Overflow zu kopieren:
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
Zwei Dinge sollten Sie über diese Konstante wissen. Sie ist bereits mit \A und
\z verankert, sodass match? die ganze Zeichenkette prüft statt einer
Teilzeichenkette — Sie müssen sie nicht selbst umschließen oder verankern. Und sie
ist bewusst großzügig: Sie folgt der WHATWG/HTML5-Definition einer gültigen
E-Mail, die lockerer ist als RFC 5322, und akzeptiert zum Beispiel bereitwillig
jane@localhost, weil sie keinen Punkt in der Domain verlangt. Das ist ein
Feature, kein Bug. Die Aufgabe von Schicht eins ist es, Tippfehler bei der Eingabe
abzufangen, nicht die RFCs auszufechten — was
ohnehin nicht helfen würde. Die
320-Zeichen-Grenze ist doppelte Absicherung: Nichts Längeres kann eine echte
Adresse sein, und es ist günstiger, früh abzulehnen, als eine riesige
Zeichenkette weiterzureichen. Ein Durchlauf hier bedeutet „prüfenswert“, niemals
„gültig“.
Schicht 2: Kann die Domain überhaupt Post empfangen?#
Hier verdient sich Rubys Standardbibliothek ihr Geld. Eine Domain ohne Mailroute
kann für niemanden Post annehmen, sodass ein einziger MX-Lookup tote Domains,
falsch geschriebene Firmennamen und erfundene TLDs aussortiert. Die
resolv-Bibliothek löst Records ohne Gems auf:
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 gibt Ihnen einen Resolver und schließt ihn, wenn der Block
endet, und getresources liefert ein Array von MX-Records zurück — ein leeres
Array, wenn die Domain keine veröffentlicht oder nicht existiert, was genau der
„keine Route“-Fall ist, den Sie abfangen wollen. Das rescue behandelt den anderen
Fehlerfall: einen Resolver, der einen Fehler wirft oder in ein Timeout läuft.
Beachten Sie, dass Resolv::ResolvTimeout keine Unterklasse von
Resolv::ResolvError ist, benennen Sie also beide, wenn ein langsamer Nameserver
sauber durchfallen soll, statt den Aufruf zu sprengen.
Wenn Sie die tatsächlichen Mailhosts statt eines Ja/Nein wollen — um sie zu
protokollieren oder Prioritäten zu prüfen — trägt jeder Record exchange und
preference, sortieren Sie also nach Letzterem:
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
Manche Domains nehmen Post über einen A-Record mit implizitem MX an; wenn Sie
diesen Sonderfall berücksichtigen müssen, weichen Sie auf getresources(domain, Resolv::DNS::Resource::IN::A) aus, wenn die MX-Menge leer zurückkommt. Für die
überwältigende Mehrheit echter Adressen ist eine MX-Prüfung der richtige Filter.
Schicht 3: Existiert das Postfach tatsächlich?#
Die Schichten eins und zwei können eine Adresse nur ausschließen. Eine Domain
kann perfekte MX-Records veröffentlichen und trotzdem kein Postfach unter der
Adresse haben, die Sie in Händen halten — noreply-9f2x@gmail.com ist gültige
Syntax auf einer aktiven Mailroute, und es ist trotzdem ein Postfach, das nie
angelegt wurde. Zu bestätigen, dass ein bestimmtes Postfach existiert, bedeutet
die SMTP-Zustellkonversation: sich mit dem Mailhost verbinden, RCPT TO
ausgeben, die Antwort lesen und trennen, bevor irgendetwas gesendet wird. Da
steckt mehr dahinter — wie E-Mail-Verifizierung
funktioniert geht die vollständige Pipeline
durch, einschließlich Catch-all-Domains, die jede Adresse akzeptieren und einen
naiven Test aushebeln.
Rubys Standardbibliothek öffnet diese Konversation mit net/smtp bereitwillig für
Sie. Das Problem ist nicht der Code; es ist das Netzwerk. Führen Sie einen
SMTP-Test von Ihrem Anwendungsserver aus, und drei Dinge gehen schief: Die meisten
Cloud-Anbieter blockieren den ausgehenden Port 25 vollständig, die Antwort hängt
von der Reputation der IP ab, von der aus Sie sich verbinden, und empfangende
Server greylisten und drosseln unbekannte Absender — sodass ein Test, der auf
Ihrem Laptop funktioniert, in der Produktion klammheimlich fehlschlägt oder Sie
auf eine Blocklist bringt. Das ist die Schicht, die sich zu delegieren lohnt.
Die vollständige Prüfung mit einer API#
Der Verifizierungs-Endpunkt von Qualisend führt die ganze Pipeline aus — Syntax,
DNS und den SMTP-Postfachtest — von einer reputationsverwalteten Infrastruktur,
die dafür gebaut ist. Er antwortet sofort mit einem Urteil aus den schnellen
Prüfungen, plus einer Job-ID für das endgültige Postfach-Urteil, solange der Test
noch läuft. Da es ein schlichter
JSON-POST ist, deckt das net/http der Standardbibliothek ihn ganz ohne Gems ab.
Lesen Sie Ihren Schlüssel aus der Umgebung, statt ihn fest zu verdrahten:
require "net/http"
require "json"
require "uri"
VERIFY_URL = URI("https://app.qualisend.com/api/v1/verify")
# A timeout, 402 (out of credits), 429 (rate limited) or 5xx: no verdict right now.
class VerifyUnavailable < StandardError; end
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,
open_timeout: 5, read_timeout: 10) do |http|
http.request(request)
end
if %w[402 429].include?(response.code) || response.is_a?(Net::HTTPServerError)
raise VerifyUnavailable, "Qualisend responded #{response.code}"
end
raise "Qualisend responded #{response.code}" unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body, symbolize_names: true)
rescue Net::OpenTimeout, Net::ReadTimeout => e
raise VerifyUnavailable, e.message
end
Die API-Referenz beschreibt die Form der Anfrage und wie Sie
einen mit Scopes versehenen Schlüssel erzeugen. Die Antwort enthält eine job_id
und umschließt ein result-Objekt:
{
"job_id": "8d1f0c2e-5b7a-4c3e-9f61-2a4b6c8d0e13",
"result": {
"status": "unknown",
"score": 61,
"smtp": "pending",
"reason": null,
"sub_flags": { "role": false, "disposable": false, "catch_all": false }
}
}
Greifen Sie in den Umschlag und handeln Sie nach 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:, ... }
result[:smtp] # "pending" while the mailbox probe runs, else "cached" | "skipped"
body[:job_id] # where the final verdict lands when smtp is "pending"
Ist result[:smtp] "cached" oder "skipped", ist result bereits endgültig.
Bei "pending" läuft der Postfachtest noch: Das sofortige Urteil stammt aus den
schnellen Prüfungen, das endgültige kommt über
GET https://app.qualisend.com/api/v1/jobs/{job_id}. Rufen Sie /verify nicht
erneut auf, um es zu aktualisieren. Speichern Sie die job_id und lassen Sie
einen Hintergrund-Job diesen Endpunkt alle paar Sekunden abfragen, bis der Job
abgeschlossen ist.
net/http hält das abhängigkeitsfrei, was in einem Hintergrund-Job oder einem
kleinen Service praktisch ist. Wenn Ihre App bereits auf Faraday oder HTTParty
setzt, ist dieselbe Anfrage ein paar Zeilen kürzer — der Umschlag, den Sie
zurücklesen, ist identisch.
Ein vollständiger Weg, eine E-Mail-Adresse in Ruby zu validieren#
Verketten Sie die drei Schichten günstigstes-zuerst und stoppen Sie in dem Moment, in dem eine entscheidend ist:
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
rescue VerifyUnavailable
{ status: "unknown", reason: "unknown" } # fail open: let it through, re-verify later
end
rpartition("@") teilt am letzten @, sodass Sie immer die echte Domain
nehmen, selbst bei seltsam gequoteten lokalen Teilen. Die beiden lokalen Schichten
kosten nichts und fangen den meisten Schrott sofort ab; die API läuft nur auf
Adressen, die den Netzwerk-Roundtrip wert sind. Genau diese Reihenfolge — nicht
die Sprache — macht Validierung zuverlässig, und es ist dieselbe Form, die Sie in
den Node.js- und
Python-Versionen dieses Leitfadens finden.
Rails-Nutzer können das Ganze als benutzerdefinierte Validierung in ein Model fallen lassen, sodass eine schlechte Adresse nie die Datenbank erreicht:
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
Eine Prüfung bei der Anmeldung sollte im Zweifel durchlassen (fail open):
validate_email macht aus einem Timeout, einem 402, 429 oder 5xx ein
unknown, sodass ein API-Ausfall, ein Rate-Limit oder ein leeres Guthaben nie
einen echten Kunden blockiert. Prüfen Sie diese Adressen später erneut.
Halten Sie den synchronen Pfad auf den beiden schnellen lokalen Schichten plus dem unmittelbaren API-Urteil; alles Langsamere gehört in einen Hintergrund-Job. Aber der Kern ist schlichtes Ruby, und er verhält sich in einer Sinatra-Route, einer Rake-Task oder einem einmaligen Skript gleich — die Schichtung ist es, die die Arbeit erledigt.
Häufig gestellte Fragen
01Kann ich in Ruby eine E-Mail-Adresse ohne Gems validieren?
Für zwei der drei Schichten ja. URI::MailTo::EMAIL_REGEXP aus der uri-Bibliothek übernimmt die Syntax, und Resolv::DNS aus der resolv-Bibliothek löst MX-Records auf - beide sind bei Ruby dabei, sodass eine Syntax-plus-Domain-Prüfung ohne jede Installation auskommt. Die dritte Schicht, das Postfach selbst zu bestätigen, bedeutet einen SMTP-Test, den Sie nicht von Ihrem eigenen Server ausführen sollten. Genau diesen Teil übernimmt ein Verifizierungsdienst für Sie.
02Reicht URI::MailTo::EMAIL_REGEXP für sich allein aus?
Es ist die richtige Prüfung für Schicht eins und eine weit bessere Wahl als eine selbstgebastelte Regex - aber es validiert die Form, nicht die Zustellbarkeit. Es löst nie DNS auf und kontaktiert keinen Mailserver, und es ist bewusst großzügig (es akzeptiert jane@localhost), sodass ein Durchlauf „sieht aus wie eine E-Mail“ bedeutet, nicht „wird zugestellt“. Kombinieren Sie es mit einem MX-Lookup und einer Postfachprüfung, bevor Sie der Adresse vertrauen.
03Warum nicht net/smtp verwenden, um zu prüfen, ob ein Postfach existiert?
net/smtp kann die SMTP-Konversation öffnen, aber sie von Ihrem Anwendungsserver aus auszuführen ist unzuverlässig und riskant: Die meisten Hoster blockieren den ausgehenden Port 25, die Antwort hängt von der Reputation Ihrer sendenden IP ab, und Tests in jeglichem Umfang führen zu Rate-Limits oder Blocklistung. Eine Verifizierungs-API führt den Test von einer reputationsverwalteten Infrastruktur aus, die genau dafür gebaut ist.
04Sollte ich E-Mails bei der Anmeldung oder beim Bereinigen einer Liste validieren?
Beides, in unterschiedlicher Tiefe. Führen Sie Syntax und den MX-Lookup synchron bei der Anmeldung aus - sie sind schnell genug, um die Anfrage zu blockieren und sofortiges Feedback zu geben - und handeln Sie dort auch nach dem unmittelbaren API-Urteil. Reservieren Sie tiefere, langsamere Arbeit für die Listenbereinigung und Back-Office-Aufgaben; der Serverless-Anmeldeleitfaden zeigt das Live-Muster von Anfang bis Ende.
Bereit, die Postfach-Schicht hinzuzufügen? Die API-Referenz hat den
/verify-Endpunkt, mit Scopes versehene Schlüssel und Copy-and-paste-Snippets,
oder fügen Sie eine Adresse in den kostenlosen
E-Mail-Checker ein und sehen Sie zu, wie eine syntaktisch
perfekte Zeichenkette als unzustellbar zurückkommt.



