Skip to main content

How to Schedule Python Scripts in the Cloud Using Render Cron Jobs?

How to Automatically Generate and Email Sales Invoices from Google Forms? (Part 6)

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

⬅ Previous Part                                                                                                                  ➡ Next Part

Preliminary: 
In this tutorial, I will walk you through how to deploy a Python automation script to Render. The file and folder are shown as follows:
Files and folders

All files and folders remain the same as the previous tutorial, except now I'm going to add a few files; these include .gitignore and render.yaml, requirements.txt, and a worker.py file. 

The description is as follows:
  1. .gitignore—those files excluded from uploading to the GitHub repository,
  2. render.yaml—a deployment configuration to automatically set up and manage my application or automation service,
  3. requirements.txt—a list of all dependencies that will be installed during the Render deployment,
  4. worker.py—the main execution script that acts like the controller or engine for your automation system on Render,
API Keys
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.  

Git and GitHub Repository
Upload files and folders to any repository. I need to download and install Git. Click here to download. Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency. 

For these tutorials, I prefer to use GitHub as my repository. If you have not signed up, please go ahead and sign up here.

Render Services
For the automation script, I have two choices to deploy on the Render Cloud. I can choose either:
1) Background workers
services that run continuously, but they don't receive any incoming network traffic. Instead, these services usually poll a task queue and process new tasks as they come in.

2) Cron jobs
services that run periodically on a schedule you define.
For these tutorials, I prefer to run the Python automation script on the Render cron job service. For the fee for these services, please click the price. 


Step 1: Amend the gsheet.py and pdf.py files
Since the automation script is running on the Render cloud and the API key will be provided in the environment, how the script loads the API keys may be different, too. As on my local computer, all API keys will be stored in .env files; however, when in the Render cloud, no such file exists, and it will throw an error.

Therefore, I need to amend the gsheet.py
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 ###

Step 2: Add and configure new files
 
As above, I had briefed the new files; now I will go further to set up the contents of each file.

First, the .gitignore file will help me avoid uploading the following files to GitHub:
__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. 

Next, render.yaml will explain how the deployment is executed and the content as follows:
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. 

The following is requirements.txt, informing Render which dependencies to install:
python-dotenv
google-auth
pillow
pygsheets
reportlab
requests
oauth2client

Finally, the worker.py file is where the command should be executed from, as follows:
import 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.")

Step 3: Install Git and execute the Git commands
Once all files have been properly set up, I can proceed. I need to follow the instructions to install Git on my local computer. 

GitHub repository

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.

I return to my code editor; I will push the file and folder from my local computer to the GitHub repository. Under my terminal, I will select Git Bash to input my command, as in my diagram below:
    
              $ git init
Bash commands

(PS: Remember to "cd" to the folder directory, in my case ('cd sales_invoice').)

Once the upload is completed, I will refresh the GitHub page as follows:
Repository content

---------------------------------------------------------------------------------------------------------------------------------------------------

🎁 Get Your FREE Flask Cheat Sheet

Get Your FREE Flask Cheat Sheet

Get more Flask, Python automation, Docker, and HTMX tutorials delivered to your inbox.

✓ Practical coding tutorials
✓ Automation tips for SMEs
✓ New project ideas and templates

Download my FREE Flask Cheat Sheet (PDF)


---------------------------------------------------------------------------------------------------------------------------------------------------

Step 4: Log in to Render, connect the GitHub repository, and configure the deployment.
Since I have created GitHub, I will log in to Render with GitHub, which will allow me to select which repository to deploy later. Following is how I set up the deployment.

Render deployment

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".


Step 5: Input the Render environment (API key)
Even though the .env file is stored on my local computer, in Render, all the API keys will be stored in Environment. 

For GMAIL_PASSWORD, a single API key, I simply input the API key with the Render environment variable as follows:
Environment variable

However, the gsheet.json contains many keys, so I will click the "+ add secret file" button from the advanced section. Then, I will name it "google_credentials.json" and copy the local computer's gsheet.json file and paste it into the secrets file and finally click the save button as follows:
Secret file



Step 6: Build the application and trigger a run to test the results.
Once the Deploy Cron Job button is clicked, the Render Cloud will take a few minutes to build the system. 
Test run

I can check from the left sidebar under the monitor section; the build menu will display the build progression.  
Logs result

When built successfully, I will scroll to the top of the page; it shows two buttons: "connect" and "run trigger". So, I go ahead and click the "run trigger" button.
Trigger run

Now it will shift to the left sidebar under the monitor section; the log menu will show the test run progression as shown below:
Deployment status

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.
Email result
Finally, the Python automation ran successfully. 

Final wrap-up: 
In this tutorial, we successfully learned how to deploy a Python automation project to Render using GitHub integration and Render Cron Jobs. Along the way, we explored how to structure an automation project using files such as worker.py, render.yaml, and modular utility scripts for PDF generation, Google Sheets integration, and email automation. We also covered important deployment concepts such as managing environment variables securely, handling service account credentials, organising dependencies with the requirements.txt file and troubleshooting common Git and deployment issues. By combining Python automation with cloud deployment, you can build powerful systems that run continuously in the background without relying on your local machine. 

Published: May 2026
Last Updated: Jun 2026
.gitignore template
# 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
---------------------------------------------------------------------------------------------------------------------------------------------------
Thanks for reading! 

If you haven't subscribed yet, join my newsletter to receive future Python and Flask tutorials.


-----------------------------------------------------------------------------------------------------------------------------
Need a similar system for your business? 
I build custom Flask web applications and Python automation solutions for SMEs and solopreneurs. 


-----------------------------------------------------------------------------------------------------------------------------

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

Popular Posts

How to Build an Audiobook Workflow Desktop System with Python?

In this tutorial, we will build a simple audiobook player using Python and CustomTkinter. You will learn how to convert text into speech using gTTS and play it with PyGame. We will also implement play, pause, and stop controls, like those in a real audio player. By the end, you will have a clean and functional desktop audiobook app. Prerequisite: This tutorial is part of the standalone tutorial. 📚 View the standalone tutorial Preliminary   Before I begin, it is recommended to activate the virtual environment before installing the relevant dependencies. python -m venv venv venv\Scripts\activate pip install customtkinter pillow gTTS pygame CTkMessagebox pypdf Then, the following steps include setting up the file structure, app.py, and two additional folders: the uploads and media folders. The media folder contains the icons necessary to build the app; there are read, pause, and stop icons, as shown on the diagram. Step 1: Build up the app interface I have 4 sections here: the...

How to Set Up PgAdmin and Adminer Using Docker Compose?

If you are new to database management with Docker, this tutorial will guide you through setting up both PgAdmin and Adminer using Docker containers. By containerising these tools, you can quickly launch lightweight and portable database management environments without installing them directly on your operating system. This approach also makes it easier to manage configurations, updates, and multiple projects across different devices.