Nette Mail
Planen Sie, E-Mails zu versenden, etwa Newsletter oder Bestellbestätigungen? Das Nette Framework stellt die nötigen Werkzeuge mit einer sehr benutzerfreundlichen API bereit. Wir zeigen Ihnen:
- wie man eine E-Mail samt Anhängen erstellt
- wie man sie versendet
- wie man E-Mails und Templates verbindet
Installation
Die Bibliothek laden und installieren Sie mit Composer:
composer require nette/mail
E-Mails erstellen
Eine E-Mail ist ein Objekt Nette\Mail\Message. Erstellen wir eines so:
$mail = new Nette\Mail\Message;
$mail->setFrom('John <john@example.com>')
->addTo('peter@example.com')
->addTo('jack@example.com')
->setSubject('Bestellbestätigung')
->setBody("Hallo,\nIhre Bestellung wurde angenommen.");
Alle angegebenen Parameter müssen in der Kodierung UTF-8 vorliegen.
Adressen mit internationalisierter Domain, etwa jan@příklad.cz, werden automatisch in die
ASCII-Form namens Punycode umgewandelt, die Mailserver verlangen; dafür wird die Extension intl benötigt.
Außer der Angabe von Empfängern mit addTo() können Sie mit addCc() Empfänger einer Kopie oder mit
addBcc() Empfänger einer Blindkopie angeben. Alle diese Methoden, einschließlich setFrom(), nehmen den
Adressaten auf drei Arten entgegen:
$mail->setFrom('john.doe@example.com');
$mail->setFrom('john.doe@example.com', 'John Doe');
$mail->setFrom('John Doe <john.doe@example.com>');
Den in HTML geschriebenen Body einer E-Mail übergeben Sie mit der Methode setHtmlBody():
$mail->setHtmlBody('<p>Hallo,</p><p>Ihre Bestellung wurde angenommen.</p>');
Eine Textalternative müssen Sie nicht erstellen; Nette erzeugt sie automatisch für Sie. Und wenn die E-Mail keinen Betreff
gesetzt hat, versucht Nette, ihn dem Element <title> zu entnehmen.
Auch Bilder lassen sich außerordentlich leicht in den HTML-Body einbetten. Übergeben Sie als zweiten Parameter einfach den Pfad, unter dem die Bilder physisch liegen, und Nette bindet sie automatisch in die E-Mail ein:
// fügt /path/to/images/background.gif automatisch der E-Mail hinzu
$mail->setHtmlBody(
'<b>Hallo</b> <img src="background.gif">',
'/path/to/images',
);
Der Algorithmus zum Einbetten von Bildern sucht nach diesen Mustern: <img src=...>,
<body background=...>, url(...) innerhalb des HTML-Attributs style sowie der
besonderen Schreibweise [[...]].
Könnte das Versenden von E-Mails noch einfacher sein?
E-Mails sind wie Postkarten. Senden Sie niemals Passwörter oder andere Zugangsdaten per E-Mail.
Weitere Möglichkeiten
Das Objekt Message erlaubt außerdem, eine Antwortadresse, einen Rückweg für unzustellbare Nachrichten und die
Priorität der Nachricht zu setzen:
$mail->addReplyTo('reply@example.com', 'Support')
->setReturnPath('bounces@example.com')
->setPriority(Nette\Mail\Message::High);
Die Priorität ist eine der Konstanten Message::High, Message::Normal oder
Message::Low.
Abmeldung mit einem Klick
Gmail und Yahoo verlangen, dass Massenmails wie Newsletter die Abmeldung mit einem einzigen Klick direkt im Mailclient
anbieten. Dafür sorgt ein Paar von Headern nach RFC 8058, die die Methode setUnsubscribe() korrekt für Sie
einrichtet:
$mail->setUnsubscribe('https://example.com/unsubscribe?token=xyz');
Die URL muss den Empfänger als Antwort auf einen bloßen HTTP-POST-Request abmelden, ohne weitere Bestätigung. Der zweite
Parameter kann eine E-Mail-Adresse als Fallback für Clients angeben, die kein POST senden können; er funktioniert auch für sich
allein: $mail->setUnsubscribe(email: 'unsubscribe@example.com').
Anhänge
Natürlich können Sie E-Mails Dateien anhängen. Verwenden Sie dafür die Methode
addAttachment(string $file, ?string $content = null, ?string $contentType = null).
// hängt die Datei /path/to/example.zip unter dem Namen example.zip an die E-Mail an
$mail->addAttachment('/path/to/example.zip');
// hängt die Datei /path/to/example.zip unter dem Namen info.zip an
$mail->addAttachment('info.zip', file_get_contents('/path/to/example.zip'));
// hängt die Datei example.txt mit dem Inhalt "Hallo John!" an
$mail->addAttachment('example.txt', 'Hallo John!');
Sie können eine Datei mit addEmbeddedFile() auch direkt in den HTML-Body einbetten. Die Methode gibt den
erzeugten MIME-Teil zurück, dessen Content-ID Sie im HTML referenzieren (genau diesen Mechanismus verwendet das
automatische Einbetten von Bildern intern):
$file = $mail->addEmbeddedFile('/path/to/logo.png');
$mail->setHtmlBody('<img src="cid:' . trim($file->getHeader('Content-ID'), '<>') . '">');
Templates
Wenn Sie HTML-E-Mails versenden, ist es eine hervorragende Möglichkeit, sie im Templating-System Latte zu schreiben. Wie geht das?
$latte = new Latte\Engine;
$params = [
'orderId' => 123,
];
$mail = new Nette\Mail\Message;
$mail->setFrom('John <john@example.com>')
->addTo('jack@example.com')
->setHtmlBody(
$latte->renderToString('/path/to/email.latte', $params),
'/path/to/images',
);
Datei email.latte:
<html>
<head>
<meta charset="utf-8">
<title>Bestellbestätigung</title>
<style>
body {
background: url("background.png")
}
</style>
</head>
<body>
<p>Hallo,</p>
<p>Ihre Bestellung mit der Nummer {$orderId} wurde angenommen.</p>
</body>
</html>
Nette bettet alle Bilder automatisch ein, setzt den Betreff anhand des Elements <title> und erzeugt eine
Textalternative zum HTML.
Verwendung in Nette Application
Wenn Sie E-Mails zusammen mit Nette Application verwenden, also mit Presentern, wollen Sie in Templates womöglich Links mit
dem Attribut n:href oder dem Tag {link} erzeugen. Latte kennt sie standardmäßig nicht, aber es ist
sehr leicht, sie zu ergänzen. Links kann das Objekt Nette\Application\LinkGenerator erzeugen, das Sie sich per Dependency Injection übergeben lassen:
use Nette;
class MailSender
{
public function __construct(
private Nette\Application\LinkGenerator $linkGenerator,
private Nette\Bridges\ApplicationLatte\TemplateFactory $templateFactory,
) {
}
private function createTemplate(): Nette\Application\UI\Template
{
$template = $this->templateFactory->createTemplate();
$template->getLatte()->addProvider('uiControl', $this->linkGenerator);
return $template;
}
public function createEmail(): Nette\Mail\Message
{
$template = $this->createTemplate();
$html = $template->renderToString('/path/to/email.latte', $params);
$mail = new Nette\Mail\Message;
$mail->setHtmlBody($html);
// ...
return $mail;
}
}
Im Template erzeugen Sie die Links dann wie gewohnt. Alle über den LinkGenerator erzeugten Links sind absolut.
<a n:href="Presenter:action">Link</a>
CSS-Inlining
Nette\Mail\CssInliner wandelt CSS-Regeln in inline
style-Attribute um, damit E-Mails in allen Clients einheitlich dargestellt werden. Außerdem erzeugt er
HTML-Attribute für die Kompatibilität mit Outlook.
Erfordert PHP 8.4 oder neuer und die Extension dom.
Die meisten E-Mail-Clients unterstützen <style>-Tags nur eingeschränkt oder ignorieren sie ganz. Damit die
Darstellung korrekt ist, müssen die CSS-Regeln in inline style-Attribute der einzelnen Elemente umgewandelt werden.
Schicken Sie Ihr HTML einfach durch inline():
$inliner = new Nette\Mail\CssInliner;
$html = $inliner->inline($html);
Enthält das HTML zum Beispiel:
<style>
p { margin: 0; color: #333; }
a { color: #a0704e; }
</style>
<p>Hallo <a href="#">Welt</a></p>
sieht das Ergebnis nach dem Inlining so aus (der <style>-Tag bleibt erhalten, ist hier aber der Kürze
halber weggelassen):
<p style="margin: 0; color: #333">Hallo <a href="#" style="color: #a0704e">Welt</a></p>
Der <style>-Tag bleibt in der Ausgabe immer erhalten, sodass @media-Abfragen und andere Regeln,
die sich nicht inlinen lassen, weiter funktionieren.
Außer dem Auslesen der Styles aus <style>-Tags können Sie CSS auch über die Methode addCss()
bereitstellen. Sie müssen das CSS inlinen, bevor Sie das HTML an setHtmlBody() übergeben:
$latte = new Latte\Engine;
$params = [
'orderId' => 123,
];
$html = $latte->renderToString('/path/to/email.latte', $params);
$html = (new Nette\Mail\CssInliner)
->addCss(file_get_contents('/path/to/email.css'))
->inline($html);
$mail = new Nette\Mail\Message;
$mail->setHtmlBody($html);
Zielen mehrere Regeln auf dieselbe Eigenschaft eines Elements, entscheidet die CSS-Kaskade über den
Gewinner, genau wie im Browser: !important-Deklarationen schlagen normale, ein vorhandenes inline
style-Attribut schlägt jeden Selektor, ein spezifischerer Selektor schlägt einen weniger spezifischen, und bei
Gleichstand gewinnt die spätere Regel. Regeln aus <style>-Tags werden vor denen verarbeitet, die über
addCss() ergänzt wurden, und nur der gewinnende Wert wird ausgegeben.
At-Regeln wie @media oder @font-face werden beim Inlining übersprungen. Beachten Sie, dass sich
Pseudoklassen wie :hover nicht sinnvoll inlinen lassen, denn inline Styles unterstützen keine dynamischen
Zustände.
HTML-Attribute für Outlook
Die Desktop-Versionen von Microsoft Outlook verwenden die Rendering-Engine von Word, die viele CSS-Eigenschaften nicht
versteht. Um die Kompatibilität sicherzustellen, erzeugt CssInliner neben den inline Styles automatisch die
entsprechenden HTML-Attribute aus den CSS-Regeln:
| CSS-Eigenschaft | HTML-Attribut | Wird angewendet auf |
|---|---|---|
background-color |
bgcolor |
<table>, <td>, <th>,
<body>, <tr> |
width |
width |
<table>, <td>, <th>, <img> |
height |
height |
<table>, <td>, <th>, <img> |
border-spacing |
cellspacing |
<table> |
Bei width, height und cellspacing wird die Einheit px automatisch entfernt
(aus width: 600px wird also width="600"), eine Prozentangabe behält ihr %, und Werte, die
ein Attribut nicht ausdrücken kann, etwa auto oder calc(), erzeugen gar kein Attribut. Sowohl der
inline Style als auch das HTML-Attribut werden gemeinsam gesetzt, sodass die E-Mail in modernen Clients und in Outlook
gleichermaßen korrekt dargestellt wird.
HTML-Attribute werden nur aus CSS-Regeln erzeugt, die CssInliner verarbeitet, nicht aus
style-Attributen, die im ursprünglichen HTML bereits vorhanden sind.
E-Mails versenden
Der Mailer ist eine Klasse, die für das Versenden von E-Mails zuständig ist. Sie implementiert das Interface Nette\Mail\Mailer, und es stehen mehrere fertige Mailer zur Verfügung, die wir vorstellen.
Das Framework fügt dem DI-Container anhand der Konfiguration automatisch einen Service
Nette\Mail\Mailer hinzu, den Sie sich per Dependency Injection übergeben lassen.
SendmailMailer
Der Standard-Mailer ist der SendmailMailer, der die PHP-Funktion mail verwendet. Anwendungsbeispiel:
$mailer = new Nette\Mail\SendmailMailer;
$mailer->send($mail);
Wenn Sie den returnPath setzen wollen und Ihr Server ihn trotzdem überschreibt, verwenden Sie
$mailer->commandArgs = '-fmy@email.com'.
Standardmäßig übergibt SendmailMailer die Adresse des Absenders der Funktion mail() als
Envelope-Sender (das Argument -f). Das können Sie mit $mailer->setEnvelopeSender(false)
abschalten.
SmtpMailer
Um Mails über einen SMTP-Server zu versenden, verwenden Sie SmtpMailer.
$mailer = new Nette\Mail\SmtpMailer(
host: 'smtp.gmail.com',
username: 'john@gmail.com',
password: '*****', // Ihr Passwort
encryption: 'ssl', // oder 'tls'
);
$mailer->send($mail);
Dem Konstruktor lassen sich die folgenden weiteren Parameter übergeben:
port– wenn nicht gesetzt, wird der Standard verwendet: 465 beissl, 587 beitls, sonst 25timeout– Timeout für die SMTP-Verbindungpersistent– eine persistente Verbindung verwendenclientHost– gibt den Host-Header des Clients anstreamOptions– erlaubt es, SSL-Kontextoptionen für die Verbindung zu setzen
Authentifizierung mit OAuth 2.0
Gmail und Microsoft 365 schaffen die Authentifizierung per Passwort für SMTP ab und verlangen stattdessen einen Access Token
nach OAuth 2.0 (den Mechanismus XOAUTH2). Übergeben Sie den Token mit der Methode setAccessToken(); der Benutzername
bleibt, das Passwort bleibt leer:
$mailer = new Nette\Mail\SmtpMailer(
host: 'smtp.gmail.com',
username: 'john@gmail.com',
password: '',
encryption: 'tls',
);
$mailer->setAccessToken($accessToken);
Da Access Tokens ablaufen, können Sie stattdessen einen Callback übergeben; er wird bei jedem Verbindungsaufbau aufgerufen und kann so immer einen frischen Token liefern. Das Beschaffen und Erneuern des Tokens bleibt Ihnen oder Ihrer OAuth-Bibliothek überlassen:
$mailer->setAccessToken(fn() => $oauthProvider->getFreshToken());
FallbackMailer
Dieser Mailer versendet E-Mails nicht direkt, sondern vermittelt den Versand über eine Menge von Mailern. Schlägt ein Mailer fehl, versucht er es mit dem nächsten. Schlägt der letzte fehl, beginnt er wieder beim ersten.
$mailer = new Nette\Mail\FallbackMailer([
$smtpMailer,
$backupSmtpMailer,
$sendmailMailer,
]);
$mailer->send($mail);
Weitere Parameter des Konstruktors sind die Anzahl der Versuche (Standard 3) und die Wartezeit dazwischen in
Millisekunden (Standard 1000). Schlagen bei jedem Versuch alle Mailer fehl, wird eine
Nette\Mail\FallbackMailerException geworfen, deren Property $failures die gesammelten Exceptions
enthält.
Ein Mailer, dessen Fehlschlag dauerhaft ist, etwa weil der SMTP-Server die Zugangsdaten ablehnt, wird von weiteren Versuchen ausgeschlossen – eine Wiederholung kann das Ergebnis nicht ändern.
Einen weiteren Mailer können Sie später mit addMailer() ergänzen und das Event $onFailure
registrieren, das nach jedem fehlgeschlagenen Versuch aufgerufen wird:
$mailer->onFailure[] = function ($mailer, $exception, $failedMailer, $mail) {
// z. B. den fehlgeschlagenen Versuch protokollieren
};
FileMailer
Dieser Mailer versendet nichts: Er schreibt jede Nachricht als .eml-Datei in das angegebene Verzeichnis. Die
Dateien lassen sich in jedem E-Mail-Client öffnen, sodass Sie genau prüfen können, was versendet worden wäre – praktisch in
Tests und während der Entwicklung.
$mailer = new Nette\Mail\FileMailer('/path/to/mails');
$mailer->send($mail);
E-Mails debuggen
Bei der Entwicklung oder auf einem Staging-Server wollen Sie nicht, dass eine Test-E-Mail zu einem echten Kunden durchrutscht. Es gibt zwei Wege, das sicher zu verhindern.
Die empfohlene lokale Einrichtung ist, auf Ihrem Rechner einen leichtgewichtigen SMTP-Catcher wie Mailpit oder MailHog laufen zu lassen. Sie
nehmen jede Nachricht an, zeigen sie in einer Weboberfläche und leiten nie etwas weiter – Sie richten Nette Mail einfach auf
127.0.0.1:1025:
mail:
smtp: true
host: 127.0.0.1
port: 1025
Für Staging oder Umgebungen, in denen Sie keinen lokalen Catcher betreiben können, hat Nette Mail eine eingebaute Umleitung.
Setzen Sie das Ziel in der Konfiguration, und jeder Empfänger in To, Cc und Bcc wird
dadurch ersetzt. Nette Mail bewahrt die ursprünglichen in X-Original-*-Headern auf, sodass Sie sehen, für wen die
E-Mail gedacht war, und Sie können dem Betreff eine Markierung voranstellen:
mail:
redirect:
to: dev@example.com
subjectPrefix: '[debug]' # optional
Die Kurzform redirect: dev@example.com funktioniert, wenn Sie kein Präfix für den Betreff brauchen. Im
Debug-Modus hängt sich automatisch ein Panel der Tracy Bar an, das alle gesendeten
E-Mails auflistet.
Intern übernimmt das Nette\Mail\Interceptor, der
außerdem ein Event $onSent für eigene Listener bereitstellt (Audit-Logs, Metriken, …).
DKIM
DKIM (DomainKeys Identified Mail) ist eine Technologie zur Erhöhung der Vertrauenswürdigkeit von E-Mails, die außerdem hilft, gefälschte Nachrichten zu erkennen. Die gesendete Nachricht wird mit dem privaten Schlüssel der Domain des Absenders signiert, und diese Signatur wird im Header der E-Mail abgelegt. Der Server des Empfängers vergleicht diese Signatur mit dem öffentlichen Schlüssel, der in den DNS-Einträgen der Domain hinterlegt ist. Stimmt die Signatur, beweist das, dass die E-Mail tatsächlich von der Domain des Absenders stammt und dass die Nachricht während der Übertragung nicht verändert wurde.
Sie können den Mailer direkt in der Konfiguration zum Signieren von E-Mails einrichten. Wenn Sie Dependency Injection nicht verwenden, wird es so verwendet:
$signer = new Nette\Mail\DkimSigner(
domain: 'yourdomain.com',
selector: 'dkim', // Selektor aus dem DNS-Eintrag
privateKey: file_get_contents('/path/to/dkim.key'), // Pfad zu Ihrem privaten Schlüssel
passPhrase: 'your_passphrase', // Passphrase für den privaten Schlüssel, falls vorhanden
);
$mailer = new Nette\Mail\SendmailMailer; // oder SmtpMailer
$mailer->setSigner($signer);
$mailer->send($mail);
Der private Schlüssel kann entweder ein RSA-Schlüssel im PEM-Format sein oder ein Ed25519-Schlüssel (RFC 8463) als base64-kodierte Rohbytes; der Typ wird aus dem Schlüssel
selbst erkannt. Das Signieren mit Ed25519 erfordert die Extension sodium.
Im Parameter oversignHeaders können Sie Header aufzählen, die davor geschützt werden
sollen, dass der bereits signierten Nachricht eine zweite Kopie angehängt wird, was ein Trick gefälschter E-Mails ist; der
übliche Kandidat ist From.
Konfiguration
Übersicht der Konfigurationsoptionen für Nette Mail. Wenn Sie nicht das gesamte Framework, sondern nur diese Bibliothek verwenden, lesen Sie, wie man die Konfiguration lädt.
Zum Versenden von E-Mails wird standardmäßig der Nette\Mail\SendmailMailer verwendet, der keine weitere
Konfiguration braucht. Wir können ihn jedoch auf Nette\Mail\SmtpMailer umstellen:
mail:
# SmtpMailer verwenden
smtp: true # (bool) Standardwert ist false
host: ... # (string) Hostname des SMTP-Servers
port: ... # (int) Port des SMTP-Servers
username: ... # (string) Benutzername für die SMTP-Authentifizierung
password: ... # (string) Passwort für die SMTP-Authentifizierung
timeout: ... # (int) Timeout für die SMTP-Verbindung
encryption: ... # (ssl|tls|null) Standardwert ist null (Alias 'secure')
clientHost: ... # (string) Hostname des Clients, Standardwert ist $_SERVER['HTTP_HOST'] oder 'localhost'
persistent: ... # (bool) persistente Verbindung verwenden, Standardwert ist false
# Optionen des Stream-Kontexts für die SMTP-Verbindung, Standardwert ist stream_context_get_default()
context:
ssl: # alle Optionen unter https://www.php.net/manual/en/context.ssl.php
allow_self_signed: ...
...
http: # Liste der Optionen unter https://www.php.net/manual/en/context.http.php
header: ...
...
Die Prüfung des SSL-Zertifikats können Sie mit der Option context › ssl › verify_peer: false abschalten.
Davon raten wir dringend ab, denn es macht die Anwendung angreifbar. Fügen Sie stattdessen die Zertifikate dem Vertrauensspeicher hinzu.
Zur Erhöhung der Vertrauenswürdigkeit können wir E-Mails mit der Technologie DKIM signieren:
mail:
dkim:
domain: myweb.com # Ihre Domain
selector: lovenette # DKIM-Selektor
privateKey: %appDir%/cert/dkim.key # Pfad zur Datei mit Ihrem privaten Schlüssel
passPhrase: ... # Passphrase für den privaten Schlüssel, falls nötig
Die Optionen zum Umleiten aller E-Mails und zum Aktivieren des Debug-Panels sind im Abschnitt E-Mails debuggen beschrieben:
mail:
# leitet alle E-Mails an eine einzige Adresse um
redirect: dev@example.com
# aktiviert (true) oder deaktiviert (false) das Tracy-Panel und das Abfangen der E-Mails
debugger: ... # (bool) Standardwert ist null, also auto im Debug-Modus
DI-Services
Diese Services werden dem DI-Container hinzugefügt:
| Name | Typ | Beschreibung |
|---|---|---|
mail.mailer |
Nette\Mail\Mailer | Klasse zum Versenden von E-Mails |
mail.signer |
Nette\Mail\Signer | Signieren mit DKIM |
Wenn Sie auf eine neuere Version aktualisieren, sehen Sie sich die Seite Upgrade an.