Codementor Events

Sending emails with smtplib and flask

Published Mar 26, 2023Last updated Apr 11, 2023
Sending emails with smtplib and flask

Flask is one of my most loved Choice of Api, Because it's Fast and lightweight.But the real magic,is your server can get as complex as you need it to be, with several extensions and pluggins, Unlike django that comes all packed up with several functionalities you might not need.

we are making an smtp email sender with, Python smtp library. We do need a ui(user interface),a form in our case, for user to enter informations,a web ui with Html/css and jquery.Then we need to send the informations from the ui to our smtp script, we would use Flask to handle that.

You need to have python install on your machine, download from https://www.python.org/downloads/ or use a package manager like homebrew for mac os to install

you can create a project folder in desired directory and open it up in your code editor or use your terminal.
in desired directory

mkdir <project folder name> && cd  <project folder name> && touch app.py

I like to keep dependencies on my projects seperate, so am not installing globally
we can use pipenv to create an enviroment for the project and manage our dependencies within. If you have python installed, using python pakage installer. pip to install pipenv
if you are using python3 its pip3

pip3 install pipenv

that installs pipenv globally on our machine.
Now to start installing our project dependencies. lets create our project enviroment and install them

pipenv shell

spins up a new enviroment for your project
with pip file generated

pipenv install flask

create a folder and name it smtp
inside smtp create a file and name init.py
this will initialize the folder as module.
In this file, lets create the server , we need some libraries from python.
some are core project dependencies, smtplib to create SMTP client session object, to interact with the receiver email host smtp listener daemon.ssl(Secure Sockets Layer) for signature and security certificate. the updated ssl is called
Tsl (Transport Layer Security)

check for the security details in the received email full details below

Screenshot 2023-03-23 at 12.05.02 AM.png
I will be using reference to the received email full details picture above

import smtplib, ssl

We might want to send an html email rather than a text email, we see html email everyday from companies, bank, bussiness, so to make life easy and proper rendering of the html page with sender details,we would use python email helper library.

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formataddr

we define a function send_email that takes these parameters.
smtp_server : smtp sender host url
PORT : smtp sender host port
smtp_svr_usrn : user or admin username
smtp_svr_password:user or admin password
FROM: sender email(can appear as what you want depending on permission granted to the user or admin on the sender host)
TO: receiver email
REPLY_TO: when receiver clicks on reply where do you want the message to go, if not stated its defaultly sent to FROM (senders email)
msg: this contains the message, message subject

and a function formatter that formats the sender details properly.
the senders name and email address (what ever you want it to be,depending on permission granted as mensioned above).
the function takes the parameters;
name: senders name
email: senders email

import smtplib, ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formataddr


def send_email(smtp_server:str,
               smtp_svr_usrn:str,
               smtp_svr_password:str,
               FROM:str,
               TO:str,
               REPLY_TO:str,
               msg:dict[str ,str],
               PORT:int=587 
               )->dict[str,str]:

   message = MIMEMultipart("alternative")
   message["Subject"] = msg['subject'] 
   message["From"] = FROM
   message["To"] = TO
   message.add_header('reply-to', REPLY_TO)

   if msg["html"]:
      html_msg = MIMEText(msg["txt"], "html")
      message.attach(html_msg)
   elif not msg["html"]:
       plain_msg = MIMEText(msg["txt"], "plain")
       message.attach(plain_msg)

   context = ssl.create_default_context()
   with smtplib.SMTP(host=smtp_server,port=PORT,timeout=20) as server:
       server.starttls(context=context)
       server.login(smtp_svr_usrn, smtp_svr_password)
       try:
           server.sendmail(FROM,TO,message.as_string())
           return { "sent":True, "info":{ "to":TO, "from":FROM,"reply-			to":REPLY_TO }}
       except Exception as e:
           print(e)
           return { "sent":False, "info":{ "to":TO, "from":FROM,"reply-to":REPLY_TO }}




def formatter(name:str,email:str)->str:
   return formataddr((name,email))
   #return f'"{name}"<{email}>'

i commented out the second return in the formatter function because, it could be done both ways ,but the initial is better.It styles the resulting string properly with font weight(boldness)

in the root of your project create two folders and name as follows
static
templates
its neccesary since we are using flask to serve the html ui.
The html files goes to the templates from where, flask can serve it to the browser and all assets such as js script,image goes to the static folder which is made available in the browser by flask

my project looks this way
Screenshot 2023-03-23 at 1.26.26 AM.png

in your api.py lets get flask running.

we need flask as core dependency, renderer to render the html file for the ui, and request to interact with the ui, our defined functions to send email

we need just one route,which can be the 1st route in our app.
If the user send a get request,we send them the ui page and if they send a post with the right informations,we call the send_email function and return to them a feedback, if it's successful or not.

from flask import Flask, render_template,request 
from smtp import send_email, formatter


app = Flask(__name__)

@app.route("/",methods=["GET","POST"])
def smtp_exl():
    if request.method == 'POST':
        print(request.form)
        FROM = formatter(request.form.get('frm_name'),request.form.get('frm_email'))
        res = send_email(
                     request.form.get('smtp_svr'),
                     request.form.get('smtp_svr_usrn'),
                      request.form.get('smtp_svr_password'),
                      FROM,
                       request.form.get('to'),
                       request.form.get('reply_to'),
                       {
                        "html":bool(int(request.form.get('html'))),
                        "subject":request.form.get("subject"),
                        "txt":request.form.get("txt"),
                         },
                         request.form.get('port'),
                         )
        return res

    
    return render_template('index.html')


if __name__ == '__main__':
    app.run()
  

in your template folder lets add a new file index.html.
its a basic html form, with jquery cdn script,
then with a link to index.js and utils.js.
which control the input field and validation respectively.
We would create the js file in a minute

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
    <script src="https://code.jquery.com/jquery-3.5.1.js"
    integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc="
    crossorigin="anonymous"></script>
    
    <title>DEMON FURRY E-SENDER</title>
</head>
<body>
    <div class="content">
        <h1 class="heading">
            DEMON FURRY E-SENDER
        </h1>
        <form class="form" action="" method="post">
            <div class="smtp_svr">
                <input type="text"  placeholder="SMTP SERVER " name="smtp_svr" id="smtp_svr">
                <input type="text"  placeholder="PORT" name="port" id="port">
                <input type="text"  placeholder="SMTP SERVER USERNAME OR EMAIL " name="smtp_svr_usrn" id="smtp_svr_usrn">
                <input type="password"  placeholder="SMTP SERVER PASSWORD " name="smtp_svr_password" id="smtp_svr_password">
            </div>
            <div class="from">
                <input type="text"  placeholder="FROM NAME" name="frm_name" id="frm_name">
             <input type="text"  placeholder="FROM EMAIL" name="frm_email" id="frm_email">

            </div>
            
            <div class="to">
                <input  type="text" placeholder="TO " name="to" id="to">
                <input type="file" placeholder="upload targets emails text file " id="to_file" name="myfile">
            </div>
            <input  type="text" placeholder="REPLY TO " name="reply_to" id="reply_to">
            <input type="text" placeholder="SUBJECT" name="subject" id="subject">
            <div class="txt_type">
                <select  name="" id="html">
                    <option value="1">HTML MAIL</option>
                    <option value="0">TEXT MAIL</option>
                </select>
            </div>
            <textarea value="" name="txt" placeholder="TEXT" id="txt" cols="30" rows="10"></textarea>
            <div class="button">
                <button id="send" type="submit">SEND MAIL</button>
            </div>
        </form>

        <div class="feedback">
            <div class="feedback_console" id="feedback_console">
                feed back **************
            </div>
           
        </div>
        
    </div>
    <script src="{{ url_for('static', filename='utils.js') }}"></script>
    <script src="{{ url_for('static', filename='index.js') }}"></script>
</body>
</html>

in the static folder
add the index.js.
Using jquery,index.js simply disable the send button after been clicked,
picks all the input fields entered informations and create a javascript object, sends it to a validator in utils.js.
If all info checks out, it sends it to flask server or send feedback for bad information

$('document').ready( function(){
        $("form.form").submit(function(event){
            $("#feedback_console").text("")
            event.preventDefault()
            $("#send").attr("disabled", true);
            const msg = {}
            msg.smtp_svr=$("#smtp_svr").val()
            msg.port=$("#port").val()
            msg.smtp_svr_usrn=$("#smtp_svr_usrn").val()
            msg.smtp_svr_password=$("#smtp_svr_password").val()
            msg.frm_name=$("#frm_name").val()
            msg.frm_email=$("#frm_email").val()
            msg.to=$("#to").val()
            msg.reply_to=$("#reply_to").val()
            //msg.to_file=$("#to_file").val()
            msg.subject=$("#subject").val()
            msg.html=$("#html").val()
            msg.txt=$("#txt").val()
            const {valid, error_msg}=validator(msg)
            if(valid){
                $.ajax(
                    {
                        type:"POST",
                        url:"/",
                        data:msg,
                        success:function(data, status, xhttp){
                            console.log(data,status,xhttp)
                           $("#send").removeAttr("disabled");
                           $("#feedback_console").text(`sent from ${data.info.from} to ${data.info.to} =====> sent ${data.sent}`)
                        },
                        error:function(xhr, ajaxOptions, thrownError){
                            $("#send").removeAttr("disabled");
                            console.log(xhr, ajaxOptions, thrownError)
                        }, 
                        async: false
                     }
                    )
            }else{
               alert(error_msg)
                $("#send").removeAttr("disabled");
            }
        })
})

Create the utils.js in the static folder
and add the validation function in


const validator = (obj)=>{
    let error_msg, valid
    for (const [key, value] of Object.entries(obj)) {

        if(!value ){
            error_msg ="All Credential Fields Are Required"
            valid = false
            break    
        }
        if (key =="smtp_svr"){
           let pattern =/^[a-z0-9\._ % \^ @ ! # \$ \^ &]*\.[a-z0-9]{2,6}$/;
            valid = pattern.test(value)
            if(!valid){
                error_msg ="Not a Valid smtp Url Pattern"
                break
            }
        }else if(key =="smtp_svr_usrn"|| key=="frm_email"|| key=="to"){
            let pattern =/^[a-z0-9\._ % \^ ! # \$ \^ &]*@[a-z0-9 % \^ ! # \$ \^ &]{2,18}\.[a-z0-9]{2,6}$/;
            const valid = pattern.test(value)
            if(!valid){
                error_msg ="Not a Valid Email Format"
                break
            }
        }
    }
    return {valid, error_msg}
}

in the static folder lets add index.css to make our app presentable,
its already linked in the index.html head

 <link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">

just a couple of basic styling and for simplicity,i didnt add the file upload for buck emails spam in the app.



@import url('https://fonts.googleapis.com/css?family=Merriweather');
body{
    border: 0;
    margin: 0, auto;
    width: 100%;
    min-height: 100vh;
    font-family: 'Merriweather';
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: 	#FFFAFA;
    padding-top: 6vh;
    padding-bottom: 5vh;
}
.content{
    width: 50%;
    padding-left: 5%;
    border: none;
    padding-right: 5%;
    height: 90%;
    background-color: rgb(185,153,108)	;
    box-shadow: 10px 10px 8px #888888;
}
.heading{
    color: #FFF;
    width: 100%;
    margin: 0, auto;
    text-align: center;
    padding:  2vh 0;
    font-weight: bolder;
}
#send:disabled{
    cursor: not-allowed;
}
input{
    margin-bottom: 3%;
    width: 90%;
    height: 5vh;
    padding: 1%;
    border: none;
    background-color: rgba(210,189,160,0.9);
    color: #231503;
    font-size: 1.2em;
}
textarea{
    padding: 1%;
    border: none;
    height: 50vh;
    background-color: rgba(210,189,160,0.9);
    width: 90%;
 
}
select{
    background-color: rgba(210,189,160,0.9);
    font-weight: bold;
    font-family: inherit;
    padding: 2%;
    margin-bottom: 3%;
    border: none;
    color: #dfdfdf;
}
.to,.from{
    display: flex;
    max-width: inherit;
}
#to,#frm_name{
    margin-right: 2%;
}
.button{
    width: 100%;
    margin: 0 auto;
    text-align: center;
}
button{ 
    padding: 3%;
    margin:4%;
    background-color:rgb(119,93,57);
    color: #FFFAFA;
    font-weight: bolder;
    font-size: 1.2em;
    border: none;
    box-shadow: 10px 10px 8px rgb(119,93,57);
}

button:focus{
    box-shadow: none;
    transition:all 1;
}
.feedback{
    width: 100%;
    margin: 0 auto;
    text-align: start;
}
.feedback_console{
    width: 90%;
    background-color: rgba(210,189,160,0.9);
    margin-bottom: 4%;
    font-size: 0.8em;
    color: #FFFAFA;
    padding: 5%;
}
select:hover{
    cursor: pointer;
}
button:hover{
   cursor: pointer;
   background-color: rgba(119,93,57,0.5);
}
select:focus{
    outline: none;
}
input:focus, textarea:focus{
    outline: none;
    box-shadow: 12px 12px 10px rgb(119,93,57);
    transition:all 0.3s ;
}
::placeholder{
    color: rgba(86,86,86,0.4);
    font-weight: bold;
}
::-webkit-file-upload-button {
    background-color: rgba(210,189,160,0.9);
    border: none;
    color: rgba(86,86,86,0.4);
  }
  ::-webkit-file-upload-button:hover,input[type="file"]{
    cursor: pointer;
  } 

now, we are all set, we can call flask directly to start our server

flask --app api.py --debug  run

or simply with your python ,am having python3 installed

python3 api.py

Screenshot 2023-03-23 at 5.48.53 AM.png

thats is my complete code
and my app looks like this, am not able to snipp the complete look
you can test with free email services such as outlook ,gmail etc

Screenshot 2023-03-23 at 5.52.37 AM.png

this is outlook smtp url, port
create a free email for user login

Screenshot 2023-03-23 at 5.56.46 AM.png

Discover and read more posts from Aremu Mohammad Abiodun
get started
post commentsBe the first to share your opinion
Aremu Mohammad Abiodun
a year ago

Give a heart if it was helpful

Show more replies