Hey guys,
In this video, I will show you how you can send and schedule emails using Python. In particular, we will build a Payment reminder
tool. And here is how it works. I have here a Google Sheets document to keep
track of my invoices. So, we have here the customer's email, name,
invoice number, invoice amount, the payment due date and if the invoice has already been
paid or not. Once the due date has passed and the invoice
is still open, I want to send out a payment reminder email on the reminder date. And in this video, we will automate the whole
process. So, I will show you how to process the spreadsheet
data and send emails using Python. Once that is working, we will deploy our script
to the cloud and schedule it. For this example, I will run the script daily. So, every day Python will access the spreadsheet
and automatically send emails if the conditions are met. OK, and without further ado, let us get started. First things first, let us install the required
dependencies for this project. We only need two packages: pandas and python-dotenv. Therefore, spin up your terminal or command,
type "pip install pandas" and "python-dotenv", and hit enter. Now, before we jump into coding out the solution,
I will do some setup regarding our email credentials. Later, when sending out emails, we will need
to authenticate ourselves using our email address and password. Somewhere you need to store that information. A bad place would be your Python script, especially
if you plan to upload your project to the internet, for instance, to GitHub. It would be better to store that kind of information
in your environment variables. To do so, I will first create a "dot env"
file. Just make a standard text file and rename
it accordingly. In this file, I will write my email address
and password. So, this is the password I am using to login
into my account. Now, you want to avoid uploading this file
to GitHub. Therefore, we will create a gitignore file. Just head over to the following website. Here, type "Python" and click on the create
button. Copy the whole text to your clipboard and
create a new file called ".gitignore". In this new file, paste the text you just
copied. Here you can now see all the files and folders
git will ignore. If you scroll up a bit, you will see that
our ".env file" will also be ignored. Great, now that we have all the preparation
out of the way, let us get down to the actual coding part. To kick things off, let us see how to send
emails with Python. Therefore, I am creating a new python file
and naming it "send_email.py". Open this file with your preferred text editor. First, I will import the required modules. Os, smtplib, email and pathlib are all core
python modules. So, they are already included in your Python
installation. Next, we will also import load_dotenv from
dot env. After our imports, I will specify the port
and email server for outlook. And here is one important point which I want
to highlight. As you noticed already, I am using my outlook
email address. You might want to use a different email provider
like Gmail or iCloud. In that case, you would need to change the
email server and the port. Because those will be different depending
on which email provider you are using. Also, for Gmail, you must go through the 2-step
verification process. In this video, I cannot cover the setup for
all email providers. But as I said, you usually only need to change
the server address. You might also need to adjust the port if
you send emails with SSL. Ok, and with that said, let us now load our
environment variables, so the email address and password. Our ".env" file is located in the current
directory. To get the path to the current directory,
I am using the pathlib module. Now, this line might look convoluted. But it will just return the current folder
of my Python file. I have included the if statement so that it
will also work in Jupyter Notebooks. And with that, we can now use the load_dotenv
function with our file path. To retrieve the values, we can use os.getenv
followed by the name. So, here I am getting my email address and
password. Now that we have that let me create some more
space and define the actual function to send an email. Later, when sending out the emails, I would
include some variables to keep things dynamic. So, this function will take the subject, receiver
email, name, due date, invoice number and amount as input arguments. Creating an email is fairly easy. First, we will initialise an EmailMessage
object. We can then use this object to add our components
to it. So, we can add a subject, the sender and receiver
address and optionally, we could also specify the CC or BCC. You will notice that I have used "formataddress"
for the sender email. Adding this will give our email a nice touch. Instead of the pure email address, the receiver
will see the name we specify here. So, this could be your company name, for instance. After setting up those things, let us create
the email body. Let me first type out the entire message,
and then I will explain it. I am using here an f-string to include our
variables. So, later, we will replace the name, amount,
invoice number and due date. This is now our plain text version and will
be our backup solution. Because, nowadays, almost all email softwares
out there support HTML emails. That means we can also write an HTML version
of our email. This has the advantage of customising the
look of the text further. To add the HTML version, simply type "msg.add_alternative"
followed by the HTML text. As before, let me type out the message before
going over it. The text itself is the same. Yet, this time, I have included HTML tags. For example, I use the "strong" tag to highlight
the amount and due date to make the font bold. Additionally, I have also set the subtype
to HTML. Now that we also have our email text in place,
we are ready to send it. To do so, I am going to use a context manager. By the way, SMTP stands for Simple Mail Transfer
Protocol, and it is an application used by mail servers to send and receive emails. To interact with that server, we need to specify
the server address and the port number. Next, we will need to establish a connection. For this, I am using the protocol command
"STARTTLS". Once we have a connection, we can log into
our account before sending the message. Alright, and that is all there is to it. Before we test it out, I want to add one more
important "if-condition". You might have seen this boilerplate code
before. I will not go into the details on how it works,
but later in the video, I want to import this Python script as a module. When important other files, Python will execute
them. To avoid that we are running our script just
by important them, I have included this if condition. However, when we run the file directly, the
following lines will be executed. So, to test things out, I will now call your
"send email" function and provide the required parameters. After executing the script, we should now
have sent a new email to this address. To validate this, let me go back to the browser
and navigate to my Gmail inbox. And indeed, here we can see our email from
the coding is fun corporation. When I open the mail, we can also see that
the placeholders got replaced and that the amount and due date are displayed in bolt. Great, now that we know that is working, let
us move on to the next step. In the final application, I want to send emails
based on my spreadsheet. I have already prepared a simple google sheets
document. In the spreadsheet, you will notice that I
have included the variables we will need later on. So, we have here the email, name, invoice
number, amount and due date. Additionally, I have added two more columns. One for the reminder date and the information
if a customer has paid already. So, the idea is that we will send out the
emails on the reminder date if the invoice has not been paid yet. It depends, of course, on how you want to
set up things. But I thought this could be like a real-world
application, where you might want to give the customers a bit more time before sending
out an invoice reminder. Ok, now that we have all the information in
google sheets, we need to access it somehow with Python. And there are two possible routes you can
go – a secure and a not-so-secure one. We will use the less secure option by sharing
our spreadsheet with the public. But there are many tutorials here on YouTube
on accessing your spreadsheet using your credentials, which is, of course, more secure. Yet, it requires a bit more setup, and I wanted
to keep this tutorial as brief as possible. And with that said, let me share my spreadsheet
and change the general access permission down here. As mentioned, this is not secure, as anybody
on the internet with the link could view your spreadsheet, but regardless, I will confirm
my action by clicking Done. Ok, now that we have this place, let us go
back to our folder and create a new Python file. I will name this file "main.py". By the way, the naming is important here. Later, when we are deploying our python script,
the web server will look for a "main.py" file. With that said, let's open the file. Essentially, we want to use this script to
loop over our spreadsheet and send emails if our conditions are met. For this, we will need the built-in python
module DateTime. To work with the spreadsheet, I also import
pandas as pd and finally, we will use our local python module "send email". After our imports, let us define the URL to
our google sheets file. And here I will show a cool hack. Without much effort, we could convert your
google sheets document to a CSV file. In doing so, we can easily read it as a pandas
dataframe. To convert it to CSV, I will define the sheet
id, sheet name, and URL as variables. Within the URL, I have used an f-string to
insert our sheet id and name. You can find your ID in your browser. If you go back, you want to copy this part
of the URL to your clipboard. Back in the script, I will paste it right
here. And the sheet name was "sheet1". As I said, we can now load our spreadsheet
into a pandas dataframe. To do this, I will define a new function,
which will take our URL as an input parameter. As we are also dealing with dates, I will
tell pandas the column's name, which should be converted to a DateTime object. In my case, the column names are "due date"
and "reminder date". Next, we can use pd.read_csv followed by the
URL and the parse dates arguments before returning the dataframe. Ok, and before going further, let us test
it by printing out the dataframe. If I run the script, I can see the data down
here. Great, now that we have our dataframe, let
us use pandas to query the data. For this, I will yet create another function. We need to know today's date to check whether
we want to send out the reminder email. For this, I am using "date.today". Next, I will initialise an email counter variable
to keep track of all emails we have been sent while running the code. With that in place, I will iterate over our
dataframe. As mentioned, I only want to send an email
if the reminder date is today or has already passed. In addition, Python should only send emails
if the invoice has not been paid yet. If both criteria are met, we will trigger
our send_email function. As seen already, this function will take a
couple of arguments, which I can get from the current row, like the invoice number,
receiver email, name, due date and amount. For the due date, I converted the date-time
object to a human-readable output string, like the 11th of August, 2022. After sending the email, I will increase our
email counter. Once we have looped over the entire dataframe,
I will return a string with the total number of emails sent. Alright, and that is all there is to it. Like before, let me also test it out by converting
our spreadsheet into a pandas dataframe. Then, I will pass the dataframe to our function,
print out the result and execute our code. At the time of this recording, it is the 12th
of August. That means Python should have sent out two
emails. Let me validate this by navigating to my Gmail
inbox. And indeed, here I can see my new email. Python sent the other mail to a temporary
inbox. When I navigate to that site, we will also
see our invoice reminder from the coding is fun corporation. Great, this seems to be working. The neat thing is that I could now simply
adjust our spreadsheet to determine whether we should send an email or not. Let us say Kumar has paid the invoice. After updating the spreadsheet and rerunning
the script, only Sven should get an email. So, once again, I will navigate to my Gmail
inbox, and voila, we can see the email. For the final test, I will change the reminder
date to the 13th of August. So, with this setting, Python should not send
any email. And also, this works. After executing the script, I can see that
no email has been sent out. As this is all working, we are ready to move
on to the last step - the deployment on a server. Because ideally, we do not want to trigger
our Python script manually. Instead, we want to schedule it on a server
that will run our script daily. Before deploying it, we need to let the server
know which external libraries we need. For this, I will create a "requirements.txt"
file. Within this file, I will simply type pandas
and python-dotenv. Now, there are many cloud platforms available. My absolute favourite cloud provider is deta. And the cool thing is that deta is offering
their service for free. All you need to do is to head over to their
website at "deta.sh". On this website, go ahead and create a new
account. I already created one for myself. After you have set up your account, navigate
to the documentation page. Deta offers different services. We are interested in the cloud instance called
"Deta Micros". Navigate to that section and click on "Getting
started". Here you can find the detailed instructions
for your operating system. As I am on a Windows machine, let me switch
to Windows. The first thing you want to do is to download
and install the deta command line interface tool. The setup is super easy. Just copy this link and open up Powershell
on your computer. Before going further, let me shortly navigate
to our project folder by using "CD". Next, I will paste the command we just copied
and press enter. Once deta has been installed, you can use
"deta" commands in your terminal. So, we can now log in to our deta account
by running "deta login". This will open up the browser, and you should
be able to login it. If that works for you, we can now create our
first micro by going back to the documentation and copying the following command. You want to paste that command into your terminal. Ensure that you are in the correct folder. In my case, the Python file is located on
my Desktop in a folder called "Automate Emails". Feel free also to change the micro name. For instance, I will call mine email automation. After pressing enter, you should see a message
which looks like this. If that is the case, you should now have a
new folder in your directory. Here, you can find a deta folder and a "main.py"
file. I am only interested in the deta folder. So, let me cut out this folder and paste it
into our root directory. With that, I can delete the additional folder
deta has created. Ok, guys, we are almost done. We will just need a couple of more commands. First, let us check if we can see the micro
in our dashboard. By the way, I am using deta also for a couple
of other projects; that I why, you can see more micros here. But I can also see our email automation instance. In the visor tab, you can check if your script
is running as expected. By default, this view is disabled. You can enable it by running "deta visor enable"
in your terminal. Once that is done, let me refresh my dashboard. Ok, now we access the visor. So far, we have not deployed anything to our
instance yet. That is why you cannot see much here. And before deploying our script, we also need
to do two more important steps. The first one is in regards to our environment
variables. As you know, we will need our email address
and password in our script. This information is stored in the 'dot.env'
file and our environment variables. Also, in deta, you can set environment variables. To do so, you just need to run "deta update
-e" followed by the name of the file. After running this command, we securely transferred
the email address and password to our instance. Next, we also need to tell deta when to run
our script. We will do this by setting up a cron job. Back in the documentation, you can find a
boilerplate code if you scroll down a bit. We will need to implement this piece of code
in our "main.py" file. Therefore let me copy it to my clipboard and
open our python file. I will paste it at the very bottom of the
script. Within the cron job function, we will define
the action we want to perform. In our case, we want to first load our dataframe
before iterating over our dataset and sending out the emails. Once that is done, we will return the result. Lastly, I will move the import statement to
the other imports. By the way, deta is also a third-party package. Yet, this package is already installed on
our micro instance by default. So, that is why I have not included it in
our "requirements.txt" file. Alright, and with that, we are ready to deploy
our script. And this is very straightforward. Just type deta deploy and press enter. Running this command for the first time might
take some time, as deta needs to install the required packages. But once that is done, we can determine via
cron expression when to run our script. In the documentation, you can already find
some examples. For instance, you could run it daily at 10
am UTC. So, I recommend checking out this page to
set your cron job to your specific needs. For testing purposes, I will run the script
every minute. Therefore, let me copy the following line. Back in my terminal, I will paste it and hit
enter. If everything worked, we should see our return
message in the deta visor dashboard. And indeed, we can see that the script runs
successfully, but zero emails have been sent. And this is expected. Because if we go back to our spreadsheet,
none of our conditions is met. So therefore, let me change back the reminder
date to the 12 of August. So the day of this recording. Now, if we go back to our dashboard and wait
for around one minute, we will see that our script ran again. One email has been sent this time, and we
could also validate this by navigating to my Gmail inbox. And voila, here is our invoice reminder. For the final test, I will change "has_paid"
to no. So, this time we should see two emails. And indeed, if we take a look here, this is
also what our script returns. As before, we could also validate this by
checking the disposal and Gmail inbox. Ok, guys, and this is how you can automate
emails and schedule your scripts online for free. If you want to change the schedule, you could
simply overwrite the cron job, or in my case, I will remove it by running "deta cron remove". And remember that any changes you make in
your Python file locally will not be automatically deployed. So, if you later add more Python files or
change the current ones, open your terminal in the root folder, log in with "deta login",
and deploy your changes by running "deta deploy" one more time. Ok, guys, and that is all I have for you today. As always, thanks for watching and see you
in the next video.