Enhance Customer Engagement with Go and Twilio SMS

October 30, 2024
Written by
Oluyemi Olususi
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

Enhance Customer Engagement with Go and Twilio SMS

Engaging customers effectively — especially during promotions — can significantly boost traction and drive profits for your business. Imagine having a list of loyal customers, or even new ones who have expressed interest or consented to receive updates about your latest products or special offers. How can you efficiently communicate these promotions to them?

This is where SMS marketing excels. Studies show that people rarely go anywhere without their mobile phones, making SMS a powerful channel for staying connected. So, by leveraging SMS marketing as a core part of your customer engagement strategy, you can maintain a consistent presence with your customers, driving sales, and fostering loyalty, ultimately increasing your business's bottom line.

In this tutorial, we will build a marketing campaign service in Go that utilizes Twilio’s Programmable Messaging API to send targeted, promotional SMS messages to customers. We will set up a Go-based backend, integrate it with Twilio to send personalized promotional messages, and allow customers to opt out by replying with a STOP word. The end result will offer businesses an effective way to enhance customer engagement through SMS marketing, helping them connect with their audience meaningfully and profitably.

Prerequisites

You will need the following for this tutorial:

  • A basic understanding of and experience with Go (version 1.22.0 or newer)
  • A Twilio account (free or paid). Sign up for a free trial account, if you don’t have one
  • A Twilio phone number capable of sending SMS
  • A phone that can send and receive SMS
  • curl for testing the application

Set up the project

To begin, create a new project directory and change in to it by running the following commands:

mkdir sms-marketing-twilio
cd sms-marketing-twilio

While you are within the sms-marketing-twilio folder, create a new Go module by running the following command.

go mod init sms-marketing-twilio-sdk

This will create a go.mod file in the root of your project which defines the module path and lists all dependencies required by your module.

Install the project's dependencies

This project will require two packages:

Install them using the following command:

go get github.com/joho/godotenv github.com/twilio/twilio-go

Next, create a .env file to store your environment variables:

TWILIO_ACCOUNT_SID=<YOUR_TWILIO_ACCOUNT_SID>
TWILIO_AUTH_TOKEN=<YOUR_TWILIO_AUTH_TOKEN>
TWILIO_PHONE_NUMBER=<YOUR_TWILIO_PHONE_NUMBER>

Then, replace the placeholder values with the details retrieved from your Twilio Console, by following these steps:

  • Log in to the Twilio Console
  • Copy the details from the Account Info panel
  • Replace <YOUR_TWILIO_ACCOUNT_SID>, <YOUR_TWILIO_AUTH_TOKEN>, and <YOUR_TWILIO_PHONE_NUMBER> with the corresponding values
Be sure to add the .env file to your .gitignore file to prevent these sensitive credentials from being exposed by Git if you publish your code to a public repository.

Load the environment variables

Next, you will create a helper function to load the environment variables defined in the .env file and make them accessible throughout the application. To do this, create a folder named config, and a file called config.go within it. Following that, paste the following code into the new file:

package config

import (
    "log"
    "os"
    "github.com/joho/godotenv"
)

func LoadEnv() {
    if err := godotenv.Load(".env"); err != nil {
        log.Fatalf("Error loading .env file")
    }
    os.Setenv("TWILIO_ACCOUNT_SID", os.Getenv("TWILIO_ACCOUNT_SID"))
    os.Setenv("TWILIO_AUTH_TOKEN", os.Getenv("TWILIO_AUTH_TOKEN"))
    os.Setenv("TWILIO_PHONE_NUMBER", os.Getenv("TWILIO_PHONE_NUMBER"))
}

This loads the environment variables from the .env file using os.Getenv(), so that they can be accessed anywhere in the Go code. The program will log an error and terminate if the file is not found.

Add the ability to send SMS with Twilio

Now, you will create another helper function for interacting with the Twilio API to send SMS. Create a folder named sms and a file called send.go within it, then paste the following content into the file:

package sms

import (
    "fmt"
    "log"
    "os"
    "github.com/twilio/twilio-go"
    openapi "github.com/twilio/twilio-go/rest/api/v2010"
)

func SendSMS(to, message string) error {
    client := twilio.NewRestClient()
    params := &openapi.CreateMessageParams{}
    params.SetTo(to)
    params.SetFrom(os.Getenv("TWILIO_PHONE_NUMBER"))
    params.SetBody(message)
    resp, err := client.Api.CreateMessage(params)
    if err != nil {
        return fmt.Errorf("failed to send SMS: %w", err)
    }
    log.Printf("SMS sent successfully to %s: %v\n", to, resp.Sid)
    return nil
}

The SendSMS() accepts to which is the recipient’s phone number and message which is the content to be sent. It uses the Twilio Go Helper Library to send the SMS. If the message is sent successfully, it logs a success message; otherwise, it returns an error.

You can also consider using an Alphanumeric Sender ID instead of a phone number to increase message open rates. This allows you to display a custom name as the sender.

Add the ability to send campaign SMS

Next, create a new file named campaign.go within the sms folder. This file will hold the list of customers’ details, a function to retrieve a specific customer’s details and the logic to send marketing campaigns to customers who have not opted out.

Paste the following code into the file:

package sms

import (
    "fmt"
    "log"
)

type Customer struct {
    PhoneNumber string
    OptedOut bool
}

var Customers = []*Customer{
    {PhoneNumber: "<YOUR PHONE NUMBER>", OptedOut: false},
}

func FindCustomer(phone string) *Customer {
    for _, customer := range Customers {
        if customer.PhoneNumber == phone {
            return customer
        }
    }
    return nil
}

func SendMarketingCampaign(message string) error {
    for _, customer := range Customers {
        if !customer.OptedOut {
            err := SendSMS(customer.PhoneNumber, message)
            if err != nil {
                log.Printf("Failed to send SMS to %s: %v\n", customer.PhoneNumber, err)
                return fmt.Errorf("failed to send campaign: %w", err)
            }
        } else {
            fmt.Printf("Customer %s has opted out. Skipping.\n", customer.PhoneNumber)
        }
    }
    return nil
}

The code first defines a Customer struct that stores a customer’s details with two fields: PhoneNumber and OptedOut. Next, an array of Customers is defined, with two phone numbers hardcoded for this tutorial. This can be replaced with database integration for more complex use cases if desired. Make sure you update the array with your phone number. 

Lastly, the FindCustomer() function searches for a customer by their phone number, and SendMarketingCampaign() sends the promotional message to customers who have not opted out, using the SendSMS() function from earlier.

Let users opt out of the campaign

To allow customers to opt out of the campaign, create a new file named optout.go within the sms folder and paste the following code within it:

package sms

import (
    "fmt"
    "net/http"
    "strings"
)

func HandleIncomingSMS(w http.ResponseWriter, r *http.Request) {
    from := r.FormValue("From")
    body := strings.TrimSpace(strings.ToLower(r.FormValue("Body")))
    customer := FindCustomer(from)
    if customer == nil {
        fmt.Printf("Customer with phone number %s not found\n", from)
        http.Error(w, "Customer not found", http.StatusNotFound)
        return 
    }
    if body == "stop" && !customer.OptedOut {
        customer.OptedOut = true
        fmt.Printf("Customer %s has opted out.\n", from)
    } else {
        fmt.Printf("Received message from %s: %s\n", from, body)
    }
    fmt.Fprintf(w, "<Response></Response>")
}

The HandleIncomingSMS() function handles responses from customers. It checks if the message body is “STOP”, and if so, marks the customer as opted out. Otherwise, it logs the received message. This function simulates how the system can process opt-out requests.

Put it all together

Create a main.go in the project’s root directory and then paste the following code in it:

package main

import (
    "fmt"
    "log"
    "net/http"
    "sms-marketing-twilio-sdk/config"
    "sms-marketing-twilio-sdk/sms"
)

func main() {
    config.LoadEnv()

    mux := http.NewServeMux()
    mux.HandleFunc("/send-campaign", sendCampaignHandler)
    mux.HandleFunc("/process-reply", sms.HandleIncomingSMS)
    fmt.Println("Server is running on port 8080...")
    log.Fatal(http.ListenAndServe(":8080", mux))
}

func sendCampaignHandler(w http.ResponseWriter, r *http.Request) {
    message := "Exclusive Offer: Get 20% off your next purchase!"
    if err := sms.SendMarketingCampaign(message); err != nil {
        http.Error(w, "Failed to send campaign", http.StatusInternalServerError)
        return
    }
    
    fmt.Fprintln(w, "Campaign sent successfully")
}

The main.go file is the application's entry point. It sets up the HTTP routes for sending campaigns and processing replies. Running the application will start a server on port 8080, enabling you to interact with the campaign and opt-out functionality.

Test the code

With the code completed, it's time to test that the application works as expected. First, start the server with the following command:

go run main.go

This will start the server on port 8080.

Then, in a new terminal session or tab, run the command below to send a campaign request.

curl -X POST http://localhost:8080/send-campaign

You will see the following output printed to the terminal:

Campaign sent successfully

And, if the message is successfully received, it should look similar to the screenshot below.

SMS received offering 20% discount, sent from Twilio trial account with phone number (703) 991-0978.

You can opt out of receiving future messages by sending the appropriate HTTP request to the "/process-reply" endpoint with the command below.

curl -X POST http://localhost:8080/process-reply -d "From=%2B<YOUR PHONE N UMBER>&Body=STOP"

The "+" symbol in the phone number must be URL-encoded as "%2B". This ensures that the "+" is correctly interpreted as part of the international phone number (in E.164 format) when processing the request.

That's how to enhance customer engagement with Go and Twilio's SMS Marketing API

The flexibility of this implementation makes it applicable across various industries that depend on effective customer communication and targeted promotions. Whether retail, e-commerce, healthcare, hospitality, or events and entertainment, businesses can leverage this SMS marketing service to engage their audience and drive sales.

What we've built here is a solid foundation that can be extended and enhanced. For instance, while this example uses hardcoded customer details, a more robust solution would involve integrating a database to manage customer data efficiently. Adopting a more organized folder structure would improve scalability and maintainability, making extending the application with new features more accessible.

I hope you found this tutorial helpful. Check out Twilio's official documentation to explore additional products and services that can help grow your business. The complete source code can be found here on GitHub. Happy coding!

Oluyemi is a tech enthusiast with a background in telecommunications engineering. With a keen interest to solve day-to-day problems encountered by users, he ventured into programming and has since directed his problem-solving skills at building software for both web and mobile. 

A full-stack software engineer with a passion for sharing knowledge, Oluyemi has published a good number of technical articles and content on several blogs on the internet. Being tech-savvy, his hobbies include trying out new programming languages and frameworks. You can find him at: https://twitter.com/yemiwebby and https://github.com/yemiwebby.

The SMS icon in the main image was created by Freepik on Flaticon.