Introduction π
In the current digital era, virtually all online purchases, whether for goods or services, include an invoice. This document helps businesses retain records and customers keep track of their transactions in addition to serving as proof of purchase. Automating the creation of invoices can streamline processes for developers and enhance client satisfaction. In this post you will learn how to use Twilio SendGrid, Python ,Flask and Bootstrap to create a straightforward yet effective invoice generation application.
Tutorial Requirements π οΈπ
Before you start, there are a few prerequisites for this tutorial that need to be addressed.
- A free Twilio Sendgrid account. If you are new to Twilio Sendgrid, signup for a free account. Using free account you can send 100 emails per day
- Python 3.10 or latest version . If your machine does not have this version or latest python version you can download it from here
- ReportLab Toolkit. It is a free and open source python library for generating PDFs and graphics
- Bootstrap version 4.6. It is a free and open-source framework for building responsive websites. You will use it for building the invoice UI
Configure Your Twilio SendGrid Account π§π€
Before you start building your app, you will need a Twilio SendGrid API key to use the Twilio SendGrid API and send emails using your Flask application.
To get an API key, first log in to your Twilio SendGrid account using your credentials. Then, on the left side, navigate to Settings, select API Keys, and click on Create API Key.
Now, add your preferred API key name (in my case, I am using Flask-invoice-new, then choose Full Access under API Key Permissions, and finally, click on Create & View.
Now, you will see that your API key has been generated. Copy the key and store it in a secure place, as Twilio SendGrid will only allow you to view the key once for security reasons.
Now that your API key has been generated successfully, you need to verify your email with Twilio SendGrid, which you will use to send emails.
Navigate to Settings, select Sender Authentication, then click on the Verify a Single Sender button. Next, click on Create New Sender, fill in the required details, and click Create.
Twilio SendGrid will now send a verification email to this address with a link. Once you click on the link and complete the verification, youβre good to go.
Configure your Python Virtual Environment ππ
Now that you have generated your API keys in the previous step, you need to set up your Python virtual environment to create a Flask application and send emails using the Twilio SendGrid API.
Create a directory for your app and set up the Python virtual environment using the commands provided below. (For this tutorial, I am using the directory name 'Twilio-Sendgrid-Flask-invoice-app').
For unix or mac users
$ mkdir Twilio-Sendgrid-Flask-invoice-app
$ cd Twilio-Sendgrid-Flask-invoice-app
$ python3 -m venv venv
$source venv/bin/activate
(venv) $ _
For Windows users
mkdir Twilio-Sendgrid-Flask-invoice-app
cd Twilio-Sendgrid-Flask-invoice-app
python3 -m venv venv
venv\Scripts\activate
Once you have configured your Python virtual environment, install the required Python dependencies using the command below
pip install flask sendgrid reportlab python-dotenv
Create your Flask application ππ§Ύπ§
Create a file named βapp.pyβ in the root directory and add the starter code for creating a Flask application
import os
from flask import Flask, render_template, request, send_file
from reportlab.lib.pagesizes import letter
from reportlab.platypus import Table, TableStyle, Paragraph, SimpleDocTemplate
from reportlab.lib import colors
from reportlab.lib.units import inch
from reportlab.lib.styles import getSampleStyleSheet
from io import BytesIO
import base64
from datetime import datetime
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, Attachment, Disposition, FileContent, FileName, FileType
app = Flask(__name__)
API_KEY=os.environ.get('TWILIO_SENDGRID_API_KEY')
EMAIL_ADDRESS = os.environ.get('MY_VERIFIED_EMAIL_ADDRESS')
In the above code I have imported all the necessary modules required to create a Flask application and build the invoice generator app. For security reasons, I am using environment variables to store my Twilio SendGrid API key and my email address, which will be used to send PDF invoices to clients' email addresses
Now, create a file named '.env' in the root directory and store your Twilio SendGrid API key and your verified email address. For this tutorial, I am using the variable names TWILIO_SENDGRID_API_KEY and MY_VERIFIED_EMAIL_ADDRESS. Flask will automatically import these environment variables using the Python dotenv package.
TWILIO_SENDGRID_API_KEY="YOUR TWILIO SENDGRID API KEY "
MY_VERIFIED_EMAIL_ADDRESS="YOUR EMAIL ADDRESS"
Create the home page for your Flask application π π»
In Flask, you can render dynamic HTML content by combining HTML templates and data, a process called template rendering. This means that when you open a specific route in your Flask application, you can render a predefined HTML file there. For this tutorial, you will create an HTML file for the invoice UI so that whenever anyone opens the home route of your Flask application, they will see a beautiful invoice UI where they can enter invoice data.
Now, create a folder named templates in the root directory and add an index.html file inside it. In Flask you can render any HTML file present within this folder
Building the invoice form using bootstrap π§Ύβ¨
Inside the index.html file paste the code below
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Invoice Generator</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="card">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">Invoice Generator</h3>
</div>
<div class="card-body">
<form id="invoice-form" action="/generate-invoice" method="POST" onsubmit="alertSubmit()">
<div class="form-group">
<label for="client_name">Client Name:</label>
<input type="text" class="form-control" id="client_name" name="client_name" required>
</div>
<div class="form-group">
<label for="email">Client Email:</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<h4 class="mt-4">Invoice Items</h4>
<div id="items">
<div class="form-row align-items-center mb-3 item">
<div class="col-md-4">
<label>Description</label>
<input type="text" class="form-control" name="description[]" required>
</div>
<div class="col-md-2">
<label>Quantity</label>
<input type="number" class="form-control" name="quantity[]" oninput="calculateTotal()"
required>
</div>
<div class="col-md-2">
<label>Price</label>
<input type="number" step="0.01" class="form-control" name="price[]"
oninput="calculateTotal()" required>
</div>
<div class="col-md-2">
<button type="button" class="btn btn-danger btn-block mt-4"
onclick="removeItem(this)">Remove</button>
</div>
</div>
</div>
<button type="button" class="btn btn-secondary" onclick="addItem()">Add Another Item</button>
<div class="form-group mt-4">
<label for="total_amount">Total Amount:</label>
<input type="number" step="0.01" class="form-control" id="total_amount" name="amount" readonly>
</div>
<button type="submit" class="btn btn-primary btn-block">Generate and Send Invoice</button>
<button onclick="resetForm()" class="btn btn-danger btn-block">Reset Invoice Form </button>
</form>
</div>
</div>
</div>
</body>
</html>
For this tutorial, I am using Bootstrap version 4.5, so make sure to add the correct CDN link for this Bootstrap version; otherwise, some features may not work properly.
In the code above, I have created a form with a simple UI that submits data to the /generate-invoice
route. As of now, it is just a basic HTML form without any actions, as I haven't added functionalities to the buttons yet and for that JavaScript is needed
Building the functionalities for buttons πβοΈ
To add functionalities to the buttons, include additional invoice fields, and calculate the total invoice amount, you will need to use JavaScript. Paste the JavaScript code just above the ending <div>
tag to add functionalities to the form.
<script>
function alertSubmit() {
alert("Email Sent Successfully");
}
function resetForm() {
document.getElementById("invoice-form").reset();
}
// Function to add a new item row
function addItem() {
const itemDiv = document.createElement('div');
itemDiv.className = 'form-row align-items-center mb-3 item';
itemDiv.innerHTML = `
<div class="col-md-4">
<label>Description</label>
<input type="text" class="form-control" name="description" required>
</div>
<div class="col-md-2">
<label>Quantity</label>
<input type="number" class="form-control" name="quantity" oninput="calculateTotal()" required>
</div>
<div class="col-md-2">
<label>Price</label>
<input type="number" step="0.01" class="form-control" name="price" oninput="calculateTotal()" required>
</div>
<div class="col-md-2">
<button type="button" class="btn btn-danger btn-block mt-4" onclick="removeItem(this)">Remove</button>
</div>
`;
document.getElementById('items').appendChild(itemDiv);
calculateTotal();
}
// Function to calculate total amount
function calculateTotal() {
let total = 0;
const quantities = document.querySelectorAll('input[name="quantity"]');
const prices = document.querySelectorAll('input[name="price"]');
quantities.forEach((quantity, index) => {
const price = prices[index];
const qty = parseFloat(quantity.value) || 0;
const prc = parseFloat(price.value) || 0;
total += qty * prc;
});
// Update the total amount field
document.getElementById('total_amount').value = total.toFixed(2);
}
// Function to remove an item row
function removeItem(button) {
const itemDiv = button.parentNode.parentNode;
document.getElementById('items').removeChild(itemDiv);
calculateTotal();
}
</script>
In the above code
-
alertSubmit()
- Displays an alert confirming that the email was sent successfully. -
resetForm()
- Resets the values in the invoice form. -
addItem()
- Adds new form fields to include more items in the invoice form. -
calculateTotal()
- Automatically updates the Total Amount field by calculating the total whenever a new item is added to the invoice. -
removeItem()
- Removes a specific item from the invoice form.
Write the Python code that would generate invoices in PDF format and send emails.
Once your UI for the invoice generator app is ready, itβs time to write Python functions for the backend.
In this part of the tutorial, you will create the /generate-invoice
route to handle the POST request and the send_invoice_via_email()
function to send emails using the free Twilio SendGrid API.
Letβs code the genenate_invoice function π»π§Ύ
Open the app.py file and write a function to fetch data from the form and generate PDF invoices.
@app.route('/generate-invoice', methods=['POST'])
def generate_invoice():
data = request.form
client_name = data.get('client_name')
email = data.get('email')
total_amount = data.get('amount')
# Retrieve item details
descriptions = data.getlist('description')
quantities = data.getlist('quantity')
prices = data.getlist('price')
# Generate PDF using ReportLab
pdf_buffer = BytesIO()
pdf = SimpleDocTemplate(pdf_buffer, pagesize=letter)
# Styles and layout
styles = getSampleStyleSheet()
title_style = styles["Title"]
normal_style = styles["Normal"]
elements = []
# Invoice Title
elements.append(Paragraph("INVOICE", title_style))
# Client and date details
elements.append(Paragraph(f"Date: {datetime.now().strftime('%Y-%m-%d')}", normal_style))
elements.append(Paragraph(f"Client: {client_name}", normal_style))
elements.append(Paragraph(" ", normal_style)) # Add space before the table
# Table for invoice items
items = [["Description", "Quantity", "Price", "Total"]]
for description, quantity, price in zip(descriptions, quantities, prices):
total = float(quantity) * float(price)
items.append([description, quantity, f"${float(price):.2f}", f"${total:.2f}"])
# Adding the Total row with only two columns
items.append(["", "", "Total", f"${float(total_amount):.2f}"])
# Table style
table = Table(items, colWidths=[3 * inch, inch, inch, inch])
table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -2), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.black),
('SPAN', (0, -1), (2, -1)), # Span the first three cells in the last row
('ALIGN', (2, -1), (3, -1), 'CENTER'),
('FONTNAME', (2, -1), (3, -1), 'Helvetica-Bold'),
]))
elements.append(table)
# Adding space and final message
elements.append(Paragraph(" ", normal_style)) # Add space after the table
elements.append(Paragraph("Thank you for your business!", title_style))
# Build PDF
pdf.build(elements)
# Rewind the buffer
pdf_buffer.seek(0)
# Send the PDF via email
try:
if email:
if(send_invoice_via_email(pdf_buffer, email)):
return send_file(pdf_buffer, as_attachment=True, download_name="invoice.pdf", mimetype='application/pdf')
except Exception as e:
return f"Error sending email: {e}"
In the above code the important points are as follows:
- I created a new route,
/generate-invoice
, of type POST, meaning this route will only handle POST requests. - I am using the
request.form
method in Flask to process data coming from the invoice form and store the values in respective variables. - I am using the
getlist()
method to retrieve multiple data entries using a single key. For this tutorial, there can be multiple invoice items with the same key. Usinggetlist()
allows me to store item descriptions, quantities, and prices in separate lists. - I am using the
BytesIO
object from Python'sio
module and theSimpleDocTemplate
function from ReportLab to set up a PDF template for the invoice. Note that thisBytesIO
class will be used to write invoice data into the PDF file. - I am using
getSampleStyleSheet()
from ReportLab to add some styles to the invoice data. - I am using Python lists and
zip
to organize the invoice items and calculate the net total amount for each item by multiplying the quantity by the price to get the final amount. - I am using the
Table
object from ReportLab to style the invoice table. Once all data processing and arrangement are complete, I use theappend
method to add each invoice element to the item list, and thebuild()
method from ReportLab to generate the invoice as a PDF document. - I am using
seek()
to reset the file handler pointer to 0. - Finally, I check if an email address is provided and, if so, whether the email is successfully sent using the
send_invoice_via_email(pdf_buffer, email)
function. Once all conditions are met, I use thesend_file()
function from Flask to send the invoice PDF for download. All these steps are wrapped intry
andexcept
blocks for exception handling.
Letβs code the send_invoice_via_email function π§β¨
As of now, the /generate-invoice
route is set up but not yet functional. This means that if you try running your Flask application now, it will show an error. The reason for this is that you still need to add the send_invoice_via_email()
function to handle sending emails. So, letβs create this function.
def send_invoice_via_email(pdf_buffer, recipient_email):
# Convert PDF to base64 for email attachment
pdf_data = base64.b64encode(pdf_buffer.getvalue()).decode()
# Create SendGrid Mail object
message = Mail(
from_email='kum9748ar@gmail.com', # Replace with your email
to_emails=recipient_email,
subject='Your Invoice',
html_content='<p>Please find attached your invoice.</p>'
)
# Create attachment for the email
attachment = Attachment(
FileContent(pdf_data),
FileName("invoice.pdf"),
FileType("application/pdf"),
Disposition("attachment")
)
message.attachment = attachment
# Send email using SendGrid
try:
sg = SendGridAPIClient(API_KEY)
response = sg.send(message)
print(f"Email sent! Status code: {response.status_code}")
return True
except Exception as e:
print(f"Error sending email: {e}")
return False
In the above function the important points are as follows:
- The function
send_invoice_via_email(pdf_buffer, recipient_email)
takes two arguments:pdf_buffer
, which is your generated invoice PDF, andrecipient_email
, which is the email address where the invoice will be sent to the client. - I am using the
base64
module to convert the PDF into an email attachment. - I am using the
Mail
object fromsendgrid.helpers.mail
to add essential details required to send an email via SendGrid, and theAttachment
object to include the generated PDF as an attachment with this email message. - Finally, I am using the
SendGridAPIClient()
object, which takes the SendGrid API key as an argument, to send the email. This portion of code is also wrapped withintry
andexcept
blocks for exception handling.
Run your application ππ»
To run your Flask application, you need to add a final piece of code.
if __name__ == "__main__":
app.run(debug=True)
The above code ensures that your app.py file will run as a script and not as a module when it is imported.
Now, open your terminal, navigate to your project directory (for this tutorial, it should be Twilio-Sendgrid-Flask-invoice-app), and use the command below to start your Flask application. Then, open your browser and go to http://127.0.0.1:5000, and you will see your fully functional invoice app running successfully
python app.py
The image above shows that my Flask application is working properly.
Now, letβs check the mailbox.
You can see that I have received an email via SendGrid containing my invoice attached as a PDF.
Some key points to remember for deployment ππ
Now that your Flask invoice generator app is ready, and youβre excited to share it with others as a live app, keep these key points in mind:
- Generate a requirements.txt file using
pip install -r requirements.txt
which contains a list of python packages required to run this app
The above image shows the requirements.txt
file for the Twilio-Sendgrid-Flask-invoice-app
- Make sure to add the
.env
file to your.gitignore
file so that nobody else can see your API keys. - Keep in mind that if you are using a Twilio SendGrid trial account, you can only send 100 emails per day. If you need to send more, I recommend upgrading your plan, as they offer a very flexible pricing structure.
- If you want to update the application UI, make sure to check out the documentation for Bootstrap version 4.6.2, which I have used for this tutorial. Otherwise, you may need to rewrite the entire UI code.
Conclusion π―β
So far in this tutorial, you have learned how to send emails using Twilio SendGrid, create a fully functional Flask app, work with Python's io module, render static HTML files, create PDFs in Python, and use Bootstrap and I hope you enjoyed it!
Check out Twilioβs developer docs and blogs to learn how to build interesting apps using various Twilio products. They also offer a $15 trial credit to new users that you can use for free.
The entire code for this tutorial is hosted on GitLab, and you can check it using this link.
Top comments (0)