Let’s build a simple contact form that sends email for a Django 2.0 website. We can take advantage of Django’s built-in email support to make this relatively painless.

Complete working code can for this tutorial can be found on Github.

Initial setup

The first step is to create a dedicated directory for our code. This can be anywhere on the computer but I’ll show how to place it on the Desktop. From the command line, execute the following commands to navigate to the Desktop and create a new contact folder.

$ cd ~/Desktop
$ mkdir contact
$ cd contact

Pipenv is now the recommended way to create a virtual environments in Django so we will use it here. If this is your first time with Pipenv, it can installed as follows:

$ pip3 install pipenv

Note: If you need help additional help configuring your Django dev environment, please see this installation guide.

Now we can install Django and activate our virtual environment.

$ pipenv install django
$ pipenv shell

The actual name of the virtual environment will be contact-XXX where the “XXX” is different for you and me. Therefore I’ll shorted the name to just contact going forward.

Next let’s create a new Django project called djcontact and then an app within it called sendemail:

(contact) $ django-admin startproject djcontact .
(contact) $ ./manage.py startapp sendemail

To make sure everything installed correctly let’s migrate and then runserver.

(contact) $ ./manage.py migrate
(contact) $ ./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 bottom.

# djcontact/settings.py
INSTALLED_APPS [
    ...
    'sendemail',
]

Then within the same settings.py file add a line at the very end specifying which email backend we’ll use:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

For now we’ll be outputting our email to the console. Later we can add a few lines to this settings.py file to specify whatever production backend mail server–MailGun, SendGrid, etc–we’d like.

Update urls.py

Since we’ve added an app to our Django project we need to update the root urls.py file, adding include to the top line and a new urlpattern for the app:

# djcontact/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('sendemail.urls')),
]

Next create a new file sendemail/urls.py in our app and add the following code:

# sendemail/urls.py
from django.contrib import admin
from django.urls import path

from . import views

urlpatterns = [
    path('email/', views.emailView, name='email'),
    path('success/', views.successView, name='success'),
]

Create forms.py

Still within our sendemail app create a new file forms.py which will contain the forms in our actual contact form:

# 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 emailView(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 and create two new files in there.

(contact) $ mkdir templates
(contact) $ touch templates/email.html
(contact) $ touch templates/success.html

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.

# djcontact/settings.py
TEMPLATES = [
    {
        ...
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        ...
    },
]

Now update our template files 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>
<!-- templates/success.html -->
<h1>Success! Email sent.</h1>

Send first email

Make sure the server is running with ./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:

(contact) $ ./manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
December 18, 2017 - 15:01:13
Django version 2.0, using settings 'djcontact.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Not Found: /
[18/Dec/2017 15:02:14] "GET / HTTP/1.1" 404 2262
[18/Dec/2017 15:02:25] "GET /email HTTP/1.1" 301 0
[18/Dec/2017 15:02:25] "GET /email/ HTTP/1.1" 200 629
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Hi there
From: [email protected]
To: [email protected]
Date: Mon, 18 Dec 2017 15:02:57 -0000
Message-ID: <[email protected]>

Check out this sweet contact form I just created in Django!
-------------------------------------------------------------------------------
[18/Dec/2017 15:03:02] "POST /email/ HTTP/1.1" 302 0
[18/Dec/2017 15:03:02] "GET /success/ HTTP/1.1" 200 36

Next Steps

Check out Django for Beginners, a free online book on how to create and deploy multiple Django applications. Starting with a simple “Hello, World” application it progresses through multiple web applications of increasing complexity showing Django best practices along the way.