E-Mails mit Django versenden

  • 30. April 2022

Manchmal möchte man mit seiner Django-App eine E-Mail verschicken. Sei es für das Zurücksetzen des Passworts, die Erinnerung an ein Ereignis oder ein Bestätigungs-E-Mail nach dem Ausfüllen eines Formulars. Mit der in Django eingebauten Funktion send_mail() gelingt das. In diesem Tutorial beschreibe ich anhand einer kleinen App, wie man ein Formular erstellt, welches den Inhalt per Mail versendet.

Bevor wir mit der Programmierung an der eigentlichen Funktion beginnen, erstellen wir ein neues Django-Projekt.

Django-Projekt erstellen

Um das Rad nicht neu zu erfinden, verweise ich hier auf meine Anleitung zum Erstellen eines neuen Django-Projektes. Ersetze den dort verwendeten Projektnamen django_start mit meinprojekt:

Erstellen eines Django-Projektes

Wenn du dein Projekt erfolgreich erstellt hast, fügen wir eine neue App namens send_email hinzu.

Erstellen der E-Mail-App

Navigiere mit der Befehlszeile zum Ort, an dem die Datei manage.py abgelegt ist, und erzeuge eine neue App namens send_email:

python manage.py startapp send_email

Nachdem du die App erstellt hast, öffne die Datei meinprojekt/settings.py und füge sie zu INSTALLED_APPS hinzu:

# meinprojekt/settings.py
(...)
INSTALLED_APPS = [
    (...)
    'send_email', # Neu
]
(...)

E-Mail-Einstellungen in settings.py konfigurieren

Behalte die Datei settings.py offen, da wir noch die grundlegenden E-Mail-Einstellungen hinzufügen müssen. Füge am Ende der Datei folgenden Code hinzu und ergänze die Beispiel-Variablen mit den Werten für die zu verwendende E-Mail-Adresse.

# Email settings
DEFAULT_FROM_EMAIL = 'Admin <admin@dein_host.ch>'  # Name unter dem die E-Mail verschickt wird und die dazugehörige E-Mail-Adresse
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'  # SMTP-Backend
EMAIL_HOST = 'smpt.dein_host.ch'
EMAIL_PORT = 465 # oder 587 oder was immer der Port deines E-Mail-Providers ist
EMAIL_USE_TLS = True  # Verbindung benutzt TLS-Verschlüsselung
EMAIL_HOST_USER = 'dein_benutzername'
EMAIL_HOST_PASSWORD = 'dein_benutzerpasswort

Die einzelnen Variablen haben folgende Bedeutung:

  • DEFAULT_FROM_EMAIL: Standard-E-Mail-Adresse, die für das Versenden von automatischen Mails verwendet wird. Wird für dieses Tutorial nicht benötigt, es ist aber nützlich, diese Variable gesetzt zu haben.
  • EMAIL_BACKEND: Spezifiziert das zu verwendende E-Mail-Backend. Für das Versenden von E-Mails müssen wir 'django.core.mail.backends.smtp.EmailBackend' verwenden. Für Testzwecke ist jedoch auch das Backend 'django.core.mail.backends.console.EmailBackend' äusserst nützlich. Damit wird das E-Mail nicht versendet, sondern in der Shell (Konsole/Terminal) ausgegeben. Eine Auflistung aller in Django zur Verfügung stehenden Backends findest du hier: https://docs.djangoproject.com/en/4.0/topics/email/#email-backends
  • EMAIL_HOST: Der zu verwendende E-Mail-Host. Im Prinzip kannst du die Mails über jede E-Mail-Adresse, die du besitzt, versenden. Erkundige dich bei deinem E-Mail-Provider über die richtigen Einstellungen. Hier wird oft smt.dein_host.ch verwendet, man sieht aber auch mail.dein_host.ch.
  • EMAIL_PORT: Der Port, über den dein E-Mail-Host die Mails versendet.
  • EMAIL_USE_SSL oder EMAIL_USE_TLS: Die Verbindung von Client zu Host wird mit SSL oder TLS verschlüsselt. Erkundige dich bei deinem E-Mail-Provider über die unterstützte Methode. Evtl. musst du ein wenig ausprobieren, bis du die richtige Einstellung gefunden hast.
  • EMAIL_HOST_USER: Der zu verwendende Benutzername (oft ist das die E-Mail-Adresse).
  • EMAIL_HOST_PASSWORT: Das Passwort des verwendeten E-Mail-Kontos.

Das Schreiben von vertraulichen Informationen wie Passwörter, etc. in die settings.py Datei wird nicht empfohlen. Es gibt verschiedene Methoden, wie dieses Problem umgangen werden kann, ist aber nicht Thema dieser Anleitung, darum gehe ich hier nicht speziell darauf ein.

Die Dokumentation aller möglichen Einstellungen findest du in den Django-Docs.

Methoden zum Versenden von Mails in Django

Der einfachste und schnellste Weg, um Mails in Django zu versenden ist eine der beiden Methoden send_mail(), oder send_mass_mail() aus dem Modul django.core.mail zu verwenden (die Dokumentation gibt es hier). Diese Methoden bieten eine vereinfachte Möglichkeit, auf die Klasse EmailMessage zuzugreifen (diese wiederum ist eine Abstraktionsschicht von Pythons smtplib). Sie bieten zwar nicht alle Eigenschaften dieser Klasse an, machen es jedoch einfach, die grundlegenden Funktionen anzuwenden.

Wenn man mehr Möglichkeiten haben möchte, z.B. das Versenden von Mails an CC- und BCC-Empfänger oder das Anhängen von Dateien, kann direkt eine Instanz von EmailMessage verwendet werden. Mehr dazu in den Docs.

Mails mittels der Shell versenden

Um die E-Mail-Einstellungen zu testen und sicherzugehen, dass alles korrekt eingestellt ist, machen wir die Feuertaufe mit der Befehlszeile.

Öffne eine Befehlszeile und navigiere zu deinem Projekt in das Verzeichnis, in dem die Datei manage.py liegt. Hier starten wir eine Django-Shell-Session. Das Besondere an einer Django-Shell-Session ist, dass damit der Python-Interpreter gestartet wird und automatisch alle nötigen Umgebungsvariablen auf settings.py zeigen.

Starte die Django-Shell mit folgendem Befehl:

python manage.py shell

Damit wird der Python-Interpreter gestartet. Das wird so angezeigt:

Python 3.8.10 (default, Nov 26 2021, 20:14:08) [GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)

Um die Mail-Funktionalität zu testen, füge folgende Befehle ein:

>>> from django.core.mail import send_mail  # Import der Funktion send_mail
>>> send_mail(
...     'Betreff',  # Betreffzeile
...     'Eine informative Mitteilung',  # Mitteilung
...     'info@deine_email_adresse.ch',  #  Absenderadresse
...     ['hans@hansmustermann.ch'],  # Liste von E-Mail-Empfängern
...     fail_silently=False,  # Definiert, dass Fehlermeldungen angezeigt werden
... )

Wenn alles klappt, siehst du folgenden Output:

-------------------------------------------------------------------------------
1
>>> 

Damit wird angezeigt, dass du erfolgreich eine (1) Mail versendet hast. Diese sollte in der Zwischenzeit beim Empfänger angekommen sein:

So ungefähr sollte das Mail aussehen, das wir über die Django-Shell versendet haben.

Wenn es hier Schwierigkeiten gibt, liegt das an nicht passenden Einstellungen in settings.py. Manchmal musst du hier ein wenig herumprobieren, bis alles klappt. Möchtest du E-Mails über Gmail, iCloud, Hotmail, etc. versenden, beachte, dass diese Dienste zusätzliche Sicherheitsmassnahmen eingebaut haben. Jeder Dienst handhabt das ein wenig anders, deswegen gehe ich hier nicht weiter darauf ein. Im Web findest du viele Anleitungen dafür.

Jetzt, da wir wissen, dass das Versenden von E-Mails funktioniert, können wir uns daran machen, die eigentliche App zu programmieren.

Beginnen wir mit der Konfiguration der URLs.

Django-URLs konfigurieren

Um die URLs für unser Projekt zu konfigurieren, passen wir zuerst die Projekt-URL in meinprojekt/urls.py an und zeigen von dort auf die App send_emails. Öffne die oben genannte Datei und ergänze sie mit folgendem Code:

# meinprojekt/urls.py
from django.contrib import admin
from django.urls import path
from django.urls import include  # neu

urlpatterns = [
    path('admin/', admin.site.urls),
    path('send_email/', include('send_email.urls')),  # neu
]

Was ist neu:

  • Zuerst importieren wir das Modul include. Damit können wir auf eine andere urls.py-Datei verweisen.
  • In der letzten Zeile dann benutzen wir das Modul include und verweisen beim Aufruf einer URL mit der Zeichenkette send_email darin auf die URL-Datei in unserer send_emails-App. Damit werden Aufrufe mit dem Muster www.meinprojekt.ch/send_email, www.meinprojekt.ch/send_email/success, etc. direkt auf die URL-Datei in der send_email-App verwiesen. Für die weitere Verarbeitung ist diese Datei verantwortlich.

Sobald das erledigt ist, erstellen wir im Verzeichnis der App send_email eine neue urls.py-Datei. Tippe in der Befehlszeile folgenden Befehl:

touch send_email/urls.py

Öffne die Datei und ergänze sie mit untenstehendem Code:

# send_emails/urls.py
from django.urls import path
from . import views
urlpatterns = [
     path('', views.contact_view, name='contact_form'),
     path('success/', views.form_success_view, name='form_success'),
]

Hier importieren wir zuerst das Modul path und auf der zweiten Zeile die Datei views aus der App send_email. In unseren urlpatterns setzten wir dann zwei URLs: Eine für die Anzeige des Formulars und eine zweite, auf die verwiesen wird, wenn dieses erfolgreich abgesendet wurde.

Nachdem wir die URLs erstellt haben, fehlen uns noch drei wichtige Teile für unsere App: die Formulardaten, die Views uns das Template.

Form erstellen

Um das Django-Formular zu erstellen, legen wir zuerst eine Datei namens forms.py in der send_email-App an:

touch send_email/forms.py

Und ergänzen die Datei mit folgendem Code:

# send_email/forms.py
from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(label='Betreff', max_length=100, required=True)
    name = forms.CharField(label='Name', max_length=100, required=True)
    email = forms.EmailField(label='E-Mail', max_length=254, required=True)
    message = forms.CharField(label='Nachricht', widget=forms.Textarea, required=True)

Wir importieren zuerst das Modul forms und erstellen dann eine Form-Klasse, die unsere Form-Felder enthält. Die vier Zeilen Code in der Klasse ContactForm werden von Django beim Aufruf des Views und des Templates zu vier HTML-Input-Formularen umgewandelt. In diesem Falle haben wir vier Form-Felder:

  1. Ein Betreff (subject) als Text-Input-Feld
  2. Ein Feld für den Namen (name) als Text-Input-Feld
  3. Ein Feld für die E-Mail-Adresse (email) als E-Mail-Input-Feld
  4. Eine Textbox (message) für die Nachricht als Text-Box-Feld (textarea)

Das ist für unser forms.py File alles. Die restliche Arbeit lassen wir vom View erledigen.

Views erstellen

Für dieses Tutorial habe ich mich entschlossen, klassische Function-Based-Views zu benutzen. Natürlich ist es für diese Aufgabe ebenso möglich, einen Class-Based-View zu verwenden (siehe Bonus). So genial die klassenbasierten Views sind, für Django-Anfänger und zum Erklären eines Konzeptes eignen sich meiner Meinung nach die Function-Based-Views besser, erlauben sie uns doch einen genauen Blick auf die Funktionen. Es gibt keine «versteckten» Methoden aus der Vererbungskette, die das Verstehen des Views oft erschweren. Um die Views zu schreiben, öffne die Datei views.py in der App send_email und ergänze sie mit folgendem Code:

# send_email/views.py

from django.core.mail import send_mail, BadHeaderError
from django.http import HttpResponse
from django.shortcuts import render, redirect

from .forms import ContactForm

def contact_view(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            subject = form.cleaned_data['subject']
            name = form.cleaned_data['name']
            email = form.cleaned_data['email']
            message = form.cleaned_data['message']
            try:
                send_mail(subject,
                          message,
                          'send@djangoblog.ch', 
                          [email]
                 )
            except BadHeaderError:
                return HttpResponse('Invalid header found.')
            return redirect('form_success')
    else:
        form = ContactForm()
    return render(request, 'send_email/contact_form.html', {'form': form})

def form_success_view(request):
    return HttpResponse('Vielen Dank für Ihre Nachricht!')

Zuerst importieren wir am Anfang der Datei, in den ersten drei Zeilen, ein paar Module:

  1. Zeile: Import der Module send_mail und BadHeaderError. Mit send_mail versenden wir die Mails und bauen mit dem Modul BadHeaderError eine Sicherheitsstufe ein
  2. Zeile: Import des Moduls HttpResponse, mit dem wir eine Zeichenkette als Antwort auf einen View zurückgeben können (ein Template ist dafür nicht nötig)
  3. Zeile: Import der Module render und redirect. render dient zum Rendern einer Webseite mit einem Template und mit redirect rufen wir eine neue Seite auf

Nach den Importen geht es weiter mit zwei Funktionen, unseren Views. Als Erstes definieren wir den View zum Rendern des Formulars (contact_view).

Der contact_view

Dieser View besteht im Kern aus einer if-else-Abfrage der angewendeten HTTP-Methode. Die Hauptarbeit passiert beim Absenden des ausgefüllten Formulars, wenn das Formular mit der POST-Methode versendet wird. Hier passiert Folgendes:

  1. Eine Instanz unsere Form-Klasse wird mit den ausgefüllten Formulardaten initialisiert (form = ContactForm(request.POST))
  2. Sind die Formulardaten okay (valid) greifen wir für jedes Feld auf die gesäuberten Daten zu (cleaned_data). Die Formulardaten werden damit in ein dem Formfeld konsistentes Format umgewandelt und von potenziell schädlichen Daten wie Escape-Zeichen, etc. gesäubert.
  3. Mit einer try-except-Anweisung versenden wir, wenn alles okay ist, die E-Mail mit der Funktion send_mail() unter Verwendung der Formulardaten. Mit der Funktion BadHeaderError, in der except-Anweisung, versuchen wir böswillige Formulardaten abzufangen. Damit wird verhindert, das im E-Mail-Header Zeilenumbrüche eingefügt werden.
  4. Ist die Anfrage keine post-Anfrage, instanziieren wir in der else-Abfrage ein leeres Formular.
  5. Sind wir an diesem Punkt angelangt, geben wir mit der return-Anweisung unser Template zurück. Entweder mit leeren Formularfeldern oder ausgefüllt mit den Angaben aus der Post-Anfrage (wenn diese nicht gültig oder unvollständig waren).

Der success_view

Hier halten wir es einfach und zeigen beim Aufruf des Views eine Zeichenkette an: 'Vielen Dank für ...'. Das Anlegen eines Templates entfällt.

BadHeaderError

Die in Django eingebaute Mail-Funktion, in django.core.mail, erlaubt keine Zeilenumbrüche in Feldern, die zur Konstruktion von Kopfzeilen verwendet werden. Das sind die Absender- und Empfängeradresse sowie der Betreff. Wenn versucht wird, mit der Funktion django.core.mail.send_mail eine E-Mail mit einem Betreff zu verwenden, der Zeilenumbrüche enthält, wird Django eine BadHeaderError-Ausnahme auslösen. In unserem Code wird diese mit der except-Anweisung aufgefangen.

Templates erstellen

Zum Anzeigen des Formulars im Webbrowser fehlt uns noch das letzte Puzzle-Stück, ein Template, welches das Formular zu HTML rendert. Dazu legen wir als Erstes ein Verzeichnis namens templates in unserer send_email-App an:

mkdir send_email/templates

In diesem Verzeichnis legen wir ein neues an mit dem Namen unserer App (send_email):

mkdir send_email/templates/send_email

Django ist damit auch bei grossen Projekten in der Lage das richtige Template zu finden,

Um das Template anzulegen, tippe Folgendes:

touch send_email/templates/send_email/contact_form.html

Öffne diese Datei und ergänze sie mit folgendem Code:

<!-- contact_form.html -->

<h1>Kontaktieren Sie uns</h1>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <div>
      <button type="submit">Absenden</button>
    </div>
</form> 

Wir haben hier folgende Elemente:

  1. <h1>: Ein HTML-Titel
  2. <form>: Ein HTML-Formular-Element
  3. {% csrf_token %}: Cross-Site-Ressource-Forgery-Tag zur Vermeidung von seitenübergreifendem Schadcode
  4. {{ form.as_p }}: Form-Tag zum Rendern des Formulars mit den Input-Feldern in HTML-<p>-Tags
  5. <div>: Ein Block-Element, welches den Senden-Button für das Formular enthält

Damit hätten wir alle Teile dieses Tutorials zusammen und können die App testen.

Senden der E-Mail

Um Mails mit unserem Formular zu versenden, müssen wir unsere neu erstellte Webseite starten. Tippe dazu folgenden Befehl in die Befehlszeile:

python manage.py runserver

Lade jetzt die Seite http://127.0.0.1:8000/send_email/. Du solltest folgende Webseite sehen:

Die Form-Seite unserer Django-App, über die wir E-Mails versenden.

Fülle die Formularfelder aus. Gib zum Testen eine E-Mail-Adresse an, auf die du Zugriff hast, und klicken den Button «absenden». Du wirst, wenn du alle Felder ordnungsgemäss ausgefüllt hast, auf die Seite http://127.0.0.1:8000/send_email/success weitergeleitet und gleichzeitig erhältst du auf die E-Mail-Adresse, die du angegeben hast, eine E-Mail:

Die E-Mail, die wir über unser Formular versendet haben.

Das wär’s für dieses Tutorial.

Happy Coding!


Bonus: Mails mit Class-Based Views versenden

Wie angesprochen, kann der oben gezeigte Function-Based View als Class-Based View geschrieben werden. Hier ist der Code dafür.

Als erstes muss die Datei urls.py in send_emails angepasst werden:

# send_emails/urls.py
from django.urls import path
from . import views

urlpatterns = [
     path('', views.ContactView.as_view(), name='contact_form'),
     path('success/', views.FormSuccessView.as_view(), name='form_success'),
]

Danach schreiben wir die beiden Views folgendermassen um:

# views.py

from django.views.generic import View, FormView
from django.core.mail import send_mail, BadHeaderError
from django.http import HttpResponse
from .forms import ContactForm

class ContactView(FormView):
    template_name = 'send_email/contact_form.html'
    form_class = ContactForm
    success_url = '/send_email/success/'

    def form_valid(self, form):
        subject = form.cleaned_data['subject']
        name = form.cleaned_data['name']
        email = form.cleaned_data['email']
        message = form.cleaned_data['message']
        try:
            send_mail(subject,
                      message,
                      'send@djangoblog.ch', 
                      [email]
             )
        except BadHeaderError:
             return HttpResponse('Invalid header found.')
         
        return super().form_valid(form)

class FormSuccessView(View):
    def get(self, request):
        return HttpResponse('Vielen Dank für Ihre Nachricht!')

Das wär’s für die Class-Based Views. Für welche Art von View du dich für deine Projekte entscheidest, hängt davon ab, was deine Vorlieben sind und wie das Projekt aufgebaut ist. Beide Methoden sind absolut legitim. Du kommst mit beiden ans Ziel.