.png)
Deploying Python automation scripts to the cloud can help you run scheduled tasks, process data automatically, send emails, generate invoices, and integrate with services like Google Sheets without relying on your local computer. In this tutorial, we will learn how to deploy a Python automation project to Render using GitHub integration, environment variables, and Render Cron Jobs. We will also cover how to securely manage API credentials, generate PDF invoices with ReportLab, and automate email delivery directly from the cloud.
Prerequisite:
This tutorial is part of the Automated Sales Invoice Series.
📚 View the Complete Automated Sales Invoice Series
Preliminary:- .gitignore—those files excluded from uploading to the GitHub repository,
- render.yaml—a deployment configuration to automatically set up and manage my application or automation service,
- requirements.txt—a list of all dependencies that will be installed during the Render deployment,
- worker.py—the main execution script that acts like the controller or engine for your automation system on Render,
The API key used to connect to Google services, such as reading Google Sheets data and sending Gmail, is stored in a .env file on my local computer. However, it will be included in the .gitignore file and does not need to be uploaded to the GitHub repository to prevent third parties from misusing the API key. Once the deployment is completed in the Render Cloud, I will provide an API key in the environment of the Render.
def get_gsheet_data():
secret_file_path = "google_credentials.json"
gc = pygsheets.authorize(service_account_file=secret_file_path)
Meanwhile, my pdf.py contains four functions, and my amendment is as follows:
from email_util import send_email_with_attachment, SMTP_USER
import os
from gsheet import get_gsheet_data
# Register Roboto fonts
# --- AFTER (FIXED) ---
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# 1. Register Regular Variant
font_regular = os.path.join(BASE_DIR, "media", "Roboto-Regular.ttf")
pdfmetrics.registerFont(TTFont("Roboto", font_regular))
# 2. Register Bold Variant
font_bold = os.path.join(BASE_DIR, "media", "Roboto-Bold.ttf")
pdfmetrics.registerFont(TTFont("Roboto-Bold", font_bold))
logo_path = os.path.join(BASE_DIR, "media", "my_logo.png")
def generate_and_send_invoice():
invoice_data = get_gsheet_data()
if not invoice_data:
return
for entry in invoice_data:
customer = entry["customer_name"]
email = entry["customer_email"]
if not customer or not email:
continue
pdf_filename = f"{customer.replace(' ', '_')}_invoice.pdf"
c = canvas.Canvas(pdf_filename, pagesize=letter)
# IMPORTANT FIX
single_invoice_data = [entry["full_row"]]
c, positions = draw_invoice_template(
c,
single_invoice_data)
c = fill_customer_info(
c,
single_invoice_data,
positions)
c = fill_invoice_data(
c,
single_invoice_data,
positions)
c = stripe_payment(c)
c.save()
# Dispatch the email attachment instantly
send_email_with_attachment(
from_addr=SMTP_USER,
to_addrs={email},
subject="Sales Invoice from How App and Web",
body=f"""Dear {customer},
Please find attached your sales invoice for your recent purchase
with How App and Web. If you have any questions or need further
assistance, feel free to reply to this email.
Thank you for your business!
Best regards,
How App and Web Team""",
attachment_path=pdf_filename
))
def draw_invoice_template(c, invoice_data):
### code remains the same except below ###
# =============================
# Invoice Date from row[0]
# =============================
#### Changes ####
if invoice_data and "full_row" in invoice_data[0]:
first_row_raw = invoice_data[0]["full_row"]
if len(first_row_raw) > 0:
#### Changes ####
raw_date = str(first_row_raw[0]) # index 0 is your date timestamp
clean_date = raw_date.split(" ")[0]
c.setFillColorRGB(0,0,1)
c.drawString(5.6*inch, 8.5*inch, clean_date)
def fill_customer_info(c, invoice_data, positions):
### code remains the same ###
def fill_invoice_data(c, invoice_data, positions):
### code remains the same ###
__pycache__/
*.pyc
*.db
*.pdf
gsheet.json
database.py
gsheet_to_sql.py
The .db, database.py, and gsheet_to_sql.py would be backup maintenance for data in Google Sheets; they have nothing to do with generating and emailing sales invoices. While gsheet.json is a secret API key, it will never be uploaded. services:
- type: cron
name: invoice-worker
runtime: python
schedule: "*/5 * * * *"
buildCommand: "pip install -r requirements.txt"
startCommand: "python worker.py"
The service is a cron job and a Python runtime, and the run interval is every 5 seconds. Install the dependencies according to the requirements.txt, and the execution command starts from the "Python worker.py" file. python-dotenv
google-auth
pillow
pygsheets
reportlab
requests
oauth2clientimport dotenv
# Load variables into environment definitions
dotenv.load_dotenv()
# Import the centralized worker controller function directly
from pdf import generate_and_send_invoice
if __name__ == "__main__":
print("Starting Cron Job Engine...")
generate_and_send_invoice()
print("Cron Job Processing Completed Successfully.")Now I will sign up for GitHub; I have the choice to sign up with Google, Apple, or manually. When I log in to the account, I click on the top-right corner "+" button. In a dropdown menu, I will select "new repository." In the next window, I will name it "render_cronjob1" and lastly click create repository.
Once the upload is completed, I will refresh the GitHub page as follows:
What I need to do is click the "+ New" button, and in a drop-down menu, I will select the cron job. Before I configure, I will select my uploaded repository, such as "render_cronjob1". Then I will select the closer region, which is "Singapore," and the command to start the automation is "python worker.py".
Now it will shift to the left sidebar under the monitor section; the log menu will show the test run progression as shown below:
Once the test run is completed, I am going to check the Gmail mailbox. I found the Gmail display below. Yeah, I have received the sales invoice from How App and Web with an invoice attached.
Finally, the Python automation ran successfully.
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
*.lcov
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
About the Author
Kelvin Loh is a Python developer focused on Flask, desktop applications, and business automation solutions. He shares practical tutorials and real-world coding projects to help developers and small businesses build useful applications.












Comments
Post a Comment