Django Email/Contact Form Tutorial
Let's build a simple contact form that sends email for a Django 4.1 website. We can take advantage of Django's built-in email support to make this relatively painless and then send the emails for real using the SendGrid email service.
Initial setup
The first step is to create a dedicated directory for our code. A convenient location is the Desktop. From the command line, execute the following commands to navigate to the Desktop and create a new contact
folder with either Windows or macOS.
# Windows $ cd onedrive\desktop\code $ mkdir helloworld $ cd helloworld # macOS $ cd ~/desktop/code $ mkdir helloworld $ cd helloworld
Now we can create and active a virtual environment called .venv
. Then install the latest version of Django.
# Windows $ python -m venv .venv $ .venv\Scripts\Activate.ps1 (.venv) $ python -m pip install django~=4.1.0 # macOS $ python3 -m venv .venv $ source .venv/bin/activate (.venv) $ python3 -m pip install django~=4.1.0
Next let's create a new Django project called config
and within it an app called sendemail
:
(.venv) $ django-admin startproject config . (.venv) $ python manage.py startapp sendemail
To make sure everything installed correctly let's migrate
and then runserver
.
(.venv) $ python manage.py migrate (.venv) $ python manage.py runserver
If you open you browser to 127.0.0.1:8000 you should see the following screen:
Update settings.py
We've created a new app so we need to explicitly add it to our Django project. Within your settings.py
file, under INSTALLED_APPS
add sendemail
at the top.
# config/settings.py INSTALLED_APPS [ "sendemail.apps.SendemailConfig", # new ... ]
Then within the same settings.py
file, specify the DEFAULT_FROM_EMAIL
, which is [email protected]
for me. And update EMAIL_BACKEND
specifying which email backend we'll use, console
, so that the email is outputted to the command line.
# config/settings.py DEFAULT_FROM_EMAIL = "[email protected]" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
Update urls.py
Since we've added an app to our Django project we need to update the root django_project/urls.py
file, adding include
to the top line and a new urlpattern for the app:
# django_project/urls.py from django.contrib import admin from django.urls import path, include # new urlpatterns = [ path("admin/", admin.site.urls), path("", include("sendemail.urls")), # new ]
Next create a new file called sendemail/urls.py
and add the following code so that the main contact form is at email/
and a successful submission redirects to success/
.
# sendemail/urls.py from django.contrib import admin from django.urls import path from .views import contactView, successView urlpatterns = [ path("contact/", contactView, name="contact"), path("success/", successView, name="success"), ]
Create forms.py
Still within our sendemail
app, create a new file named sendemail/forms.py
. This will contain the fields in our actual contact form. We'll require three: from_email
, subject
, and message
.
# sendemail/forms.py from django import forms class ContactForm(forms.Form): from_email = forms.EmailField(required=True) subject = forms.CharField(required=True) message = forms.CharField(widget=forms.Textarea, required=True)
We're using Django's built-in Forms API here to quickly create three fields.
Create views.py
Let's create the view now that will do the bulk of the work for our contact form. Update the existing sendemail/views.py
file:
# sendemail/views.py from django.core.mail import send_mail, BadHeaderError from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render, redirect from .forms import ContactForm def contactView(request): if request.method == "GET": form = ContactForm() else: form = ContactForm(request.POST) if form.is_valid(): subject = form.cleaned_data["subject"] from_email = form.cleaned_data["from_email"] message = form.cleaned_data['message'] try: send_mail(subject, message, from_email, ["[email protected]"]) except BadHeaderError: return HttpResponse("Invalid header found.") return redirect("success") return render(request, "email.html", {"form": form}) def successView(request): return HttpResponse("Success! Thank you for your message.")
There's a lot going on here! We start by importing send_mail and BadHeaderError for security reasons. At the bottom of the imports we reference ContactForm which we just created in our forms.py
file.
Create templates
Final step! We need to create the templates for our email and success pages. I like to create a project-level templates
folder and put all of my templates in there. So create a new directory called templates
in the project directory.
(.venv) $ mkdir templates
Next update our settings.py
file to tell Django to look in this directory for templates. Update the DIRS
settings within TEMPLATES
. This is a one-line change.
# django_project/settings.py TEMPLATES = [ { ... "DIRS": [BASE_DIR / "templates"], # new ... }, ]
Now create a new file called templates/email.html
and update it with the following code:
<!-- templates/email.html --> <h1>Contact Us</h1> <form method="post"> {% csrf_token %} {{ form.as_p }} <div class="form-actions"> <button type="submit">Send</button> </div> </form>
Send first email
Make sure the server is running with python manage.py runserver
and load http://127.0.0.1:8000/email/ in your web browser, fill out the form, and click the Send button.
You will be redirected to the http://127.0.0.1:8000/success/ if the email goes through.
And in your console you can see that the email was sent:
(.venv) $ python manage.py runserver Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). August 22, 2022 - 21:21:53 Django version 4.1.0, using settings 'django_project.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. [22/Aug/2022 21:21:59] "GET /contact/ HTTP/1.1" 200 622 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: Hello From: will@learndjango.com To: admin@example.com Date: Mon, 22 Apr 2022 21:22:35 -0000 Message-ID: <158715855539.8761.15735317856240850309@williams-mbp.lan> Does this email contact form work? ------------------------------------------------------------------------------- [22/Aug/2022 21:22:35] "POST /contact/ HTTP/1.1" 302 0 [22/Aug/2022 21:22:35] "GET /success/ HTTP/1.1" 200 36
Email Service
To send actual emails you'll need a managed service like SendGrid, mailgun, or SES. Fortunately, Django makes switch to these services straightforward.
To implement with SendGrid, create a free account and select the SMTP option, which is easier to configure than the Web API approach.
The next screen requires naming your API Key Name, which I've called newspaper
but it could be named anything. Click the blue "Create Key" button next to it.
Update your settings.py
as follows, switching from console
to smtp
for EMAIL_BACKEND
, while adding several new fields. Update the EMAIL_HOST_PASSWORD
with the SendGrid password for your account.
# django_project/settings.py EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" # new DEFAULT_FROM_EMAIL = "[email protected]" EMAIL_HOST = "smtp.sendgrid.net" # new EMAIL_HOST_USER = "apikey" # new EMAIL_HOST_PASSWORD = "<sendgrid_password>" # new EMAIL_PORT = 587 # new EMAIL_USE_TLS = True # new
There is also one additional step required, which is single sender verification. Essentially, this is an additional step to help SendGrid comply with anti-spam laws. Follow SendGrid's instructions to verify an email account. While previously it was possible to send emails from a free address at services like gmail.com
or yahoo.com
, that is no longer the case due to the DMARC email authentication protocol. So to send actual emails now you must use a custom, non-free email account which you can verify ownership of.
Conclusion
With this configuration in place, you should be able to send real emails from your Django app. Often this is used when a user signs up for the first time, needs to reset a password, and so on. All these steps, including the use of an environment variable for EMAIL_HOST_PASSWORD
, and customizing each email are covered in the Django for Beginners book.