How to Schedule and Track Marketing Campaigns

Marketer planning out campaigns to be scheduled and tracked on a board
September 20, 2022
Written by
Reviewed by

SMS Marketing campaigns are a common way for a business to promote a new product, kick off a promotion, or increase engagement. Even with SMS having the highest viewing rate of most traditional marketing channels, businesses still want to ensure a campaign is successful by making sure they send at the right time, place, and reason. A business would also want to be able to track the success of campaigns through viewable metrics and optimize for future success such as through A/B testing.  

This is where a brand new set of tools for Twilio’s Programmable Messaging API would come in including Message Scheduling, Link Shortening and Click Tracking.

Message Scheduling is now generally available. Link Shortening and Click Tracking is in Public Beta. Read our release blog post to learn about these products in depth!

For many companies sending links via SMS, MMS, or WhatsApp at the right time and place is important for their business. These links can act as a gateway to access a webpage, customer portal, make a payment, or to trigger any kind of workflow.

You will now be able to schedule a message (SMS, MMS, or WhatsApp) which can include any link from your brand’s website into our Link Shortening tool, which will reduce the size of the link (lessening message segments), increase trust and likelihood of click throughs due to your company registered name being included in the URL, and also automatically add the ability to track the status of the link click.

In the following tutorial we will be walking through an example of how a business, Owl Shoes Inc., will be planning to run an SMS marketing campaign for upcoming Black Friday sales.

Empowering the Marketer

Owl Shoes Inc. is planning on dropping a new line of shoes on Black Friday and wants to schedule a series of messages to promote the new line.They especially want to send a specific SMS that will tell customers about a limited edition release on their website.

With Twilio’s Message Scheduling feature, we use an existing list of customers (who have opted into SMS) and plan a specific time to send these promotional messages for Owl Shoes Inc.

Our new Link Shortening feature, in Public Beta, will allow for Owl Shoes Inc. to use their existing domains in order to send urls via SMS. These original links can even be extra long and have query parameters built in. Twilio will convert these to a shorter link, which can then be sent out via SMS. Owl Shoes Inc. will benefit from using this service by reducing the number of message segments per SMS, while still being able to get the full link sent. In addition, Twilio’s Link Shortening solution includes Owl Shows registered name within the URL to increase trust and likelihood of click throughs from the recipients.


After the URL has been sent, Owl Shoes Inc. will be able to see which links and messages had the most engagement, and they’ll gain insights into the excitement around their product release.

How To

You’ll need:

  • A domain or subdomain which you can verify ownership of
  • A Twilio account which resides within an Organization
  • A Twilio Phone Number
  • A Twilio Messaging Service
  • A terminal application to generate SSL certificates and make API calls to Twilio

Some Background

To allow Twilio to generate shortened links using your domain or subdomain, track links clicked within a message, and surface relevant data about these interactions, some access to your domain will need to be granted to Twilio. This will be done via TLS keys and certificates, as well as DNS records or HTML files.

In this example, I’m setting Owl Shoes Inc. up with a specific subdomain to use for Link Shortening and Click Tracking, a “Callback URL” which will surface click events, and as a “Fallback URL” for when the shortened link expires (after 90 days). I’m associating these settings with a Messaging Service that they’ll use for sending messages with links.

Setting up an Organization

Link Shortening requires that I set up an Organization in the Twilio Admin Center via the Twilio console. One quick way to check if I’ve set up an Organization is by looking for the “Create an Organization” option in the drop down list of accounts in the top left of my Twilio Dashboard:

Create an Organization in Dropdown Menu

Register and Verify a Domain for use with Twilio

Since Link Shortening and Click Tracking can only be used with verified domains, I need to register and verify the subdomain I’d like to use. To get set up, I can simply add a DNS record to the zone at my DNS provider or upload an HTML file to my website’s root directory to verify the subdomain.

I’ve chosen to use the subdomain ls.owlshoesinc.com, and will use the DNS verification option. In the Domains section of my Organization’s Admin Center, I click the “Add Domain” button in the top right corner of the page to start the process.

Add Domain Button

I enter my subdomain and get a DNS TXT record for verification:

Verify Domain Page

Over at my DNS provider, R53 (yours might be Google, GoDaddy, Cloudflare, etc.) I add the TXT record and then check that the record is live using dig in my terminal. I can also use dig from my browser using Google Admin Toolbox.

dig _twilio.ls.owlshoesinc.com txt +short
"twilio-domain-verification=9dxxxxxxxxxxxxxxxxxxxx"

Once I’ve completed verification with the “Verify Domain” button, I can move on. The next steps will show configuration via the API, though the same can be achieved using the Twilio Console, namely in the Messaging Services section.

Adding DNS Records

Domains or subdomains set up for Link Shortening purposes have to be exclusive to Twilio, so I couldn’t also host a webpage at ls.owlshoesinc.com, since I’ve chosen to use it specifically for Twilio Link Shortening and Click Tracking.

I’m not onboarding the root domain (owlshoesinc.com), but if I was, I’d need to point the A records of the root domain to the following 3 IPs only. If other IPs are included, Twilio will not be able to redirect to the destination URL.

  • 3.233.187.46
  • 3.233.108.250
  • 54.157.2.211

Since I’m onboarding a subdomain (ls.owlshoesinc.com), I’ll add a CNAME record pointing to the following:

  • lsct.ashburn.us1.twilio.com

These instructions and values can always be reviewed inside of your Messaging Services config:

Messaging Services configuration in the Twilio Console showing Link Shortening configuration
Link Shortening configuration in Messaging Services

After creating the CNAME record, I’ll check it using dig again. If I leave off the +short flag I used earlier, I can see the DNS resolve through the CNAME record and back to the previously mentioned IPs:

dig ls.owlshoesinc.com

; <<>> DiG 9.10.6 <<>> ls.owlshoesinc.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14741
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;ls.owlshoesinc.com.                IN        A

;; ANSWER SECTION:
ls.owlshoesinc.com.        300        IN        CNAME        lsct.ashburn.us1.twilio.com.
lsct.ashburn.us1.twilio.com. 1800 IN        CNAME        tbt-edge-proxy-nlb-d2bf09d621f110a4.elb.us-east-1.amazonaws.com.
tbt-edge-proxy-nlb-d2bf09d621f110a4.elb.us-east-1.amazonaws.com. 60 IN A 3.233.108.250
tbt-edge-proxy-nlb-d2bf09d621f110a4.elb.us-east-1.amazonaws.com. 60 IN A 3.233.187.46
tbt-edge-proxy-nlb-d2bf09d621f110a4.elb.us-east-1.amazonaws.com. 60 IN A 54.157.2.211

TLS Certificate and Private Keys

To prove ownership of the Owl Shoes Inc.’s domain, and to ensure that traffic is passed over HTTPS, I’ll need to upload a TLS certificate and private keys. Twilio requires that all redirects be served over HTTPS, so we can set up a trusted connection with the requesting clients (consumers' browsers) and our servers on your behalf.

PLEASE NOTE:

  • The TLS certificate and private key must be generated in a PEM format for the specific subdomain or domain.
  • We do not support Subject Alternative Names in TLS certificates.
  • We only support private keys that start with BEGIN PRIVATE KEY or PKCS #8 formatted keys.
  • It is your responsibility to track certificate expiration dates and update as necessary.

Generating TLS Certificate and Keys

Most domain hosting services offer managed certificate solutions which could be used for this step, along with other paid solutions, but I’ll use a free option, letsencrypt.org. The one caveat here is that most free options provide an expiration of up to 3 months.

Below, I’ve generated a TLS certificate for ls.owlshoesinc.com using Let's Encrypt via my terminal.

1. Install certbot and openssl if you haven't done so already. If you are on a Mac and have brew installed, you can install the packages by running:

brew install certbot
brew install openssl

2.Generate a certificate by executing the following command. You may need to run it as sudo:

certbot certonly --manual --key-type rsa --preferred-challenges dns

3. The instructions will prompt you to enter in the domain name(s) you would like on your certificate and then generate a DNS TXT record for you to add to verify the ownership of your domain or subdomain. Here’s an example of what the record might look like:

TXT Record Example

4. Once the TXT record is created, check it with dig or another DNS tool:

dig _acme-challenge.ls.owlshoesinc.com txt +short
"2Rys8zTHn-PBr5kiSPOwAEgijQwF2Dan5Rx2QkDqc4U"

4. Go back to the terminal and press "Enter" to continue if all looks correct. It will create a new folder (usually  /etc/letsencrypt/live/[domain]) that contains both your Private Key and your Certificate, privKey.pem and cert.pem.

5. Verify that the privKey.pem file is valid by running the following command. If the private key is valid, it should return writing RSA key.

sudo openssl rsa -in /etc/letsencrypt/live/ls.owlshoesinc.com/privkey.pem -text -inform pem -out -

6. Verify that the cert.pem file is valid by running the following command:

sudo openssl x509 -in /etc/letsencrypt/live/ls.owlshoesinc.com/cert.pem -text -noout

A correctly formatted public certificate will show information like signing authority and expiration date. Make note of the validity period, as you will need to reapply the cert before it expires.

 Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            00:00:00:00:00:00:00:00:00:00:00:00:00:00
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Let's Encrypt, CN=R3
        Validity
            Not Before: Sep  9 18:31:45 2022 GMT
            Not After : Dec  8 18:31:44 2022 GMT
        Subject: CN=ls.owlshoesinc.com

Uploading TLS Certificate and Private Key

In this next step, I’ll need two things:

  • A Domain SID for ls.owlshoesinc.com to use with the API.
  • The TLS Certificate and Private key I just generated in a single, concatenated blob of text, like this:
-----BEGIN CERTIFICATE-----
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
-----END PRIVATE KEY-----

1. I can find my Domain SID in my Verified Domains by clicking into ls.owlshoesinc.com and looking in the URL bar. I can see my Domain SID at the end, starting with DN:

www.twilio.com/console/admin/domains/DNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

2. If I wanted to check the contents of my certificate and private key, I can performing these two commands:

sudo cat /etc/letsencrypt/live/ls.owlshoesinc.com/cert.pem
sudo cat /etc/letsencrypt/live/ls.owlshoesinc.com/privkey.pem

3. I’ll create the Certificate and Private Key blob with the following command and store it in my environment variables in my terminal’s session as $TLS_CERT which I’ll use in my next step:

TLS_CERT=`sudo cat /etc/letsencrypt/live/ls.owlshoesinc.com/cert.pem /etc/letsencrypt/live/ls.owlshoesinc.com/privkey.pem`

4. Finally, I’ll make a POST request that sends the combined TLS certificate and private key to Twilio and associates it with the Owl Shoes Inc. subdomain:

curl -X POST \
https://messaging.twilio.com/v1/LinkShortening/Domains/$DOMAIN_SID/Certificate \
--data-urlencode "TlsCert=$TLS_CERT" \
-u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN

Be sure to replace $DOMAIN_SID with the Domain SID gathered in step 1, and use your Twilio credentials in place of TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN. You should get a successful response like this:

{
  "certificate_sid": "CWxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "date_updated": "2022-09-09T19:42:41Z",
  "domain_sid": "DNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "domain_name": "ls.owlshoesinc.com",
  "date_expires": "2022-12-08T18:31:44Z",
  "url": "https://messaging.twilio.com/v1/LinkShortening/Domains/DNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/Certificate",
  "date_created": "2022-09-09T19:42:41Z"
}

Alternatively, I can upload my certificate and private key inside of the Messaging Services config:

Uploading a new cert in the Twilio Console

Cert and key upload in Twilio Console

 

 

Configuring Fallback and Callback URLs

Now that I've uploaded a TLS certificate along with the private key for the domain I wish to use, I can set default configurations for the domain and assign the configuration to Messaging Services. The following are the fields included in the Domain Config API. After setting default configurations for the domain, Twilio will automatically apply the default domain, fallback URL, and callback URL whenever sending a message with long links through the associated Messaging Service.

Field RequiredDescription
DomainYesDomain you wish to use when shortening long links.
FallbackUrlNoTwilio will redirect the recipients to the fallback URL when the shortened links expire, 90 days after creation.
CallbackUrlNoA callback URL where you’ll receive click events.

Setting Default Configuration for my Subdomain

I’d like to set up where to receive click data if someone clicks my link, so I’ll add the CallbackUrl. I also want to define where my links will point to after the 90 day expiration date of my shortened URL, so I’ll set the FallbackUrl param. My request looks something like this:

curl -X POST 
https://messaging.twilio.com/v1/LinkShortening/Domains/$DOMAIN_SID/Config \
--data-urlencode "FallbackUrl=https://whoops.owlshoesinc.com" \
--data-urlencode "CallbackUrl=https://webhook.site/00000000-0000-0000-0000-000000000000" \
-u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN

Again, I swap out the variables above or use environment variables. My response looks like this:

{
  "fallback_url": "https://whoops.owlshoesinc.com",
  "domain_sid": "DNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "date_updated": "2022-09-09T21:20:56Z",
  "config_sid": "ZKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"url": "https://messaging.twilio.com/v1/LinkShortening/Domains/DNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/Config",
  "date_created": "2022-09-09T21:20:56Z",
  "callback_url": "https://webhook.site/00000000-0000-0000-0000-000000000000"
}

Alternatively, I can configure these two core components in the UI within the Messaging Services config:

Callback and Fallback URL config in the Twilio Console

 

This means that if anyone clicks my shortened URL in the first 90 days of me sending it, information about that click will go to https://webhook.site/00000000-0000-0000-0000-000000000000 and they’ll be redirected to the original URL I used in the API call (we’ll cover this next). If someone clicks the shortened URL on the 91st day, they will be redirected to https://whoops.owlshoesinc.com, since the link expired.

Associating a default domain to a Messaging Service

Domain configurations can be associated with multiple Messaging Services so that the same domain, callback URL, and fallback URL are used when sending messages. In order to associate my domain configuration with my Messaging Service, I'll send the following API call:

curl -X POST https://messaging.twilio.com/v1/LinkShortening/Domains/$DOMAIN_SID/MessagingServices/$MESSAGING_SERVICE_SID \
-u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN

(Optional) Disabling Link Shortening for a Messaging Service

I won't run this now, but if I need to in the future, I can dissociate my Messaging Service that's using Link Shortening from my domain configuration by sending the following API call:

curl -X DELETE https://messaging.twilio.com/v1/LinkShortening/Domains/$DOMAIN_SID/MessagingServices/$MESSAGING_SERVICE_SID \
-u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN

(Optional) Get Domain config associate with a Messaging Service

I can also retrieve the Domain associated to a Messaging Service, as well as other relevant information, using the following request:

curl -X GET https://messaging.twilio.com/v1/LinkShortening/MessagingService/$MESSAGING_SERVICE_SID/DomainConfig \
-u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN

{
  "fallback_url": "https://whoops.owlshoesinc.com",
  "continue_on_failure": null,
  "domain_sid": "DNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "date_updated": "2022-09-09T21:20:56Z",
  "messaging_service_sid": "MGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "config_sid": "ZKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "url": "https://messaging.twilio.com/v1/LinkShortening/MessagingService/MGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/DomainConfig",
  "date_created": "2022-09-09T21:20:56Z",
  "callback_url": "https://webhook.site/00000000-0000-0000-0000-000000000000"
}

Sending Messages with Twilio Messaging API

Now that we have everything configured, the last thing to do is to send a message with a long URL in it! Using the Twilio Messaging API, I’ll compose a message like I normally would with a couple small changes:

  • Specify the MessagingServiceSid parameter to set our previously configured Messaging Service as the sender.
  • Specify the ShortenUrls parameter and set it to true

Scheduling the Message

Message Scheduling is another feature I can simply add onto my SMS with a couple parameters:

  • ScheduleType - This parameter indicates your intent to schedule a message. The value fixed will indicate I want to send my message at a fixed time.
  • SendAt - This parameter indicates when Twilio will send a message. Your datetime should be in ISO-8601 format, eg: 2022-09-09T21:55:00Z.

My Messaging API call with URL Shortening, Click Tracking, and Message Scheduling all enabled now looks like this:

curl 'https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Messages.json' -X POST \
--data-urlencode 'To=+1xxxxxxxxxx' \
--data-urlencode 'Body=Check out the latest drop in footwear! https://www.owlshoesinc.com/newdrops/blackfriday?cat=150463332457&source=usa&h=bf&uid=aDwEv34fFsa3\
--data-urlencode 'MessagingServiceSid=MGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--data-urlencode 'ShortenUrls=true' \
--data-urlencode 'ScheduleType=fixed' \
--data-urlencode 'SendAt=2022-09-09T21:55:00Z' \
-u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN

The response from my API call is successful, along with a "status": "scheduled" confirmation. After waiting for my timestamp I put into the SendAt parameter to roll around, I receive an SMS with a shortened URL, which is now branded with my subdomain ls.owlshoesinc.com, on my phone:

Link Shortening

When the link is clicked, my web browser opens, and the URL is quickly loaded and redirected to the original URL from my API call:

 

Link Shortening

After the click, I check my Callback URL tracking application at webhook.site to see what I was able to gather about my interaction with the shortened URL:

{
  "event_type": "click",
  "messaging_service_sid": "MGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "clicked_at": 1662760520349,
  "sms_sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "link": "https://www.owlshoesinc.com/newdrops/blackfriday?cat=150463332457&source=usa&h=bf&uid=aDwEv34fFsa3",
  "account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "from": "+1714xxxxxxx",
  "to": "+1xxxxxxxxxx",
  "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/105.0.5195.100 Mobile/15E148 Safari/604.1"
}

Tracking Engagement and Maximizing ROI

This is where Owl Shoes Inc. can get really creative and start to associate data to trends. Not only did they shorten a unique URL, making the SMS more trustworthy, they also scheduled it to be sent when I’d be most engaged. They knew that I clicked on the message in under a minute, which says a lot about how much I want to know what’s happening in the world of shoes. This information is then fed into Owl Shoes Inc.’s CRM and used in an upcoming “Insider Exclusive” deal promo campaign that only goes to the most interested customers.

Savvy marketers will also be thrilled with their capability to measure click through rates (CTR) and optimize future campaigns. For example, if the marketer A/B tested two variations of the promotional text, then they can determine the highest performing format and use it within their “Insider Exclusive” promotion to further increase conversions and ROI.

Good luck with your future marketing and engagement efforts. We can’t wait to see what you build!

Brian Mgrdichian is a Principal Solutions Engineer at Twilio. When he’s not working alongside clients to unearth the most optimal integrations that will fulfill their needs, he loves to tinker with 3D design and printing, work on electronics, and go for hikes in the mountains of Colorado. He can be reached at bmgrdichian@twilio.com.

Sabeel Siddiqi is a Solutions Engineer at Twilio. He lives in Atlanta, GA. Aside from being an amatuer chef in his kitchen, he enjoys breaking down challenges and building up solutions with customers. He can be reached at sasiddiqi@twilio.com