Sending Emails With SendGrid, Django, and AWS Route 53
A guide to getting you sending emails quickly with Django
In this tutorial, you’ll learn how to configure a Django application to send emails using SendGrid’s email service and AWS Route 53. SendGrid offers a free plan which gives you 100 emails per day and it is enough to get you started.
To follow this tutorial you need the following items:
- A SendGrid account
- A registered domain
- Configured Route 53 Host Zones
- AWS account
Set Up A Domain on SendGrid
Log in to your SendGrid account and before anything create an API key. Click on the left sidebar, select Settings, then API Keys. Finally, click on Create API Key. In the pop-up window choose Restricted Access if you are only using this key for emails, plus doing so maintains other more important keys secure. Also don’t forget to give your new key a meaningful name to keep better track of what this key does, for example django_emails.
In the access details section choose Mail Send and give it mail send Full Access. This will allow your Django app to only send emails.
Now click on the Create & View button. On the next screen, you will be presented with your brand new key. Note that this key will never be shown again, so click on it to copy and paste it in a secure place to use it later. Click on Done to finalize this section.
Configure Django
For this tutorial, we’ll be using an existing application that you can clone from GitHub. Keep in mind this application is at a more advanced state and extra configuration is needed to run it locally on your computer. The README goes over some of the steps to accomplish that. Here is a screenshot of the contact form that will be sending out emails when someone fills it out and submits it.
To start the configuration go to the Django reactorlabs project and edit reactor/settings.py with your favorite text editor. Add the following variables to use Django’s SMTP interface. You can put the variables at the bottom of the file.
# reactor/settings.py....# SendGrid
EMAIL_HOST = 'smtp.sendgrid.net'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'apikey'
EMAIL_HOST_PASSWORD = os.environ.get('SENDGRID_API_KEY')
Note that the EMAIL_HOST_PASSWORD points to an environment variable called SENDGRID_API_KEY. It is usually bad practice to include secrets in your source code so we have to import it from either a .env file or from postactivate if you are using virtualenvwrapper. There are other ways to do this but for now, follow the instructions in the README or do a quick search about setting Python environment variables.
This is the form the application is using on the home page. Look at the pages/forms.py in the project.
# pages/forms.py
from django.forms import ModelForm
from django.forms import TextInput, Textarea
from .models import Contact
from django.core.mail import send_mailclass ContactForm(ModelForm):
def send_email(self):
# send email using the self.cleaned_data dictionary
subject = f"New email from {self.cleaned_data['name']}"
message = f"Contact Name: {self.cleaned_data['name']}\n" \
f"Contact Phone Number: {self.cleaned_data['phone']}\n"\
f"Contact Email: {self.cleaned_data['email']}\n\n" \
f"{self.cleaned_data['message']}"
sender = 'noreply@reactorlabs.studio'
recipient_list = ['jqngl@icloud.com']send_mail(
subject,
message,
sender,
recipient_list,
fail_silently=False,
)class Meta:
model = Contact
fields = ["name", "email", "phone", "message"]
widgets = {
"name": TextInput(
attrs={
"class": "form-control",
"id": "name",
"type": "text",
"placeholder": "Your Name *",
"required": True,
"data-validation-required-message": "Please enter your name.",
}
),
"email": TextInput(
attrs={
"class": "form-control",
"id": "email",
"type": "email",
"placeholder": "Your Email *",
"required": True,
"data-validation-required-message": "Please enter your email address.",
}
),
"phone": TextInput(
attrs={
"class": "form-control",
"id": "phone",
"type": "tel",
"placeholder": "Your Phone *",
"required": True,
"data-validation-required-message": "Please enter your phone number.",
}
),
"message": Textarea(
attrs={
"class": "form-control",
"id": "message",
"placeholder": "Your Message *",
"required": True,
"data-validation-required-message": "Please enter a message.",
"rows": 8
}
)
}
This form is using Django’s core email module and the built-in Form API clean method. Here is a shorter version of the changes. You can use Python’s new F-Strings feature to customize the way your email looks like and access the forms input values too.
# pages/forms.py....from django.core.mail import send_mailclass ContactForm(ModelForm):
def send_email(self):
# send email using the self.cleaned_data dictionary
subject = f"New email from {self.cleaned_data['name']}"
message = f"Contact Name: {self.cleaned_data['name']}\n" \
f"Contact Phone Number: {self.cleaned_data['phone']}\n"\
f"Contact Email: {self.cleaned_data['email']}\n\n" \
f"{self.cleaned_data['message']}"
sender = 'noreply@reactorlabs.studio'
recipient_list = ['jqngl@icloud.com']send_mail(
subject,
message,
sender,
recipient_list,
fail_silently=False,
)
...
Now take a look at pages/views.py HomePage view. This view checks if the form is valid and calls the send_email method from the contact form.
# pages/views.py
from django.views.generic import TemplateView
from django.views.generic.edit import CreateView
from .models import Contact
from django.urls import reverse_lazy
from django.http import HttpResponse
from .forms import ContactForm
# Create your views here.class HomePageView(CreateView):
template_name = 'home.html'
model = Contact
form_class = ContactForm
success_url = reverse_lazy("thanks")def form_valid(self, form):
form.send_email()
return super().form_valid(form)def thanks(request):
return HttpResponse("Thank you! Will get in touch soon.")
These are the details for more clarity.
# pages/views.py
...class HomePageView(CreateView):
...
def form_valid(self, form):
form.send_email()
return super().form_valid(form)
Validate Your Domain
I use AWS Route 53 for my DNS web service and my domain is from Google Domains. This had me confused for a minute as to where to install SendGrid’s DNS records. In this case, install them in Route 53 as opposed to Google Domains.
Go back to your SendGrid account and this time select Sender Authentication under Settings in the left sidebar. Select Domain Authentication and click on Authenticate Your Domain.
On the next screen find your DNS host from the list — in this case, Amazon Route 53. Check Yes to brand the links for your domain to make your emails nicer and more user-friendly.
On this screen add your domain. Here I’m using reactorlabs.studio. Click on Next to continue.
In the last step, you’ll receive the DNS Records to install in AWS Route 53. The table contains a Name and a Value which you’ll need to create five CNAME records.
Create New CNAME Records In AWS Route 53
Log in to your AWS account and navigate to the Route 53 console. Here you should already have a Hosted Zone for your domain. Click on Hosted Zones to continue to the Record Sets.
Select your Domain Name from the list and edit the record.
Create five new CNAME record sets. Make sure to omit the domain from the record name and add the value in the value section in Route 53. Here create the CNAME record with Name url1145.reactorlabs.studio and value sendgrid.net. Create all five records required by SendGrid following the same pattern.
You should end up with something similar to this.
Verify The Records Are Installed Correctly
Go back SendGrid and verify the records by checking on I’ve added these records and click on Verify.
If everything goes well you should get green checkmarks for all the records once you click Verify.
Now it’s time to go back and test out the contact form in Django. If all is configured correctly you should be able to start sending emails. I ran into a block error when testing things out. According to their documentation, it has to do with the IP Address SendGrid uses to send the email. You are more likely to run into this if you are using the free option. SendGrid’s documentation notes that they are already working on it and there should be a need to contact support but I did anyway.
This is an example of the error.
550 5.7.1 Mail from IP 167.89.12.138 was rejected due to listing in Spamhaus SBL. For details please see http://www.spamhaus.org/query/bl?ip=167.89.12.138
Don’t hesitate to reach out or connect on Twitter, let me know if there is anything missing, ask questions, or just give me some claps. Best.