Mail

Senden von
E-Mails

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 bei ssl, 587 bei tls, sonst 25
  • timeout – Timeout für die SMTP-Verbindung
  • persistent – eine persistente Verbindung verwenden
  • clientHost – gibt den Host-Header des Clients an
  • streamOptions – 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.

Version: 4.x