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-backendsEMAIL_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
oderEMAIL_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:
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 andereurls.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 unserersend_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 dersend_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:
- Ein Betreff (
subject
) als Text-Input-Feld - Ein Feld für den Namen (
name
) als Text-Input-Feld - Ein Feld für die E-Mail-Adresse (
email
) als E-Mail-Input-Feld - 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:
- Zeile: Import der Module
send_mail
undBadHeaderError
. Mit send_mail versenden wir die Mails und bauen mit dem ModulBadHeaderError
eine Sicherheitsstufe ein - 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) - Zeile: Import der Module
render
undredirect
.render
dient zum Rendern einer Webseite mit einem Template und mitredirect
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:
- Eine Instanz unsere Form-Klasse wird mit den ausgefüllten Formulardaten initialisiert (
form = ContactForm(request.POST)
) - 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. - 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 FunktionBadHeaderError
, in der except-Anweisung, versuchen wir böswillige Formulardaten abzufangen. Damit wird verhindert, das im E-Mail-Header Zeilenumbrüche eingefügt werden. - Ist die Anfrage keine post-Anfrage, instanziieren wir in der else-Abfrage ein leeres Formular.
- 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:
<h1>
: Ein HTML-Titel<form>
: Ein HTML-Formular-Element{% csrf_token %}
: Cross-Site-Ressource-Forgery-Tag zur Vermeidung von seitenübergreifendem Schadcode{{ form.as_p }}
: Form-Tag zum Rendern des Formulars mit den Input-Feldern in HTML-<p>
-Tags<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:
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:
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.
Quellen
- Django Dokumentation: E-Mail backends (englisch)
- Django Dokumentation: E-Mails versenden mit send_mail() (englisch)
- Django Dokumentation: E-Mails versenden mit EmailMessage (englisch)
- Django Dokumentation: Mit Formen arbeiten (englisch)
- Django Dokumentation: Auf gesäuberte Daten ("clean data") zugreifen (englisch)
- Django Book: E-mail Header Injection und BadHeaderError (englisch)