PHP/Tutorials/Symfony Mailer

Aus SELFHTML-Wiki
< PHP‎ | Tutorials
Wechseln zu: Navigation, Suche

Symfony Mailer ist eine Bibliothek, welche es auf eine recht einfach anmutende Weise ermöglicht, auch „komplizierte“ Mails zu versenden.[1] Das sind beispielsweise Mails mit Anhängen, oder mit alternativem HTML- sowie reinem Text-Inhalt, wie ihn immer noch manche Mail-Empfänger aus Sicherheitsgründen bevorzugen.

Beachten Sie: Wenn Sie lediglich einfache Textmails an sich selbst (oder eine immer gleiche, hart konfigurierte Mailadresse) versenden wollen, dann wird die Nutzung des Symfony Mailer – insbesondere wenn Ihr Hoster einen lokalen Mail-Transfer-Agent hierfür eingerichtet hat – kontraproduktiv sein. Schauen sich sich in diesem Fall die PHP-Funktion „mb_send_mail()“ an.

Grundregeln für Mails via Webformular

In solchen Mails bestimmt eine unbekannte oder nicht „disziplinarisch kontrollierte“ (z. B. zum Unternehmen gehörende) und natürlich nicht ausreichend authentifizierter Person.

  • entweder den Inhalt der Mail (einschließlich des Betreffs)
  • oder und selbst das nur in Ausnahmefällen den Empfänger
  • Ein Versand einer „Bestätigungsmail“ an eine vermeintliche Adresse des Nutzers kann und wird dazu führen, dass böswillige Nutzer diese vermeintlich tolle Funktion dazu nutzen, nicht etwa die eigene, sondern fremde Mailadressen einzugeben und damit Dritten „Nachrichten“ (z.B. Spam, Drohungen, Phishing) zukommen zu lassen. Zeigen Sie also besser eine Betätigungsseite an.

Denken Sie also sehr genau nach, bevor Sie ein Webformular online stellen, bei dessen Nutzung von Ihrem Server aus ein Mail versandt wird. Wenn es missbraucht werden kann, um Spam oder Phisching-Mails zu versenden – oder auch um zu stalken –, dann wird das auch geschehen. Der Beweis findet sich im Spamordner Ihres Mailkontos. Es ist auch kein Fehler, sich hier Hilfe zu holen, denn im Fehlerfall droht Ihnen von der Hausdurchsuchung (wegen mutmaßlicher Beihilfe zu einer Straftat) bis hin zu teuren gerichtlichen Unterlassungsverfahren Unbill. Eine zweite Meinung oder Implementierung durch jemanden, der weiß, was er tut, ist weitaus billiger.

Installation

Zwei sinnvolle Varianten der Installation

Der Symfony Mailer lässt sich auf drei Wegen installieren. Jeder davon hat Vor- und Nachteile sowie Voraussetzungen, so dass eine konkrete Empfehlung nicht angebracht ist. Allerdings ist die Variante des Herunterladens von Github nur für Entwickler des Symfony Mailers selbst sinnvoll, weil die Benutzer in diesem Fall selbst dafür sorgen müssen, das Symfony Mailer und PHP in Versionen vorliegen, welche zusammen funktionieren. Es kann da (nach Updates) also „unangenehme Überraschungen“ geben.

Im Folgenden werden die Möglichkeiten gezeigt und auf grundlegende Sachverhalte hingewiesen, die den Machern des Handbuches zum Symfony Mailer nicht als erwähnswert erschienen...

Installation mit Linux-Bordmitteln

Dieser Weg funktioniert nicht in jeder Linux-Distribution und steht auch nicht im Handbuch des Symfony Mailers. Aber beispielsweise unter Debian oder Ubuntu (und Derivaten) kann man sich mit

apt list "*symfony*mail*"

davon überzeugen, dass die Distribution (hier Debian 12 „bookworm“ im März 2024) die Pakete bereithält und auch die Version erfahren:

hp-symfony-amazon-mailer/stable 5.4.23+dfsg-1+deb12u1 all
php-symfony-google-mailer/stable 5.4.23+dfsg-1+deb12u1 all
php-symfony-mailchimp-mailer/stable 5.4.23+dfsg-1+deb12u1 all
php-symfony-mailer/stable 5.4.23+dfsg-1+deb12u1 all
php-symfony-mailgun-mailer/stable 5.4.23+dfsg-1+deb12u1 all
php-symfony-mailjet-mailer/stable 5.4.23+dfsg-1+deb12u1 all
php-symfony-mailjet-notifier/stable 5.4.23+dfsg-1+deb12u1 all
php-symfony-oh-my-smtp-mailer/stable 5.4.23+dfsg-1+deb12u1 all
php-symfony-postmark-mailer/stable 5.4.23+dfsg-1+deb12u1 all
php-symfony-sendgrid-mailer/stable 5.4.23+dfsg-1+deb12u1 all
php-symfony-sendinblue-mailer/stable 5.4.23+dfsg-1+deb12u1 all

Aus dieser Liste wäre mindestens das Paket „php-symfony-mailer“ und natürlich dessen Abhängigkeiten zu installieren – die übrigen stellen Erweiterungen für bestimmte Cloud-Systeme zur Verfügung, was hier wegen des Umfangs nicht zu besprechen ist. Aber die Dokumentation hält dafür Erklärungen bereit.

Die Installation erfolgt dann im einfachsten Fall mit

sudo apt install php-symfony-mailer

und bietet dann folgende Installation an:

Die folgenden zusätzlichen Pakete werden installiert:
  php-doctrine-deprecations php-doctrine-lexer php-email-validator php-psr-event-dispatcher
  php-symfony-event-dispatcher php-symfony-event-dispatcher-contracts php-symfony-mime
Vorgeschlagene Pakete:
  php-symfony-dependency-injection php-symfony-http-kernel
Die folgenden NEUEN Pakete werden installiert:
  php-doctrine-deprecations php-doctrine-lexer php-email-validator php-psr-event-dispatcher
  php-symfony-event-dispatcher php-symfony-event-dispatcher-contracts php-symfony-mailer
  php-symfony-mime

Wenn Sie für den Versand der Mails einen der oben erwähnten besonderen Transportwege benötigen, dann müssen Sie diesen zusätzlich installieren:

apt install php-symfony-oh-my-smtp-mailer

Wenn Sie einen eigenen Server betreiben, erhalten Sie auf diesem Weg womöglich nicht die neueste Version des Symfony Mailers, dafür aber eine, die zum – ebenfalls aus den Repos installierten – PHP passt. Wer die neueste PHP-Version (z.B. von Sury.org) installiert hat, sollte das nicht tun, denn im oben gezeigten Beispiel wäre es die Version 5.4 des Symfony Mailers. Dann sollten Sie auch die dazu gehörende Handbuchseite beachten.

Ein erster Test der Bibliothek
<?php
require_once '/usr/share/php/Symfony/Component/Mime/autoload.php';
require_once '/usr/share/php/Symfony/Component/Mailer/autoload.php';
require_once '/usr/share/php/Egulias/EmailValidator/autoload.php';

use Symfony\Component\Mime\Email; 
use Symfony\Component\Mime\Address; 
$email = (new Email());
$email -> from(Address::create('M. Mustermann <submitter@rfc2606.invalid>'));
Vorteile dieser Vorgehensweise:
  • Einfachheit des Vorgehens
  • Zentrale Installation für alle Projekte/Websites auf dem Server
  • Das Update des Symfony Mailers erfolgt zentral und mit den Mitteln des Betriebsystems.
  • Übereinstimmung der Versionen von PHP und Symfony Mailer – sofern auch PHP aus dem Repo des Linux-Distributors installiert wurde.
Nachteile:
  • Nicht die neueste Version (aber mit Backports, die sicherheitsrelevante Fehler beheben)
  • Root-Rechte sind zwingend erforderlich.
  • Umfangreiches Laden einzelner Teile (require_once)

Installation mit dem Composer

Das Handbuch des Symfony Mailers schlägt die Installation im Verzeichnis des Projektes mit dem „composer“ vor. Der „composer“ selbst kann mit den Mitteln des Betriebssystems (z.B. sudo apt install composer) oder auf anderen Wegen installiert werden. Wenn Sie sich dafür entschieden haben (oder dafür entscheiden mussten), diesen Weg zu gehen, dann sollten Sie die Notwendigkeit eines Updates mit composer self-update beachten. Haben Sie den „composer“ mit apt installiert, dann wird das Self-Update indes nicht funktionieren – das kann störend sein, wenn Sie ein aktuelles PHP, z. B. von Sury.org, installiert haben.

Der Symfony Mailer wird mit dem „composer“ wie folgt installiert:

composer require symfony/mailer

Hierbei ist zu beachten, dass die Installation im aktuellen Verzeichnis erfolgt. Darin wird beim Setup mit dem „composer“ ein Verzeichnis „vendor“ angelegt, welches eine Datei „autoload.php“ enthält. Ein erster Test der Bibliothek sähe dann so aus:

Ein erster Test der Bibliothek
<?php
require once __DIR__ . '/vendor/autoload.php';
use Symfony\Component\Mime\Email; 
use Symfony\Component\Mime\Address; 
$email = (new Email());
$email -> from(Address::create('M. Mustermann <submitter@rfc2606.invalid>'));
Hauptartikel: PHP/Tutorials/Abhängigkeiten mit Composer verwalten
Vorteile dieser Vorgehensweise:
  • Keine Root-Rechte erforderlich.
  • Einfachheit des Vorgehens
  • Einfachere Einbindung in Projekte (weniger requires nötig)
Nachteile:
  • Manuelles Updates mit „composer update“ (im selben Ordner auszuführen!) sind notwendig. Das wird gern vergessen und daraus resultieren dann offen bleibende Sicherheitslücken.

Anwendungsbeispiel „Formmailer“

Hier wird mit Absicht kein allgemeiner Formmailer gezeigt, sondern ein solcher, der spezielle Bedürfnisse (→ Szenario) erfüllt.

Vorbetrachtungen

Als Formmailer wird ein Programm bezeichnet, das Formular-Eingaben per E-Mail versendet. Es gibt dabei zwar die Möglichkeit, die in einem HTML-Formular getätigten Eingaben durch ein mailto: im action-Attribut per E-Mail zu versenden. Allerdings ist diese Methode nicht sonderlich zuverlässig, da sie vom Browser und E-Mail-Programm des Benutzers abhängig ist.

Ein Kontaktformular ist ein spezieller Formmailer, der lediglich den Namen, die E-Mail-Adresse und eine Nachricht abfragt und per E-Mail an den Betreiber versendet. Dabei sollte allerdings – sofern eine Impressumspflicht besteht – immer auch die E-Mail-Adresse zusätzlich angegeben werden: eRecht24.de: Impressumspflicht: E-Mail-Adresse oder Kontaktformular im Impressum?.

Hinzu tritt, dass je nachdem, ob Sie als Privatperson oder als Unternehmen/Organisation handeln, den Nutzern die Rechte aus Kapitel 3 der DSGVO gewähren müssen. Das kann erheblichen Mehraufwand bedeuten.

Hauptartikel: Grundlagen/Rechtsfragen/Impressumspflicht

Szenario

Die Firma „Mustermann & Co.“ möchte, dass Benutzer ihrer Webseite Angebote für den Versand von Äpfeln und Birnen anfordern können.

  • Hierzu sollen die Benutzer die gewünschte Anzahl der Äpfel und/oder der Birnen angeben können.
  • Damit die eingebenen Daten in einem Ticket-System maschinell verarbeitbar sind soll aus diesen eine Datei im json-Format erzeugt werden, welche an das Mail angehangen wird.
  • Zusätzlich sollen die Daten in für Menschen angenehm lesbarer Form im Mail aufscheinen.
  • Das dann je nach Geschmack des Empfängers als reiner Text oder als HTML und hübsch formatiert.

Skriptbeispiel:

Das Beispiel besteht aus vier Dateien: Formular.html, data-mail.php, danke.php, sowie der dsn.php, die an einem sichereren Ort als im Webverzeichnis abgelegt werden sollte.

Datei: Formular.html
Dieses ist das HTML-Formular, mit dem die Daten gesendet werden.
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<meta charset="UTF-8">
<title>Angebotsanfrage erstellen</title>
<style>
label         {display: inline-block;width: 8rem;}
</style>
</head>
<body>
<p>Um ein Angebot für den Kauf unserer Äpfel und Birnen zu erhalten geben Sie bitte die gewünschten Stückzahlen, ggf. Ihre Kundennummer und Ihre Mailadresse ein:</p>
<form action="data-mail.php" method="post">
<label for="aepfel">Äpfel:(*)</label>
<input name="aepfel" id="aepfel" type="number" min=0 max=1000 step=1 value=0 size=6> 
<br>
<label for="birnen">Birnen:(*)</label>
<input name="birnen" id="birnen" type="number" min=0 max=1000 step=1 value=0 size=6>
<p>Ihre Daten:</p>
<label for="knr">Kundenummer:</label><input type="text" name="knr" id="knr" value='' size=10> 
<br>
<label for="reply-to">Mailadresse(*):</label><input required type="email" name="reply-to" id="reply-to" value='' size=40> 
<p>
(Hinweise zu Datenschutz, AGB, Verarbeitungsverlauf)
</p>
<button>senden</button>
</form>
<body>
</html>

Datei: dsn.php

Bei Benutzung eines „SMTP-Smarthostes“ werden Zugangsdaten benötigt. Da diese natürlich geheim sind, sollten diese in einer Datei außerhalb des Webspaces abgelegt werden, müssen von PHP (bzw. dem Webserver) aber gelesen werden können)

<?php
define( 'Dsn', 'smtp://username:password@smtp.invalid:25/?encryption=ssl&auth_mode=login';

Datei: data-mail.php

<?php

### Für die Fehlersuche anschalten.
/*
error_reporting(E_ALL);
ini_set("display_errors", 1);
#/*

/*
 * Konfiguration der erwarteten Daten in $_POST.
 * Wenn diese nicht passen liegt entweder ein Angriff 
 * oder ein Fehler bei den Benutzereingaben vor.
*/

$arExpectedData[ 'aepfel' ][ 'type' ]        = 'int';
$arExpectedData[ 'aepfel' ][ 'min' ]         = 0;
$arExpectedData[ 'aepfel' ][ 'max' ]         = 1000;

$arExpectedData[ 'birnen' ][ 'type' ]        = 'int';
$arExpectedData[ 'birnen' ][ 'min' ]         = 0;
$arExpectedData[ 'birnen' ][ 'max' ]         = 1000;

$arExpectedData[ 'knr' ][ 'type' ]           = 'str';
$arExpectedData[ 'knr' ][ 'maxLen' ]         = 10;
$arExpectedData[ 'knr' ][ 'minLen' ]         = 0;
$arExpectedData[ 'knr' ][ 'required' ]       = false;

$arExpectedData[ 'reply-to' ][ 'type' ]      = 'email';
$arExpectedData[ 'reply-to' ][ 'maxLen' ]    = 120;
$arExpectedData[ 'reply-to' ][ 'minLen' ]    = 8;
$arExpectedData[ 'reply-to' ][ 'required' ]  = true;

/*
 * Konfiguration des Empfängers und des (technischen) Versenders des Mails.
 * Für den Fall, das auf die Mails direkt geantwortet wird, wird die
 * angebebene/übertragene Mailadresse im Header mittels reply-to verschickt,
 * Mailprogramme senden die Antwort dann also dort hin.
*/ 
#define( 'mailTo'  , 'Max Mustermann <reciver@rfc2606.invalid>');
#define( 'mailFrom', 'Angebotsanforderung<submitter@rfc2606.invalid>');

/*
 * Konfiguration des SMTP-Servers  
 * Siehe oben ...Datei dsn.php
*/ 

require_once '/pfad/zu/sicherem/verzeichnis/dsn.php'; 

/*
 * Programm
*/

if ( headers_sent() ) {
	/*
	 * Manche Editoren speichern Texte (Skripte) gerne mit vorangestellter,
	 * aber unsichtbarer BOM.
	 * 
	 * Manche fügen vor dem <?php am Beginn Leerzeichen oder Zeilenumbrüche ein.
	 * 
	 * Dieses Skript funktioniert in diesen Fällen nicht.
	*/ 
    trigger_error( 'Skipt ohne BOM speichern! Keine Zeichen oder Leerzeilen vor dem <?php in Zeile 1!', E_USER_ERROR );
    # exit
}


function exitWithZeroResponce($msg=false) {
    /*
     * Diese Funktion hat den Job, keine Antwort zu geben.
     * Es wird benutzt, um auf fehlerhafte/unmögliche Eingaben NICHT zu reagieren.
     * 
     * Folgen:
     * 
     * Der Browser bleibt hierdurch aus Sicht eines normalen
     * Benutzers stehen und bietet das Formular weiterhin an.
     * 
     * Personen mit Missbrauchsabsicht erhalten keine
     * für diese brauchbaren Informationen.
     *
     * Die Übermittlung einer id oder message kann für das Debugging logischer Fehler genutzt werden,
     * dann kann man diese anzeigen.
    */ 
    http_response_code( 204 );
    exit;
}

/*
* Wie erwartet nimmt das Überprüfen der Daten den größten Raum ein...
* Wenn Sie sich nur für den Versand interessieren: Scrollen!
*/


$data = [];
$data['id'] = hexdec( uniqid() );

if (
        isset( $_SERVER['REQUEST_METHOD'] ) 
    and
        $_SERVER['REQUEST_METHOD'] != 'POST'
) {
    exitWithZeroResponce(1);
}

foreach ( array_keys( $_POST ) as $key ) {
	
    if ( ! isset( $arExpectedData[ $key ] ) ) {
        exitWithZeroResponce(2);
    }
    switch ( $arExpectedData[ $key ]['type'] ) {

        case 'int' :
            $val = ( intval( $_POST[ $key ] ) );
            if (
                    $val < $arExpectedData[ $key ][ 'min' ]
                or
                    $val > $arExpectedData[ $key ][ 'max' ]
            ) {
                exitWithZeroResponce(3);
            }
            $data[ $key ] = $val;
            break;
            
        case 'str' :
            $val = trim( $_POST[ $key ] );
            if (
                    ( '' == $val ) 
                and
                    ( $arExpectedData[ $key ]['required'] )
            ) {
                exitWithZeroResponce(4);
            }
            $l = mb_strlen( $val );
            if ( 
                    $l  >  $arExpectedData[ $key ][ 'maxLen' ] 
                or
                    $l  <  $arExpectedData[ $key ][ 'minLen' ]     
            ) {
                exitWithZeroResponce(5);
            }
            $data[ $key ] = $val;
            break;
            
        case 'email' :
			$val = trim( $_POST[ $key ] );
			if ( filter_var( $val, FILTER_VALIDATE_EMAIL ) ) {
				$data[ $key ] = $val;
			} else {
				exitWithZeroResponce(6);
			}
			$data[ $key ] = $val;
			break;
            
       default :
           trigger_error( 'Konfigurationsfehler. Datentyp ' . $arExpectedData[ $key ][ 'type' ] . ' ist nicht vorgesehen.', E_USER_ERROR );
           #exit;
    }
}    

/*
 * An diesem Punkt sind alle Daten geprüft, es kann also das Mail erzeugt werden.
 * Hier wird nur gezeigt, wie das funktioniert, wenn der Symfony Mailer mit dem
 * Composer geladen wurde, außerdem wird nur gezeigt, wie der Versand funktioniert,
 * wenn dieser via „smarthost“ erfolgen soll.
 * 
 * Auch das Landen (Linten unnd Kompilieren!) der Bibliothek erfolgt erst zu
 * diesem Zeitpunkt!
 *
 * Das ist im Hinblick auf Skripte von Missbrauchern wichtig, weil das natürlich
 * Systemlast erzeugt. Stimmen die Daten nicht (und lässt das wie im Beispiel Missbrauch
 * vermuten), dann kann und sollte man sich das Laden von Bibliotheken sparen.
*/

$textMail = <<<EOF
Angebotsanforderung #{$data['id']}

Der Benutzer oder die Benutzerin hat um ein Angebot für 

{$data['aepfel']} Äpfel und 
{$data['birnen']} Birnen

gebeten.

Rückantwort an {$data['reply-to']}
EOF;

$htmlMail = <<<EOF
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<meta charset="utf-8">
</head>
<p><strong>Angebotsanforderung #{$data['id']}</strong></p>
<p>Der Benutzer oder die Benutzerin hat um ein Angebot für</p> 
<ul>
    <li>{$data['aepfel']} Äpfel und</li>
    <li>{$data['birnen']} Birnen<li>
</ul>
<p>gebeten.</p>
<p>Rückantwort an <a href="mailto:{$data['reply-to']}">{$data['reply-to']}</a></p>
</html>
EOF;

/*
 * Laden der notwendigen Biblioteken:
 * composer require symfony/mailer wurde also im aktuellen Ordner ausgeführt.
*/

require_once __DIR__ . '/vendor/autoload.php';
use Symfony\Component\Mime\Email; 
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\Transport;
use Symfony\Component\Mime\Address; 

/*
 * Erzeugen des „Transport-Methode“ aus der Konstante Dsn
*/
$Transport = Transport::fromDsn( Dsn );
$oMailer = new Mailer( $Transport );
$oMail = new Email();

/*
 * Hinzufügen der Adressen:
*/
$oMail -> from(Address::create( mailFrom ) );
$oMail -> to(Address::create( mailTo ) ) ;
$oMail -> replyTo( $data[ 'reply-to' ] );

/*
 * Subjekt und alternativen Text/HTML-Part:
*/
$oMail -> subject( 'Angebotsanforderung via Web: #' . $data['id'] );
$oMail -> text( $textMail );
$oMail -> html( $htmlMail );

/*
 * Die Daten im Json-Format als Anhang:
*/
$oMail -> attach( json_encode( $data, JSON_PRETTY_PRINT ) , 'Angebotsanforderung-' . $data['id'] . '.json', 'text/json' );
$oMailer->send($oMail);

/*
 * Weiterleitung (Reload-Sperre)
*/
header( 'Location: danke.php?id=' . $data['id'] );

Nach dem Versenden des Mails wird, zum einen Reload und damit das mehrfache des Mails unmöglich zu machen, auf eine Seite „danke.php“ weiter geleitet. Dabei wird eine, mit uniqid() erzeugte ID angehangen, die im Subjekt, im MaiLtext und im Name des Dateianhanges wieder auftaucht.

Datei: danke.php
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<meta charset="utf-8">
<title>Danke für Ihre Anfrage</title>
</head>
<body>
<h1>Ihre Angebotsanforderung Nr. <?=htmlspecialchars($_GET['id']);?></h1>
<p>ist verschickt worden und wird nun von uns bearbeitet.</p>
<p>Sie erhalten innerhalb von spätestens zwei Werktagen von der Max Mustermann & Co. OHG eine Antwort.</p>
<p>→ <a href="./">Zurück</a></p>
</body>
</html>

Siehe auch

  • Composer

    Abhängigkeiten mit Composer verwalten

  • E-Mail-Newsletter

    E-Mails und E-Mail-Newsletter mit HTML und CSS gestalten

  • Einführung in Symfony

Weblinks

  1. Sending Emails with Mailer (symfony.com)