Tutorial

12 Days of DigitalOcean (Day 4) - Deploying Birthday Notifications with DigitalOcean Functions

12 Days of DigitalOcean (Day 4) - Deploying Birthday Notifications with DigitalOcean Functions

Welcome to Day 4 of 12 Days of DigitalOcean! Yesterday, we added Twilio SMS notifications to our Birthday Reminder Service, making it capable of sending text messages for today’s birthdays. 🎂

Today, we’ll take things to the next level by deploying our script to DigitalOcean Functions. This lets our service run in the cloud without the need for a dedicated server, making our app lightweight, scalable, and ready for automation.

With this setup, you’ll receive birthday reminders even when your computer is off or not connected to the internet—no need to run the script manually on your machine anymore. 🎉

Why DigitalOcean Functions?

Sometimes, all you need is a simple script that runs occasionally. Managing infrastructure for something like that can be an overkill. That’s where Functions comes in. It’s a serverless platform, meaning you can deploy code that runs only when needed, and you only pay for what you use. Perfect for our use case—checking birthdays and sending reminders daily.

🚀 What You’ll Learn

By the end of today, you’ll know how to:

  1. Set up DigitalOcean’s doctl CLI tool.
  2. Create and connect to a serverless namespace (DigitalOcean’s way of keeping functions organized).
  3. Package and deploy your Birthday Reminder Service to DigitalOcean Functions.
  4. Test your deployed function in the cloud.

🛠 What You’ll Need

Before starting, make sure you have:

🧑‍🍳 Recipe for Day 4: Deploying to DigitalOcean Functions

Step 1: Set Up the doctl CLI

If you’ve already set up doctl on your machine, you can skip this step. For those who need to set it up, follow these instructions:

Before we begin, let’s quickly talk about doctl. It’s DigitalOcean’s official command-line interface tool that allows you to manage your cloud resources right from your terminal. We’ll use it to create a namespace (a folder for our serverless functions), deploy our Python script, and test the function.

Setting it up is straightforward:

  1. Install doctl: Follow the installation guide for your operating system.

  2. Authenticate doctl: Connect it to your DigitalOcean account by running:

    doctl auth init
    
  3. Verify the installation: Ensure everything is working by running:

    doctl account get
    

If successful, this command will return details about your DigitalOcean account, like your email and account ID.

Step 2: Install the Serverless Software

DigitalOcean Functions requires serverless support software, which you’ll need to install. This is a one-time setup, so once it’s installed, you won’t need to do it again for future projects.

Run the following command:

doctl serverless install

You can check the installation status with:

doctl serverless status

If you see an error like:

Error: serverless support is installed but not connected to a functions namespace

Don’t worry—that just means we haven’t created or connected to a namespace yet. We’ll handle that in the next step.

Step 3: Create and Connect to a Namespace

Namespaces are like folders for organizing serverless functions. Let’s create one for our Birthday Reminder Service:

  1. Create a new namespace:

    doctl serverless namespaces create --label "my-birthday-reminder-namespace" --region "nyc1"
    

    alt text

  2. Connect to the namespace:

    doctl serverless connect my-birthday-reminder-namespace
    

    doctl_serverless_connect_namespace

  3. Verify the connection:

    doctl serverless status
    

You should now see a confirmation that you’re connected to the namespace.

doctl_serverless_status_terminal

Pro Tip: To see a list of all available namespaces, use the following command:

doctl serverless namespaces list

This can be handy if you’re managing multiple projects or want to verify the namespace you just created.

Step 4: Initialize and Set Up the Project Structure

DigitalOcean Functions expects a specific project structure for serverless deployments. You can kickstart this structure using doctl serverless init, create it manually, or even clone a starter repo. To keep things simple, we’ll set it up using doctl serverless init:

  1. Run the following command to initialize the project:

    doctl serverless init --language python birthday-reminder-service
    

    docti_serverless_init_terminal

    This creates a local project directory named my-birthday-reminder-service with the following default structure:

    my-birthday-reminder-service/
    ├── packages
    │   └── sample
    │       └── hello
    │           └── hello.py
    └── project.yml
    

    doctl_serverless_init_python_project_setup

  2. Navigate into the project directory:

    cd my-birthday-reminder-service
    
  3. Rename the folders to match our use case:

    mv packages/sample packages/reminders
    mv packages/reminders/hello packages/reminders/birthdays
    mv packages/reminders/birthdays/hello.py packages/reminders/birthdays/__main__.py
    
  4. Create the necessary files:

    • Create an empty .env file in the root of the project:
    touch .env
    

    This will hold your database and Twilio credentials. The file will be located at the root of the my-birthday-reminder-service folder.

    • Create a requirements.txt file in the birthdays folder:
    touch packages/reminders/birthdays/requirements.txt
    

    This file will list the Python dependencies needed for your function. It will be located under packages/reminders/birthdays.

    • Create a build.sh file in the birthdays folder:
    touch packages/reminders/birthdays/build.sh
    chmod +x packages/reminders/birthdays/build.sh
    

    The build.sh script is necessary for deploying functions with external dependencies. The chmod command ensures the script is executable on Mac/Linux systems.

Updated Structure: After completing these steps, your project structure should look like this:

my-birthday-reminder-service/
├── project.yml
├── .env
├── packages
│   └── reminders
│       └── birthdays
│           ├── __main__.py
│           ├── requirements.txt
│           ├── build.sh
├── .gitignore

Pro Tip: If you accidentally misname a folder, you can run the command again or manually rename it in your file explorer.

Step 5: Update Files

Now that the structure is in place, let’s populate it with the necessary files. Open the my-birthday-reminder-service directory in your favorite code editor

1. Update project.yml

The project.yml file is a configuration file that defines your serverless project’s structure, environment variables, and functions.

This file sets up the reminders package and maps environment variables to DigitalOcean Functions. Each variable corresponds to the credentials needed for your database and Twilio integration.

2. Update your .env file

Refer to Day 1: Setting Up a PostgreSQL Database for Birthday Reminders for the database credentials and Day 3: Checking Birthdays and Sending SMS Notifications for Twilio credentials to populate the following values:

# Database credentials (from Day 1)
DB_HOST=<your-database-hostname>
DB_NAME=<your-database-name>
DB_USER=<your-database-username>
DB_PASSWORD=<your-database-password>
DB_PORT=5432  # Default PostgreSQL port

# Twilio credentials (from Day 3)
TWILIO_ACCOUNT_SID=<your-twilio-account-sid>
TWILIO_AUTH_TOKEN=<your-twilio-auth-token>
TWILIO_PHONE_FROM=<your-twilio-phone-number>
TWILIO_PHONE_TO=<your-personal-phone-number>

Note: The .env file is used to store sensitive credentials securely. Your project.yml file will read these values and map them to the serverless environment during deployment, making them accessible to your function in the cloud.

3. Add Dependencies

Update requirements.txt file with the following dependencies:

pg8000  
python-dotenv  
twilio  

pg8000: A pure-Python PostgreSQL client library.

python-dotenv: Used to load environment variables from the .env file.

twilio: The Twilio Python library for sending SMS messages.

4. Update build.sh

Add the following script to the build.sh file:

#!/bin/bash
set -e

# Print the current working directory for debugging
echo "Current working directory: $(pwd)"

# Check if requirements.txt exists
if [[ -f "requirements.txt" ]]; then
  echo "Found requirements.txt in $(pwd)"
else
  echo "Error: requirements.txt not found in $(pwd)"
  exit 1
fi

# Create a virtual environment
virtualenv --without-pip virtualenv

# Install dependencies from requirements.txt
pip install -r requirements.txt --target virtualenv/lib/python3.9/site-packages

This script ensures that all dependencies are packaged correctly with your function. The chmod +x command from Step 4 ensures it is executable.

5. Update __main__.py

This is the main script for your Birthday Reminder Service. We’re essentially using the script we built in Day 3 for sending birthday notifications. However, to make it compatible with DigitalOcean Functions, we need to make a few small adjustments.

Update the __main__.py file with the following content:

# birthday_reminder_service/__main__.py

from datetime import datetime
import pg8000
from dotenv import load_dotenv
from twilio.rest import Client
import os

# Load environment variables
load_dotenv()

def main(params):
    """DigitalOcean Functions entry point."""
    try:
        # Connect to the database
        connection = pg8000.connect(
            host=os.getenv("DO_DB_HOST"),
            database=os.getenv("DO_DB_NAME"),
            user=os.getenv("DO_DB_USER"),
            password=os.getenv("DO_DB_PASSWORD"),
            port=int(os.getenv("DO_DB_PORT"))
        )
        cursor = connection.cursor()

        # Get today's month and day
        today = datetime.now()
        today_month = today.month
        today_day = today.day

        # Query to fetch contacts whose birthday matches today's date
        cursor.execute(
            """
            SELECT first_name, last_name, birthday
            FROM contacts
            WHERE EXTRACT(MONTH FROM birthday) = %s
              AND EXTRACT(DAY FROM birthday) = %s;
            """,
            (today_month, today_day)
        )
        rows = cursor.fetchall()

        # Notify for each matching contact
        if rows:
            account_sid = os.getenv("TWILIO_ACCOUNT_SID")
            auth_token = os.getenv("TWILIO_AUTH_TOKEN")
            client = Client(account_sid, auth_token)

            for row in rows:
                first_name, last_name, _ = row
                message = client.messages.create(
                    body=f"🎉 It's {first_name} {last_name or ''}'s birthday today! 🎂",
                    from_=os.getenv("TWILIO_PHONE_FROM"),
                    to=os.getenv("TWILIO_PHONE_TO")
                )
                print(f"Message sent for {first_name} {last_name}. Message SID: {message.sid}")
        else:
            print("No birthdays today.")

        # Close the cursor and connection
        cursor.close()
        connection.close()

    except Exception as e:
        print(f"An error occurred: {e}")

Here’s what we’ve changed:

  1. Added a main(params) function: DigitalOcean Functions expects an entry point function named main, which takes a params argument. This is where the function starts execution.

  2. Moved the script logic inside the main function: The code from Day 3 has been wrapped inside the main function to align with this requirement.

  3. Everything else remains the same: The database connection logic, birthday checks, and SMS notification logic are unchanged.

Step 5: Package and Deploy

With everything in place, deploy your project to DigitalOcean Functions:

  1. Deploy the project:
doctl serverless deploy my-birthday-reminder-service

doctl_serverless_deploy

To verify that your function was successfully deployed to the namespace:

  1. Visit the DigitalOcean Control Panel, and navigate to Functions in the left-side bar.
  2. Locate your namespace (e.g., my-birthday-reminder-namespace).
  3. Check that your function appears under the namespace, typically listed as reminders/birthdays.
  4. Click on the function name to view details, including logs, configuration, and invocation history.

digitalocean_function_overview

Step 6: Test Your Deployed Function

Once your function is deployed, it’s time to test it. You can invoke the function manually to ensure it works as expected. There are two ways to do this:

Option 1: Using the DigitalOcean CLI

doctl serverless functions invoke reminders/birthdays

If everything is set up correctly, your function will run in the cloud, checking for today’s birthdays and sending SMS notifications.

![https://doimages.nyc3.cdn.digitaloceanspaces.com/006Community/12-Days-of-DO/Postgressql-birthday/birthday_reminder_service_text_message.jpeg]

Option 2: Using the DigitalOcean Dashboard

  1. Go to the DigitalOcean Control Panel.
  2. Navigate to Functions and locate your reminders/birthdays function.
  3. Click Run to run it manually.
  4. View the output and logs directly in the console.

This method is especially helpful if you prefer a visual interface or want to check the logs in a clean, easy-to-read format.

digitalocean_function_dashboard_console

Testing Tips

When you invoke the function, it will check for birthdays matching today’s date. You’ll receive a text message with the details if there’s a match. To test the function effectively:

  • Add one or more birthdays that match the current date in your database.
  • Check the console or CLI logs to confirm the function executed successfully.

🎁 Wrap-Up

Here’s what you accomplished today:

  1. Set up doctl and created a namespace for our project.
  2. Refactored the Python script for deployment.
  3. Packaged and deployed the Birthday Reminder Service to DigitalOcean Functions.
  4. Tested the cloud function using the CLI and the DigitalOcean Dashboard.

Here are the previous tutorials from this series:

Up next: While this is a big step forward, we’re still running the function manually. In the next tutorial, you’ll automate this process so the Birthday Reminder Service runs automatically every day at a specific time. Imagine waking up to a text reminder without lifting a finger—let’s make that happen tomorrow! 🚀

Here is the next tutorial on Day 5: Automating Birthday Reminders with Daily Triggers.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors

Default avatar

Sr Technical Writer

Senior Technical Writer @ DigitalOcean | 2x Medium Top Writers | 2 Million+ monthly views & 34K Subscribers | Ex Cloud Consultant @ AMEX | Ex SRE(DevOps) @ NUTANIX


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.