In dem Moment, in dem Sie in Rails eine E-Mail-Adresse validieren müssen, lässt
Active Record es trivial erscheinen — schreiben Sie validates :email, sehen Sie
den Test bestehen, machen Sie weiter. Doch diese eine Zeile beantwortet nur die
erste der drei Fragen, die echte Validierung stellt. Ist die Adresse korrekt
geformt? Kann ihre Domain überhaupt Mail empfangen? Existiert das Postfach
tatsächlich? Rails liefert eine saubere Antwort auf die erste Frage und gar nichts
für die anderen beiden. Dieser Leitfaden baut jede Ebene als idiomatische
Rails-Validierung auf — eine format-Regel, dann zwei eigene
ActiveModel::EachValidator-Klassen — und zeigt genau, wo das Framework aufhört.
Er erweitert den
Leitfaden zur E-Mail-Validierung in Ruby:
Rails verpackt dieselben Bausteine der Standardbibliothek in die deklarative
Validierungs-API.
Die Kurzfassung#
Verwenden Sie validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } für
die Syntax, einen eigenen Validator auf Basis der Standardbibliothek resolv für
die MX-Abfrage und eine Verifizierungs-API für die SMTP-Postfachprüfung — drei
Validierungen auf einem Attribut, das günstigste zuerst, wobei jede übersprungen
wird, wenn eine günstigere Ebene bereits fehlgeschlagen ist. Öffnen Sie keine
SMTP-Sitzung aus Ihrer App heraus, um Postfächer selbst zu sondieren: Der
ausgehende Port 25 ist auf den meisten Hosts blockiert, und die Antwort hängt von
der Reputation der sendenden IP sowie von Greylisting ab, das Sie nicht neu
implementieren möchten. Eine Besonderheit unterscheidet Rails von Frameworks mit
einem bail-Schlüsselwort — Active Model führt jeden deklarierten Validator aus
und sammelt alle Fehler, sodass ein Kurzschluss eine Schutzabfrage ist, die Sie
hinzufügen, kein Flag, das Sie übergeben. Jede Ebene schließt Adressen
günstiger aus als die vorherige; nur die letzte kann eine Adresse einschließen.
Ebene 1: Formatvalidierung mit einer validates-Regel#
Rails gehört Ebene eins, und Sie müssen keine Regex schreiben, um sie
einzufordern. Rubys uri-Bibliothek — die Rails ohnehin lädt — liefert ein gut
getestetes Muster in URI::MailTo::EMAIL_REGEXP, übergeben Sie es also einer
format-Validierung, statt etwas von Stack Overflow einzufügen:
app/models/user.rb
class User < ApplicationRecord
validates :email,
presence: true,
format: { with: URI::MailTo::EMAIL_REGEXP }
end
Diese Konstante ist dieselbe, auf die sich der
Ruby-Leitfaden stützt. Sie ist bereits mit
\A und \z verankert, prüft also den gesamten String, und sie ist bewusst
permissiv: Sie folgt der WHATWG/HTML5-Definition, die lockerer ist als RFC 5322
und bereitwillig jane@localhost akzeptiert, weil sie keinen Punkt in der Domain
verlangt. Das ist ein Feature. Die Aufgabe von Ebene eins ist es, Tippfehler bei
der Eingabe abzufangen, nicht die RFCs auszufechten — was
ohnehin nicht helfen würde.
Was zählt, ist zu wissen, wo Rails aufhört. Jede integrierte E-Mail-Funktion — die
format-Regel, die Scaffolds, der email_field-Helper — validiert die Form.
Keine davon löst DNS auf oder kontaktiert einen Mailserver, sodass
noreply-9f2x@gmail.com, typo@gmial.com und sales@company-that-folded.com alle
bestehen. Alle drei sind unzustellbar. Rails liefert überhaupt keine
Zustellbarkeitsprüfung mit.
Ebene 2: Kann die Domain Mail empfangen?#
Dies ist die erste Ebene, die Rails Ihnen nicht abnimmt, und sie lässt sich günstig
nachrüsten. Eine Domain ohne Mail-Route kann für niemanden Mail annehmen, sodass
eine einzige MX-Abfrage tote Domains, falsch geschriebene Firmennamen und erfundene
TLDs ausschließt. Ein Rails-Validator ist einfach eine Klasse, die von
ActiveModel::EachValidator ableitet und validate_each implementiert — verpacken
Sie also die MX-Abfrage der resolv-Bibliothek in einen solchen:
app/validators/deliverable_domain_validator.rb
require "resolv"
class DeliverableDomainValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank? || record.errors[attribute].present?
domain = value.rpartition("@").last
return if domain.present? && mail_route?(domain)
record.errors.add(attribute, options[:message] || "can't receive email")
end
private
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
end
Resolv::DNS.open gibt Ihnen einen Resolver und schließt ihn, wenn der Block
endet, und getresources liefert ein Array von MX-Einträgen zurück — leer, wenn
die Domain keine veröffentlicht oder nicht existiert, was genau der Fall "keine
Route" ist. Das rescue deckt den anderen Fehlermodus ab; beachten Sie, dass
Resolv::ResolvTimeout keine Unterklasse von Resolv::ResolvError ist, benennen
Sie also beide, wenn ein langsamer Nameserver sauber durchfallen soll. Binden Sie
ihn über seinen abgeleiteten Schlüssel in das Modell ein — Rails camelisiert
deliverable_domain zu DeliverableDomainValidator und findet die Klasse, die
Zeitwerk aus app/validators automatisch geladen hat:
validates :email,
presence: true,
format: { with: URI::MailTo::EMAIL_REGEXP },
deliverable_domain: true
Die Schutzabfrage return if ... record.errors[attribute].present? ist der ganze
Trick. Active Model führt jeden deklarierten Validator aus und aggregiert die
Fehler — es gibt kein bail. Doch Validierungen laufen in der Reihenfolge, in der
Sie sie deklarieren, sodass zu dem Zeitpunkt, an dem diese auslöst, ein
format-Fehler bereits auf record.errors[:email] sitzt und die Schutzabfrage die
DNS-Abfrage vollständig überspringt. Eine fehlerhaft geformte Adresse verbraucht
niemals einen Nameserver-Roundtrip.
Ebene 3: Existiert das Postfach tatsächlich?#
Ebene eins und zwei können eine Adresse nur ausschließen. Eine Domain kann
perfekte MX-Einträge veröffentlichen und trotzdem kein Postfach an der Adresse
haben, die Sie in der Hand halten — noreply-9f2x@gmail.com ist syntaktisch gültig
auf einer aktiven Mail-Route und dennoch ein Postfach, das nie angelegt wurde. Ein
bestimmtes Postfach zu bestätigen bedeutet die SMTP-Zustellungskonversation: sich
mit dem Mail-Host verbinden, RCPT TO absetzen, die Antwort lesen und trennen,
bevor irgendetwas gesendet wird. Es steckt mehr dahinter als das —
wie E-Mail-Verifizierung funktioniert
durchläuft die gesamte Pipeline, einschließlich Catch-all-Domains, die jede Adresse
annehmen und eine naive Sondierung aushebeln.
Rubys net/smtp öffnet diese Konversation bereitwillig. Das Problem ist nicht der
Code; es ist das Netzwerk. Führen Sie eine SMTP-Sondierung 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 eine Sondierung, die auf Ihrem
Laptop funktioniert, in der Produktion still fehlschlägt oder Sie auf eine
Blockliste bringt. Dies ist die Ebene, die es sich zu delegieren lohnt.
Qualisend aus einem eigenen Validator aufrufen#
Die Delegation bleibt sauber, weil eine Verifizierungs-API nur ein weiterer
Validator ist. Der verify-Endpunkt von Qualisend durchläuft die gesamte Pipeline —
Syntax, DNS und die SMTP-Postfachsondierung — von einer reputationsverwalteten
Infrastruktur aus und liefert ein einziges Urteil zurück. Es ist ein einfacher
JSON-POST, sodass die Standardbibliothek net/http ihn ohne Gems abdeckt. Lesen Sie
den Schlüssel aus der Umgebung, statt ihn fest zu verdrahten:
app/validators/deliverable_email_validator.rb
require "net/http"
require "json"
require "uri"
class DeliverableEmailValidator < ActiveModel::EachValidator
VERIFY_URL = URI("https://api.qualisend.com/v1/verify")
def validate_each(record, attribute, value)
return if value.blank? || record.errors[attribute].present?
result = verify(value)
return if result.nil? # our outage shouldn't block a real signup
if result["status"] == "undeliverable"
record.errors.add(attribute, options[:message] || "appears to be undeliverable")
end
end
private
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
) { |http| http.request(request) }
return nil unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)["result"]
rescue StandardError
nil # unreachable or slow — treat as unknown and re-verify later
end
end
Dieser Endpunkt ist ein Platzhalter — prüfen Sie die
API-Referenz für die genaue Basis-URL, die Request-Struktur und wie
Sie einen scoped Key erstellen. Die Antwort umschließt ein result-Objekt:
{
"result": {
"status": "deliverable",
"score": 95,
"reason": null,
"sub_flags": { "role": false, "disposable": false, "catch_all": false }
}
}
status ist einer von deliverable, risky, undeliverable oder unknown. Der
obige Validator lässt nur undeliverable hart durchfallen und lässt risky und
unknown passieren, sodass Sie nachgelagert entscheiden können, was damit
geschehen soll — auf score filtern oder auf einen sub_flags-Eintrag wie
disposable verzweigen — statt eine Grenzfall-Adresse in einen Formularfehler zu
verwandeln. Und wenn der Request selbst fehlschlägt, gibt verify nil zurück und
der Validator fügt keinen Fehler hinzu: Die Syntax- und MX-Ebenen liefen bereits
lokal, sodass ein Abmildern nur der Netzwerkebene verhindert, dass ein
vorübergehender Ausfall eine echte Registrierung blockiert. Behandeln Sie das obige
JSON als die Struktur, nicht als den Vertrag; die vollständige Feldliste finden Sie
in den Entwicklerdokumenten.
Die Ebenen zusammenführen, um eine E-Mail-Adresse in Rails zu validieren#
Deklarieren Sie alle drei Validierungen auf dem Attribut, das günstigste zuerst. Die Deklarationsreihenfolge ist die Ausführungsreihenfolge, und jede Schutzabfrage überspringt, wenn eine günstigere Ebene bereits fehlgeschlagen ist, sodass sich die zusammengesetzte Regel von oben nach unten genau so liest, wie sie ausgeführt wird:
app/models/user.rb
class User < ApplicationRecord
validates :email,
presence: true,
format: { with: URI::MailTo::EMAIL_REGEXP },
deliverable_domain: true,
deliverable_email: true
end
Nun erreicht user.save die API nur bei Adressen, die Syntax und MX-Prüfung
bereits bestanden haben — ein Tippfehler scheitert bei format und verbraucht
niemals einen Nameserver-Roundtrip oder ein API-Guthaben, und eine tote Domain
scheitert bei deliverable_domain, bevor der Netzwerkaufruf erfolgt. user.valid?
führt die gesamte Kette aus, und user.errors[:email] trägt, welche Ebene auch
immer Einspruch erhoben hat. Diese Reihenfolge, nicht das Framework, macht die
Validierung zuverlässig — dieselbe Struktur wie die
Ruby-Version, ausgedrückt als deklarative
Validierungen statt als handverkettete Methode.
Weil dies gewöhnliche Validierungen sind, funktioniert jede Standardoption.
Beschränken Sie die beiden Netzwerkebenen mit if: :email_changed?, damit ein
unabhängiges user.update(name: ...) keine DNS-Abfrage erneut ausführt und kein
Guthaben für eine bereits verifizierte Adresse erneut verbraucht. Halten Sie den
synchronen Pfad auf die schnellen lokalen Ebenen plus das unmittelbare API-Urteil
bei der Registrierung; alles Aufwändigere gehört in einen Background-Job. Der
Leitfaden zur serverlosen Registrierung
zeigt das Echtzeit-Muster, und
wie man eine E-Mail-Liste bereinigt deckt die
Batch-Seite ab.
Häufig gestellte Fragen#
Bietet Rails eine integrierte Möglichkeit zu prüfen, ob eine E-Mail zustellbar ist?#
Nein. Jede integrierte E-Mail-Funktion — die format-Validierung mit
URI::MailTo::EMAIL_REGEXP, die Scaffolds, der email_field-Helper — validiert nur
die Form. Keine davon löst DNS auf oder kontaktiert einen Mailserver, sodass eine
erfundene Domain oder ein nicht existierendes Postfach die Prüfung besteht. Ergänzen
Sie eine MX-Abfrage und eine SMTP-Postfachprüfung als eigene Validatoren, um die
Zustellbarkeit abzudecken, denn Rails liefert beides nicht mit.
Wie schreibe ich einen eigenen E-Mail-Validator in Rails?#
Leiten Sie von ActiveModel::EachValidator ab und implementieren Sie
validate_each(record, attribute, value), wobei Sie record.errors.add(attribute, message) aufrufen, wenn der Wert die Prüfung nicht besteht. Speichern Sie die
Klasse unter app/validators, damit Zeitwerk sie automatisch lädt, und binden Sie
sie dann über ihren abgeleiteten Schlüssel ein — ein DeliverableDomainValidator
wird als deliverable_domain: true eingebunden. Er akzeptiert dieselben Optionen
wie jede Validierung, sodass if:, on: und ein eigenes message: unverändert
funktionieren.
Kann ich in Rails ohne API prüfen, ob ein Postfach existiert?#
Teilweise. Die Standardbibliothek resolv bestätigt, dass die Domain Mail annimmt,
was tote Domains kostenlos und direkt aus einem eigenen Validator heraus
ausschließt. Das Postfach zu bestätigen bedeutet eine SMTP-Konversation, die Sie
mit net/smtp öffnen können, aber nicht von Ihrem App-Server aus ausführen sollten
— der ausgehende Port 25 ist vielerorts blockiert und das Ergebnis hängt von der
Reputation Ihrer sendenden IP ab. Genau diese letzte Ebene übernimmt ein
Verifizierungsdienst von einer eigens dafür reputationsverwalteten Infrastruktur
aus.
Sollte ich E-Mails bei der Registrierung oder beim Bereinigen einer Liste validieren?#
Beides, in unterschiedlicher Tiefe. Führen Sie die Syntax- und MX-Ebenen bei der
Registrierung synchron aus — sie sind schnell genug, um den Request zu blockieren
und sofortiges Feedback zu geben — und handeln Sie dort auch nach dem unmittelbaren
API-Urteil. Reservieren Sie tiefere, gebündelte Verifizierung für das
Bereinigen einer bestehenden Liste, wo Latenz
keine Rolle spielt, Gründlichkeit aber schon. Wenn Sie die Netzwerk-Validatoren auf
if: :email_changed? beschränken, verhindern Sie, dass gewöhnliche
Aktualisierungen eine bereits geprüfte Adresse erneut verifizieren.
Bereit, die Postfach-Ebene hinzuzufügen? Die API-Referenz enthält
den /verify-Endpunkt, scoped Keys und Copy-and-paste-Snippets, oder fügen Sie eine
Adresse in den kostenlosen E-Mail-Checker ein und sehen Sie
zu, wie ein syntaktisch perfekter String als unzustellbar zurückkommt.