How to send an email using five Python full-stack frameworks

A deep look into five of the most important Python Full-Stack Frameworks: NiceGUI, Solara, PyWebIO and Taipy and how to send an email.

How to send an email using five Python full-stack frameworks

Python full-stack frameworks are becoming a critical part of today’s development, as they allow developers to create backend and frontend applications without needing the knowledge of JavaScript, CSS and so on, so we will develop an application to send an email using four of the most popular frameworks.

Is your system ready?

Continue with the blog if you already have the Nylas Python SDK installed and your Python environment configured. Also, you can check the Nylas Quickstart Guides.

Otherwise, I recommend reading the post How to Send Emails with the Nylas Python SDK, which covers the basic setup. I will teach you how to send an email although without using a full-stack framework.

What are we going to talk about?

What our Email Sender application will look like

Let’s design how our application will look like. We will need a combo box for the recipient, three text inputs for the name, email and subject, a textarea for the body and a submit button. The combo box will get the contacts list using the Nylas Contacts API. Once we select a contact, its name and email will be populated into the name and email fields, although we can specify the name and email manually. When building this with a full-stack framework, we will be able to send an email with almost no effort.

Application mockup

Introduction to NiceGUI

NiceGUI first version was released on May 14, 2021, and it’s currently on version 1.3.4.

It uses Vue and Quasar for the frontend, and it’s built on top of FastAPI.

The learning curve is gentle, and it’s overall user-friendly for beginners. The documentation is complete and useful.

NiceGUI logo

Working with NiceGUI

Once installed, we’re ready to go. Let’s create a file called email_sender.py with the following code:

# Import your dependencies
from dotenv import load_dotenv
import os
from nylas import APIClient  # type: ignore
from nicegui import ui

# Load your env variables
load_dotenv()

# Initialize an instance of the Nylas SDK using the client credentials
nylas = APIClient(
    os.environ.get("CLIENT_ID"),
    os.environ.get("CLIENT_SECRET"),
    os.environ.get("ACCESS_TOKEN"),
)

# Dictionary to hold all email information
contact_info = {'recipient':'','name':'','email':'','subject':'', 'body':''}

# List to hold all contacts
contacts_list = []
emails_list = {}

# Function to send the email
def send_email():
    # Create the draft
    draft = nylas.drafts.create()
    # Assign subject, body and name and email of recipient
    draft.subject = contact_info['subject']
    draft.body = contact_info['body']
    draft.to = [{"name": f"{contact_info['name']}", 
                     "email": f"{contact_info['email']}"}]
    try:
        # Send the email
        draft.send()  
        ui.notify("Email was sent successfully")
    except Exception as e:
        ui.notify(f'An error ocurred: {e}')
    finally:
        # Clear all fields
        recipient.set_value("")
        name.set_value("")
        email.set_value("")
        subject.set_value("")
        body.set_value("")

# Update the dictionary with the updated values
def update_field(field, value):
    # If we choose a recipient
    if field == 'recipient' and value != '':
		# Get the name and email from the contact
        contact_info[f'{field}'] = value
        contact_info['name'] = field
        contact_info['email'] = emails_list[value]
        # Update the fields
        email.set_value(emails_list[value])
        name.set_value(value)
    else:
        # Update any other value
        contact_info[f'{field}'] = value

# Grab the first 5 contacts from the specified group
contacts = nylas.contacts.where(source = 'address_book', limit = 5, 
                                                 group = "517v55haghlcvnuu7lcm4f7k8")
# Loop all contacts and get the Id and Full Name
for contact in contacts:
    contacts_list.append(contact.given_name + " " + contact.surname)
    emails_list[f'{contact.given_name + " " + contact.surname}'] = list(contact.emails.values())[0][0]

# Main layout with everything centered
with ui.column().classes('w-full items-center'):
    # Set application title, list of contacts, name, email and email body
    ui.label('Email Sender').tailwind.font_weight('black').font_size('6xl').text_color('blue-700')
    recipient = ui.select(label = "Recipient", options=contacts_list, with_input=True, 
                                  on_change = lambda e: update_field('recipient', e.value)).classes('w-50')
    name = ui.input(label='Name', placeholder='Name', 
                                  on_change = lambda e: update_field('name', e.value)).classes('w-64')
    email = ui.input(label='Email', placeholder='Email', 
                                  on_change = lambda e: update_field('email', e.value)).classes('w-64')
    subject = ui.input(label='Subject', placeholder='Email Subject', 
                                  on_change = lambda e: update_field('subject', e.value)).classes('w-64')
    body = ui.textarea(label='Body', placeholder='Email Body', 
                                  on_change = lambda e: update_field('body', e.value)).classes('w-64')
    # Submit button
    ui.button('Send Email', on_click=send_email)

# Run our application
ui.run(title = 'Email Sender')

To run our application, we need to type the following on our terminal window:

$ python3 email_sender.py

The browser will open automatically:

NiceGUI email sender

Here we can fill in the details and send our email:

Filling up the NiceGUI application

After we hit send, we will get a notification at the bottom of the screen, letting us know if the email as sent or not:

Email confirmation

And we can check our mailbox just to confirm:

Email from NiceGUI

Introduction to Reflex

Reflex was initially called Pynecone. Its first version was released on November 24, 2022, and it’s currently on version 0.2.0

It uses React for the frontend and Bun as the JavaScript runtime.

The learning curve is not so gentle, but still easier to grasp.

It uses a State concept that can be a little complicated sometimes.

Reflex Logo

We can install it by typing the following command in our terminal window:

$ pip3 install reflex

Working with Reflex

We’re going to create a folder called reflex_send_email using the following command in the terminal:

$ mkdir reflex_send_email

Once we are inside the folder, we need to initialize our Reflex application:

$ reflex init

This will generate some files and also another folder called reflex_send_email. Inside this new folder, we will find a file called reflex_send_email.py, this is the file that we need to update:

# Import your dependencies
from rxconfig import config
import reflex as rx
from dotenv import load_dotenv
import os
from nylas import APIClient  # type: ignore
from typing import List

# Load the app configuration
filename = f"{config.app_name}/{config.app_name}.py"

# This class represents the State of the app
# and hold all the important functions
class State(rx.State):
# Variables to hold form information
    recipient : str = ""
    name : str = ""
    email : str = ""
    subject : str = ""
    body : str = ""
    message : str = ""

# We need methods to update the fields
    def set_name(self, name: str):
        self.name = name

    def set_email(self, email: str):
        self.email = email
        
    def set_body(self, body: str):
        self.body = body
        
    def set_subject(self, subject: str):
        self.subject = subject        

    def set_email_name(self, name : str):
        self.name = name
        self.email = emails_list[name]

# When we press submit, we want to get 
# all the form information
    def handle_submit(self, form_data: dict):
         # Create the draft
        draft = nylas.drafts.create()
        # Define the subject, body, name and email of recipient
        draft.subject = form_data['Subject']
        draft.body = form_data['Body']
        draft.to = [{"name": f"{form_data['Name']}", "email": f"{form_data['Email']}"}]
        try:
            # Send the email
            draft.send()
            self.message = "Email was sent successfully"
        except Exception as e:
            self.message = f'An error ocurred: {e}'
        finally:
            self.name = ""
            self.email = ""
            self.subject = ""
            self.body = ""
            self.recipient = ""    

# Load your env variables
load_dotenv()

# Initialize an instance of the Nylas SDK using the client credentials
nylas = APIClient(
    os.environ.get("CLIENT_ID"),
    os.environ.get("CLIENT_SECRET"),
    os.environ.get("ACCESS_TOKEN"),
)

# Get a list of contacts, from a particular group
# limiting to 5 contacts only
full_names: List[str] = []
emails_list = {}
# Call the contacts endpoint
contacts = nylas.contacts.where(source = 'address_book', limit = 5, group = "517v55haghlcvnuu7lcm4f7k8")
# Loop the contacts
for contact in contacts:
    # Append them to lists
    full_names.append(contact.given_name + " " + contact.surname)
    emails_list[f'{contact.given_name + " " + contact.surname}'] = list(contact.emails.values())[0][0]

# This section defines the UI
def index():
    return rx.center(
        rx.vstack(
            rx.form(
                rx.heading("Email Sender"),
                rx.text("Recipient"),
                rx.select(
                    full_names,
                    placeholder = " ",
                    value = State.recipient,
                    on_change = State.set_email_name,
                    id = "recipient"
                ),
               rx.text("Name"),
               rx.input(value = State.name, on_change = State.set_name, id="Name"),
               rx.text("Email"),
               rx.input(value = State.email, on_change = State.set_email, id="Email"),
               rx.text("Subject"),
               rx.input(value = State.subject, on_change = State.set_subject, id="Subject"),
               rx.text("Body"),
               rx.text_area(value = State.body, on_change = State.set_body, id="Body"),
               rx.button("Send Email", type_="submit", color_scheme="red", size="md"),         
               width="100%",
               on_submit=State.handle_submit,
            ),
            # This will work as our notification system
            rx.input(value = State.message, bg="#68D391"),
        ),
    )


# Add state and page to the app.
app = rx.App(state=State)
app.add_page(index)
app.compile()

To be able to run our application, we need to be in the root folder and type the following on the terminal:

$ reflex run

Our application will run on port 3000, so we need to open our favourite browser:

Starting the Reflex server

And start populating the fields:

Filling up the Reflex application

After we hit send, we will get a notification at the bottom of the screen, letting us know if the email as sent or not:

Email confirmation

And we can check our mailbox just to confirm:

Email from Reflex

Introduction to Solara

Solara first version was released on April 4, 2022, and it’s currently on version 1.18.0

It uses React for the frontend. It can be run on web apps or in Jupyter Notebooks.

The learning curve is somehow easy, although its documentation is not the best. I needed to dig into their Github repository more than once.

Still, a great user experience.

Solara logo

We can install it by typing the following command in our terminal window:

$ pip3 install solara

Working with Solara

Once installed, we’re ready to go. Let’s create a file called email_sender.py with the following code:

# Import your dependencies
import solara
from solara.alias import rv
from dotenv import load_dotenv
import os
from nylas import APIClient  # type: ignore

# Load your env variables
load_dotenv()

# Initialize an instance of the Nylas SDK using the client credentials
nylas = APIClient(
    os.environ.get("CLIENT_ID"),
    os.environ.get("CLIENT_SECRET"),
    os.environ.get("ACCESS_TOKEN"),
)

# List to hold all contacts
contacts_list = []
emails_list = {}
# Reactive components
recipient = solara.reactive("")
name = solara.reactive("")
email = solara.reactive("")
subject = solara.reactive("")
message = solara.reactive("")
name_txt = ""
email_txt = ""

# Grab the first 5 contacts from the specified group
contacts = nylas.contacts.where(source = 'address_book', limit = 5, group = "517v55haghlcvnuu7lcm4f7k8")
# Loop all contacts and get the Id and Full Name
for contact in contacts:
    contacts_list.append(contact.given_name + " " + contact.surname)
    emails_list[f'{contact.given_name + " " + contact.surname}'] = list(contact.emails.values())[0][0]

# Placeholder for the email body
markdown_initial = """

""".strip()

@solara.component
def Page():
    # When we select a contact, fill in the details
    def fill_details(foo):
        name.set(recipient.value)
        email.set(emails_list.get(recipient.value))

    def send_email():
        draft = nylas.drafts.create()
        draft.subject = str(subject).strip("'")
        draft.body = str(markdown_text).strip("'")
        name_txt = str(name).strip("'")
        email_txt = str(email).strip("'")
        draft.to = [{"name": f"{name_txt}", "email": f"{email_txt}"}]
        try:
            draft.send()
            message.set("Email was sent successfully")
        except Exception as e:
            message.set(f'An error ocurred: {e}')
        finally:
            name.set("")
            email.set("")
            subject.set("")
            recipient.set("")
            set_markdown_text("")
	
# Define the markdown to be used as the email body
    markdown_text, set_markdown_text = solara.use_state(markdown_initial)
# This section is for the UI
    with solara.Card("") as main:
        with solara.Column(gap="12px", align="center"):
            solara.Markdown(r'''# Email Sender''')
            solara.Select(label="Recipient", value = recipient, values=contacts_list, on_value=fill_details)
            solara.InputText(label="Name", value=name)
            solara.InputText(label="Email", value=email)
            solara.InputText(label="Subject", value=subject)
            rv.Textarea(label = "Body", v_model=markdown_text, on_v_model=set_markdown_text, rows=5)
            solara.Button(label="Send Email", on_click=send_email)
            # This will work as our notification system
            solara.Success(
                f"{message}",
                text=False,
                dense=True,
                outlined=True,
                icon=False,
        )
    return main

To be able to run our application, we need to be in the root folder and type the following on the terminal:

$ solara run email_sender.py

The browser will open automatically:

Solara email sender

Here we can fill in the details and send our email:

Filling up the Solara application

After we hit send, we will get a notification at the bottom of the screen, letting us know if the email as sent or not:

Email confirmation

And we can check our mailbox just to confirm:

Email from Solara

Introduction to PyWebIO

PyWebIO first version was released on April 30, 2020, and it’s currently on version 1.8.2

It seems it relies on Python, HTML and JS. So no JavaScript frameworks are involved.

The learning curve is somehow easy, although its documentation is not bad, not everything is that easy to find. However, a few web searches were more than enough to help me.

A surprisingly great experience.

Pywebio Logo

We can install it by typing the following command in our terminal window:

$ pip3 install pywebio

Working with PyWebIO

Once installed, we’re ready to go. Let’s create a file called pywebio_email_sender.py with the following code:

# Import your dependencies
from dotenv import load_dotenv
import os
from nylas import APIClient  # type: ignore
from pywebio.input import *
from pywebio.output import *
from pywebio import start_server

# Load your env variables
load_dotenv()

# Initialize an instance of the Nylas SDK using the client credentials
nylas = APIClient(
    os.environ.get("CLIENT_ID"),
    os.environ.get("CLIENT_SECRET"),
    os.environ.get("ACCESS_TOKEN"),
)

# List to hold all contacts
contacts_list = []
emails_list = {}

# Grab the first 5 contacts from the specified group
contacts = nylas.contacts.where(source = 'address_book', limit = 5, group = "517v55haghlcvnuu7lcm4f7k8")
# Loop all contacts and get the Id and Full Name
contacts_list.append("")
emails_list[""] = ""
for contact in contacts:
    contacts_list.append(contact.given_name + " " + contact.surname)
    emails_list[f'{contact.given_name + " " + contact.surname}'] = list(contact.emails.values())[0][0]

# Update the name and email when selecting the combo box
def set_name(contact):
	input_update('email', value = emails_list[contact])
	input_update('name', value = contact)

# Our main function
def main():
	# Run while the application is running
    while True:
		# This is our UI
        data = input_group(
            "Email Sender",
            [
                select('Recipient', options = contacts_list, name="recipient", onchange=set_name),
                input("Name", name="name", type=TEXT),
                input("Email", name="email", type=TEXT),
                input("Subject", name="subject", type=TEXT),
                textarea('Body', name="body", rows=3, placeholder=''),
            ],
        )

        # This happens when we click on the "Submit" button
        draft = nylas.drafts.create()
        # Populate the email
        draft.subject = data["subject"]
        draft.body = data["body"]
        name_txt = data["name"]
        email_txt = data["email"]
        draft.to = [{"name": f"{name_txt}", "email": f"{email_txt}"}]
        try:
            # Send the email and get notified
            draft.send()
            toast('Email was sent successfully', position='right', color='#2188ff', duration=0)
        except Exception as e:
            toast(f'An error ocurred: {e}', position='right', color='#2188ff', duration=0)

if __name__ == '__main__':
    # Start the server
    start_server(main, port=8080, debug=True)

To be able to run our application, we need to be in the root folder and type the following on the terminal:

$ python3 pywebio_email_sender.py

We need to open our favourite browser on port 8080:

Pywebio email sender

Here we can fill in the details and send our email:

Filling up the Pywebio application

After we hit send, we will get a notification at the bottom of the screen, letting us know if the email as sent or not:

Email confirmation

And we can check our mailbox just to confirm:

Email from Pywebio

Introduction to Taipy

Taipy first version was released on April 7, 2022, and it’s currently on version 3.0.0

It uses mui.com, which is a set of React UI tools.

The learning curve is strange, and the documentation is not easy to navigate, although getting it to work is surprisingly fast.

A great experience and a way different way of working.

Taipy Logo

We can install it by typing the following command in our terminal window:

$ pip3 install taipy

Working with Taipy

Once installed, we’re ready to go. Let’s create a file called taipy_email_sender.py with the following code:

# Import your dependencies
from dotenv import load_dotenv
import os
from nylas import APIClient  # type: ignore
from taipy.gui import Gui , notify

# Load your env variables
load_dotenv()

# Initialize an instance of the Nylas SDK using the client credentials
nylas = APIClient(
    os.environ.get("CLIENT_ID"),
    os.environ.get("CLIENT_SECRET"),
    os.environ.get("ACCESS_TOKEN"),
)

# List to hold all contacts
contacts_list = []
emails_list = {}

# Grab the first 5 contacts from the specified group
contacts = nylas.contacts.where(source = 'address_book', limit = 5, 
                                                 group = "517v55haghlcvnuu7lcm4f7k8")
# Loop all contacts and get the Id and Full Name
for contact in contacts:
    contacts_list.append(contact.given_name + " " + contact.surname)
    emails_list[f'{contact.given_name + " " + contact.surname}'] = list(contact.emails.values())[0][0]

# Global variables for the state
name = ""
email = ""
subject = ""
body = ""

# Definition of the page
page = """

# Email Sender

Recipient
<|{contact}|selector|lov={contacts_list}|dropdown|> 

Name 

<|{name}|input|> 

Email 

<|{email}|input|> 

Subject 

<|{subject}|input|> 

Body 

<|{body}|input|multiline=true|> 

<|Send Email|button|on_action=on_button_action|> 
"""

# When we press the send button
def on_button_action(state):
    # Create the draft
    draft = nylas.drafts.create()
    # Assign subject, body and name and email of recipient
    draft.subject = state.subject
    draft.body = state.body
    draft.to = [{"name": f"{state.name}", 
                     "email": f"{state.email}"}]
    try:
        # Send the email
        notify(state, 'info', 'Email was sent successfully')        
        draft.send() 
    except Exception as e:
        notify(state, 'info', 'An error ocurred')   
    finally:
        # Clear all fields
        state.subject = ""
        state.body = ""
        state.name = ""
        state.email = ""
        state.contact = ""

# When we select the contact combo box
def on_change(state, var_name, var_value):
    if var_name == "contact":
        state.name = var_value
        state.email = emails_list[var_value]
        return

# Run the page
Gui(page).run()

In order to apply some CSS, we need to create a file with the same as our application, so taipy_email_sender.css:

:root {
    height: 200px;
    display: flex;
    align-items: center;
    justify-content: center;
}

To be able to run our application, we need to be in the root folder and type the following on the terminal:

 $ taipy run taipy_email_sender.py
Running the Taipy server

Our favourite browser will be opened automatically on port 5000:

Taipy email sender

Here we can fill in the details and send our email:

Filling up the Taipy application

After we hit send, we will get a notification at the bottom of the screen, letting us know if the email as sent or not:

Email confirmation

And we can check our mailbox just to confirm:

Email from Taipy

What’s next?

We have built the same email sender example using four different Python full-stack frameworks so that you can draw your conclusions on which one suits your project requirements. They all have good and bad things, but we cannot deny they are useful and help speed development.

Do you have any favourite full-stack framework that you use to send an email? Share it with us, we would love to see it and explore it.

To learn more about our Email APIs, visit our documentation Email API Overview.

You can sign up for Nylas for free and start building!

You May Also Like

Transactional Email APIs vs Contextual Email APIs
Best email tracker
Find the best email tracker and elevate your app’s email game
How to create and read Webhooks with PHP, Koyeb and Bruno

Subscribe for our updates

Please enter your email address and receive the latest updates.