How to Send Scheduled Emails with Python

By Nikola Dokoski

Witnessing the rise of Python, we can’t help but notice its implementation in almost every aspect of our lives. From its general purpose for developing GUI applications, web applications, and websites, to its core functionality, which is to take care of the common programming tasks, Python is certainly classified as a high-level programming language. With that being said, Python has a simple syntax that makes the code base readable and maintainable. There are many other advantages of using Python, especially its easy-to-use feature that separates it from other programming languages. 

In this article, Nikola Dokoski covers a few methods for automatically sending email messages using Django. His basic idea is to create a Django project where he will present a model for holding scheduled mail, add a management command to send scheduled emails manually, and finally cover a few methods to automate this process.

Nikola will explain the entire process – if you are interested in the main bits of code as well as a `tar.gz` that you can install via pip, just scroll to the end.

Let’s start off with some basic stuff.

I will be using a virtualenv for this article. I recommend that you use it too – you can install it via `pip install virtualenv` and check out the docs at https://pypi.org/project/virtualenv/1.7.1.2/

Create a venv and install Django:

virtualenv venv
source venv/bin/activate
pip install django # At the time of writing, I am using django 3.0.6.

Then we create the project and the app.

django-admin startproject auto_mail
cd auto_mail
python manage.py migrate # For default models - users and whatnot.
python manage.py startapp mail_app

We won’t really be creating any views and URLs here – we will just use the models, admin, and management modules. This makes the app easier to use and to add to other projects.
On that end, we want a model for a scheduled mail.

# in mail_app/models.py

class MailAttachment(models.Model):
	attachment_file = models.FileField()
	attached_to = models.ForeignKey('ScheduledMail', related_name = 'attachments', on_delete = models.CASCADE)

	def __str__(self):
    	return '%s (%s)' % (self.attachment_file.filename, self.attached_to.subject)


class MailRecipient(models.Model):
	mail_address = models.CharField(max_length = 40)

	def __str__(self):
    	return self.mail_address


class ScheduledMail(models.Model):
	subject = models.CharField(max_length = 40)
	template = models.FileField(upload_to = 'mail_app/mails')
	send_on = models.DateTimeField(default = timezone.now())
	recipients_list = models.ManyToManyField(MailRecipient, related_name = 'mail_list')

	def __str__(self):
    	return self.subject

Fairly simple. We have a ScheduledMail model, which is the basis for this app that holds a subject and a template. We have the template as a file because, well, we may want our mail to be formatted nicely as an HTML message. If we were to use a standard CharField, it would have to be with a really high max length, which is not the best for use in a database, so way better is to just upload files. Furthermore, we can create a better method of creating mails via a view, as well as preview and whatnot, then save it to a file and upload it.

The other models – MailAttachment and MailRecipient – will allow us to add multiple users and attachments to our mail messages. With that, we have most of what our app will be using! We just need to write a management command.

Before we do that though, let’s create the database. For development, I just go with sqlite3, as it’s easy to backup and play around with. You might want to use PostgreSQL or something heavier for production. So, let us add our app in the project settings, under INSTALLED_APPS. Also, since we are using a FileField for our mail template, we also need to define MEDIA_ROOT and MEDIA_URL values so that our files can be uploaded properly. 

# in auto_mail/settings.py
...
INSTALLED_APPS = [
	'django.contrib.admin',
	'django.contrib.auth',
	'django.contrib.contenttypes',
	'django.contrib.sessions',
	'django.contrib.messages',
	'django.contrib.staticfiles',
	'mail_app',
]

MEDIA_URL = '/media/'
MEDIA_ROOT = 'media/'

And now we can migrate:

python manage.py makemigrations
python manage.py migrate

One last step before finally viewing the admin – registering the models. Add this to your app’s admin.py folder:

# in auto_mail/admin.py
from django.contrib import admin
from .models import ScheduledMail, MailAttachment, MailRecipient

# Register your models here.

@admin.register(ScheduledMail)
class MailAdmin(admin.ModelAdmin):
	pass

@admin.register(MailAttachment)
class AttachmentAdmin(admin.ModelAdmin):
	pass

@admin.register(MailRecipient)
class RecipientAdmin(admin.ModelAdmin):
	pass

And finally, create a superuser and run the server:

python manage.py createsuperuser
python manage.py runserver

Great, we have our models, we can add mails, add recipients, and so on. Let’s create the actual command. And, well, it’s pretty simple.

mkdir -p mail_app/management/commands
touch mail_app/management/commands/__init__.py
touch mail_app/management/__init__.py

After this, we just need to add our send mail command in that folder. To keep the code clean, let us add all mail functionalities in the models themselves.

# in mail_app/models.py

from django.conf import settings

auto_mail_from = 'from@mail.com'
if hasattr(settings, 'AUTO_MAIL_FROM'):
	auto_mail_from = settings.auto_mail_from

class ScheduledMail(models.Model):


        ...
	@classmethod
	def get_today_mail(cls):
    	today = date.today()
    	return cls.objects.filter(send_on__year = today.year, send_on__month = today.month, send_on__day = today.day)

	def send_scheduled_mail(self):
    	message = self.template.read().decode('utf-8')
    	recipient_list = list(self.recipients_list.values_list('mail_address', flat = True))
    	mail_msg = EmailMessage(
        	subject = self.subject,
        	body = message,
        	from_email = settings.AUTO_MAIL_FROM,
        	to = recipient_list,
    	)
    	mail_msg.content_subtype = 'html'

    	mail_msg.send()

Then we can use these methods in the management command. 

# in mail_app/management/commands/send_scheduled_mails.py

from datetime import date

from django.core.management import BaseCommand

from mail_app.models import ScheduledMail

class Command(BaseCommand):
	help = 'Sends an email to any client for which a discount has started today.'

	def handle(self, *args, **options):
    	today_mail = ScheduledMail.get_today_mail()
    	for mail_message in today_mail:
        	mail_message.send_scheduled_mail()

Here, to define the from_email argument, we have added value in settings.py. This makes it a bit more modular, but you should then add this variable to the settings file. If not, you can just hardcode a string here. While we’re editing settings though, let’s also add some email parameters. For now, we will just use the file-based email backend so we can test the app.

# in settings.py

...
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/

STATIC_URL = '/static/'


# AUTO_MAIL stuff
# it doesn't actually matter where this is

EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location

AUTO_MAIL_FROM = 'some_mail@mail.com'

Neat! Let’s try it out (you might have to add some objects in admin for it to work)

$python manage.py send_scheduled_mails
$cat django_mail/*.log

Content-Type: text/html; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: My first mail
From: some_mail@mail.com
To: TestAccount@mail.com, AnotherTestAccount@mail.com
Date: Fri, 29 May 2020 11:13:12 -0000
Message-ID: <159075079254.4003.12926778827038569342@ninoneutrino>

This is a simple mail message, uploaded to auto-mail.
I hope this works!

Yours,
Nikola

-------------------------------------------------------------------------------

And ta-da! We can send mail via a simple command. This is not the final product yet though – we will add a bunch of quality of life additions later on (such as a few options when selecting recipients for a new mail, variables within the mail message and so on).

Before we do that though, we have a couple of things to finish off. First off – the automation. Sending mail via commands is cool, but we can set it up so it’s all automatic.
Enter Cron. Cron is extremely basic, it’s great for making quick and dirty projects, or demonstrating how to automate something.
Just add this to your crontab:

0 0 * * * cd /path/to/your/project/root/ && /path/to/your/venv/bin/python3.6 manage.py send_scheduled_mails

You can change the minutes and hour values (the first two zeroes) to whatever the current time is + a few minutes, and see if you get anything new in django_mail/. If it doesn’t work, check if your paths are correct by executing the command you paster in crontab from your home directory. If there are errors, you can usually find them in /var/log/syslog.

And as far as automation goes, this is enough. In a more serious project, however, you may want to use Celery, which comes included in the project and will spare you from messing with Cron. Celery is its own beast though, and there’s little point in writing a tutorial on how to write a celery app when the celery website has its own tutorial, so you might want to check that out at First Steps with Celery and then later on First Steps with Django.

Then, there is one last thing to do – actually, send emails. So far we’ve only been saving them in files, but in real life, we want to send real messages, This might be a bit tricky and may involve several other technologies that are out of the scope of this tutorial. If you want to accomplish this, there are a few methods you can think about:

  • Host your own mail server. If you are doing this as a side project, proof of concept or just messing around, this is probably the way to do it. I recommend either using virtual machines or containers, but the easiest way is to probably use a web solution and just run an AWS or GCE instance.
  • Use an existing mail server. Gmail, Mailgun, Amazon SES, etc. There’s a bunch of these, some free, some paid, but in general, you can configure your settings to use a mail server.

Short version: 

So, if you found this and just want to copy and paste some code, here are the final tidbits: 

# models.py
import datetime
from datetime import date

from django.db import models
from django.utils import timezone
from django.core.mail import EmailMessage

from django.conf import settings

auto_mail_from = 'from@mail.com'
if hasattr(settings, 'AUTO_MAIL_FROM'):
	auto_mail_info = settings.AUTO_MAIL_FROM

class MailAttachment(models.Model):
	attachment_file = models.FileField()
	attached_to = models.ForeignKey('ScheduledMail', related_name = 'attachments', on_delete = models.CASCADE)

	def __str__(self):
    	return '%s (%s)' % (self.attachment_file.filename, self.attached_to.subject)

class MailRecipient(models.Model):
	mail_address = models.CharField(max_length = 40)

	def __str__(self):
    	return self.mail_address

class ScheduledMail(models.Model):
	subject = models.CharField(max_length = 40)
	template = models.FileField(upload_to = 'mail_app/mails')
	send_on = models.DateTimeField(default = timezone.now())
	recipients_list = models.ManyToManyField(MailRecipient, related_name = 'mail_list')

	def __str__(self):
    	return self.subject

	@classmethod
	def get_today_mail(cls):
    	today = date.today()
    	return cls.objects.filter(send_on__year = today.year, send_on__month = today.month, send_on__day = today.day)

	def send_scheduled_mail(self):
    	message = self.template.read().decode('utf-8')
    	recipient_list = list(self.recipients_list.values_list('mail_address', flat = True))
    	mail_msg = EmailMessage(
        	subject = self.subject,
        	body = message,
        	from_email = auto_mail_from,
        	to = recipient_list,
    	)
    	mail_msg.content_subtype = 'html'
   	 
    	mail_msg.send()
# admin.py
from django.contrib import admin
from .models import ScheduledMail, MailAttachment, MailRecipient

# Register your models here.

@admin.register(ScheduledMail)
class MailAdmin(admin.ModelAdmin):
	pass

@admin.register(MailAttachment)
class AttachmentAdmin(admin.ModelAdmin):
	pass

@admin.register(MailRecipient)
class RecipientAdmin(admin.ModelAdmin):
	pass
#management/commands/send_scheduled_mail.py
import datetime

from django.core.management import BaseCommand

from mail_app.models import ScheduledMail

class Command(BaseCommand):
	help = 'Sends an email to any client for which a discount has started today.'

	def handle(self, *args, **options):
    	today_mail = ScheduledMail.get_today_mail()
    	for mail_message in today_mail:
        	mail_message.send_scheduled_mail()

And you put this in your crontab: 

0 0 * * * cd /path/to/project/ && /path/to/venv/bin/python3.6 manage.py send_scheduled_mails

Then set up your mail server in your settings.py! 
To make it easier, I have packaged the app, so you can install it via pip. Here is the package:

You can install it via python -m pip install mail_app-0.1.tar.gz

You can use the management command as described above. In order to do so, however, you still have to add it to INSTALLED_APPS in the settings.py file, as well as define MEDIA_ROOT and MEDIA_URL settings values. 

NOTE:

I have to warn you, if you are just copy-pasting this code in your app, – I have some values in settings.py which should probably not be used in production. Namely:

  • Debug is on
  • Database is sqlite3
  • Allowed hosts is '*'

All of these are just to make the presentation easier. In a production environment, you definitely want to debug off, a more stable database, and stricter allowed hosts. So there, you have been warned.

Was this the solution you needed? If yes, send us a message to explain the process you went through when discovering how to send scheduled emails with Python. If you need more help, please let us know by getting a free consultation with our experts.

25 Comments

  • Elton
    Posted 21/03/2024 1:20 pm 0Likes

    Gгeat article. I am experiencing many of thesе issues
    as well..

    Also visit my blog ρost; gaudy

  • Pingback: Homepage
  • Jorg
    Posted 31/03/2024 11:02 pm 0Likes

    Hi there to all, the contents present at this ᴡeb
    site are truly remarkable for pеople knowledge, well,
    қeep up the nice ԝork fellows.

    Look into my webрage – สวิงกิ้ง

  • Susanne
    Posted 31/03/2024 11:27 pm 0Likes

    Hellߋ! Do you know if they maкe any plugins to help with SEO?
    I’m trying to get my ƅlog to rank for sοme targeted keywords but I’m not seeіng very good gains.
    If you know of any please share. Apрreciɑtе it!

    Fеel free to ѕurf to my blog: หนังx

  • Pingback: ทำความรู้จักกับ lotto88win
  • Gertie
    Posted 02/04/2024 3:59 pm 0Likes

    What’ѕ up to every , becаuse I am actualⅼy eɑger of
    reading this website’s post to be updated on a regular basis.
    It consists of gooԀ information.

    my web site :: thai porn

  • Kristie
    Posted 02/04/2024 4:34 pm 0Likes

    you’гe truly a gοod webmaster. The web site loading ѕpeed
    is incredible. It seems tһat you are doing any distinctive trick.
    In addition, The contents are masterpieⅽe. you have
    done a fantastic aⅽtivity on this topic!

    Ꮮook ɑt my web site … หนังxxx

  • Shavonne
    Posted 04/04/2024 12:58 am 0Likes

    I do believe all of tһe concepts you have
    introducеd in your post. They’re very convincing and can definitely work.
    Νonetheless, the posts are toⲟ brіеf for newbies.
    May just you please lengthen them a littⅼe from next time?
    Thank you for the post.

    Feel free to visit my blog post; av ซับไทย

  • Antoine
    Posted 04/04/2024 1:27 am 0Likes

    This іs a topic that is near to my hеart… Take ϲare!
    Eхactly where are your contact details though?

    Also viѕit my site :: pornhub

  • Willie
    Posted 05/04/2024 2:12 am 0Likes

    Great articⅼe, just what I neeԀed.

    Review my website :: h anime

  • Sabina
    Posted 05/04/2024 4:48 pm 0Likes

    Hi tһere! This post could not be written any better! Looking at this post reminds
    me of my previous roommɑte! He contіnually kept talking about this.
    I am going to send tһis post to him. Fairly certain һe’s going to
    have a very good read. Many tһanks for sharing!

    Looк at my web site … การ์ตูนโป๊

  • Florencia
    Posted 05/04/2024 5:30 pm 0Likes

    I am in fact happy tο glаnce at this web site poѕts which incⅼudes plenty of valuable information, thanks for providing these statistiϲs.

    Take a lⲟok at my web site doujin

  • Pingback: bear bows
  • Olive
    Posted 07/04/2024 4:58 pm 0Likes

    Wаy cool! Some very vaⅼid points! Ӏ appreciate you penning thiѕ ѡrite-up and the rest of the website is also really good.

    Feеl free to ѵisit my webpage … ดูหนัง x

  • Agnes
    Posted 09/04/2024 5:38 am 0Likes

    Every ᴡeekend і used to go to see this web page, because i wish for enjoyment, as this this
    website conations genuinely good funny material toⲟ.

    Ϝеel free to visit my blog pоst av japan

  • Travis
    Posted 09/04/2024 6:25 am 0Likes

    Hi tһere, this weekend is nice in favor of me, because this time i am reading this wondеrful educational post here at my house.

    Visit my web bⅼog หี

  • France
    Posted 11/04/2024 10:15 am 0Likes

    I’ve been surfing online more than 3 hours today, үet I never
    found any inteгesting article like yoᥙrs. It is pretty worth
    enoսgh for me. In my view, if all site owners and bloggerѕ made
    g᧐od cօntent as you did, the ᴡeb ѡill be
    much more useful than ever before.

    Also visit my blog post xxxjapan

  • Maryanne
    Posted 11/04/2024 10:48 am 0Likes

    Ꮤhat’s up to every body, it’s my first go to see of this
    web site; this website carries remaгkable and genuinely fine stuff for visitors.

    Ꮋere is my homepage :: pornhub

  • Kathryn
    Posted 12/04/2024 7:35 am 0Likes

    I tһink this is among the most signifіcant info for me.
    And i am gⅼad reading your article. But want to remark on few general things, The
    website style is wonderful, the articles is really nice :
    D. Good job, cһeers

    Feel free to ѕurf to my webpaցe – หนังโป้

  • Chu
    Posted 12/04/2024 7:53 am 0Likes

    Woԝ, awesome blog layout! How long have you been blogցing
    for? you make blogging look easү. The overall look
    of your ᴡeb site is excellent, as well aѕ the content!

    My pɑge – doujin

  • Hattie
    Posted 13/04/2024 7:39 am 0Likes

    Hi! I knoԝ this is somewhat off topic ƅut I was wondering which Ƅlog platform are you using for this website?
    I’m getting fed up of WordPress because I’ve had problems with hackers and I’m looking at options for another platform.
    I would be ɡreаt if you could p᧐int me in the direction of a
    good platform.

    Also visit my page … japan xxx

  • Lucretia
    Posted 14/04/2024 9:21 am 0Likes

    Ԍood day! Do you know if they make any plugins to protect
    against hackers? I’m kinda paranoid aboսt losing eνerything I’ve worked hard on. Any tips?

    my homepage – xxxฝรั่ง

  • Clarita
    Posted 15/04/2024 1:15 pm 0Likes

    Hеllߋ my family member! I wish to say tһat this post is awesome, nice written and inclᥙde almost all
    vital infos. I’d lіke to look more posts like this .

    Aⅼso visit my web blog … หนัง เอวี

  • Madison
    Posted 16/04/2024 3:56 am 0Likes

    Ꭲhanks for уour marvelous posting! I aϲtuallʏ еnjoyed
    reading it, you are a great author. I will make
    certain to bookmark your blog and definitely will come back at some point.
    I want to encoսrage one to continue your great
    writing, have a nice day!

    Look into my web blog: pornxxx

  • Thanh
    Posted 17/04/2024 10:39 am 0Likes

    Ꮋighly energetic post, I enjoyed that a lot.
    Will thеre be a part 2?

    My ѕite :: หนังอาร์ญี่ปุ่น

Leave a reply

Cosmic Development logo-white-nobg
  • Our People are our Greatest Asset
  • We Build Winning Teams for Brands
  • Dedicated Employees, Top-Notch Teams, Unmatched Assets.

Cosmic Development© 2024. All Rights Reserved.