Skip to main content

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

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:

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. 


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

(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:


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.


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:

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:



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. 

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

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.

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. 

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

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 Design a Location Tracking Module for Desktop Business Systems Using Python?

This tutorial will show you how to create an interactive map application that allows users to input geographic coordinates and visualize locations on an interactive map using Python's Tkinter GUI framework enhanced with TTKBootstrap styling and the TkinterMapView widget.  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 ttkbootstrap tkintermapview As usual, we need to import the relevant module and set the root values from tkinter import * import ttkbootstrap as tb import tkintermapview from ttkbootstrap.dialogs import Messagebox root = tb.Window(themename='darkly') root.title('Find My Map') root.geometry("1200x1200") I would like to divide it into three sections.  a) Input Panel Layout,  The headers are mainly a label...

How to Create Flask Forms with CKEditor and Flask-WTF?

In this tutorial, we will be integrating Flask-WTF and Flask-CKEditor into your Flask application! You'll learn how to set up the editor, securely handle formatted HTML content, and create a seamless user experience that enhances any project that requires user-generated content. Let's get started! Prerequisite: This tutorial is part of the Flask CKEditor Project Series. 📚 View the Complete Flask CKEditor Series                                                                                                                                                  ➡ Next Part Preliminary Before I begin, it is recommended to activate the v...

How to Store Application Data with SQLite in Python?

  In the previous tutorial, I used TinyDB as our storage solution to keep application data in a simple JSON-based format. While TinyDB is lightweight and easy to use, many web applications require a more structured and scalable database system. In this tutorial, we will switch to SQLite , a powerful relational database that integrates well with Python and web frameworks. By using SQLite, you will learn how to store, manage, and query data in a more structured way for real-world applications. Prerequisite: This tutorial is part of the Flask CKEditor Project Series. 📚 View the Complete Flask CKEditor Series ⬅ Previous Part                                                                                                         ...