×
Community Blog How to Automate Email and SMS Marketing Campaigns with Alibaba Cloud Direct Mail and DataWorks

How to Automate Email and SMS Marketing Campaigns with Alibaba Cloud Direct Mail and DataWorks

How to Automate Email and SMS Marketing Campaigns with Alibaba Cloud Direct Mail and DataWorks, spend more time managing data and campaigns than on strategy.

Disclaimer: The views expressed herein are for reference only and don't necessarily represent the official views of Alibaba Cloud.

Picture a typical campaign cycle. A data analyst queries the CRM, exports a spreadsheet, passes it to the marketing team, who then format it, upload it to an email tool, and schedule a send.

The same process runs again for SMS. By the time the campaign goes out, the audience data is already a few days old.

This is not a process problem. It is an architecture problem.

The data, the logic, and the delivery channels are all sitting in separate silos with a human doing the connecting work in between.

Alibaba Cloud solves this at the infrastructure level. DataWorks handles the data extraction and scheduling.

Object Storage Service (OSS) acts as the staging layer between your data and your delivery services. Direct Mail handles email delivery.

And for SMS, Alibaba Cloud Short Message Service takes care of the last mile. Wire these together once, and your campaigns run on their own.

This tutorial walks through exactly how to build that pipeline, step by step.

Architecture Overview

Before getting into configuration, it helps to understand what each service does in this pipeline:

DataWorks is Alibaba Cloud's data development and pipeline orchestration platform.

It lets you write SQL-based transformation jobs, schedule them on a cron-like schedule, and connect to dozens of data sources through its Data Integration feature.

In this pipeline, DataWorks reads your audience data from MaxCompute and writes it as a CSV to OSS on whatever schedule you define.

Object Storage Service (OSS) acts as the handoff point between your data layer and your communication layer.

When DataWorks writes the audience file to an OSS bucket, a downstream script can pick it up, parse it, and use it to drive sends.

OSS is the connective tissue that decouples your data pipeline from your delivery pipeline.

Direct Mail is Alibaba Cloud's managed email delivery service.

It handles everything from domain verification and sender reputation management to bounce tracking and delivery rate reporting.

You interact with it through its API or SDK to send both triggered emails (one at a time) and batch campaigns (using a template and a recipient list).

Short Message Service (SMS) is Alibaba Cloud's programmatic SMS gateway. Like Direct Mail, you call it via API after preparing your recipient list from OSS.

Prerequisites

You will need the following before starting:

  • An Alibaba Cloud account with billing set up
  • DataWorks activated with a workspace created in your preferred region
  • A MaxCompute project linked to that DataWorks workspace (this is where your audience data lives)
  • An OSS bucket created in the same region as your DataWorks workspace
  • Direct Mail activated with a verified sender domain and at least one sender address configured
  • An Alibaba Cloud AccessKey ID and AccessKey Secret stored securely (use RAM user credentials, not root account keys)
  • Python 3.8+ installed locally or on an ECS instance

Install the required Python packages before proceeding:

pip install alibabacloud-dm20151123 alibabacloud-tea-openapi oss2 pandas

Step 1: Prepare Your Audience Data in MaxCompute

For this tutorial, assume your audience data lives in a MaxCompute table called marketing.campaign_subscribers.

The table contains the fields your campaign will use: subscriber ID, email address, phone number, first name, and the segment tag.

If you are ingesting subscriber data from an external source like ApsaraDB RDS or a flat file, you can use DataWorks Data Integration to load it into MaxCompute first.

The DataWorks Data Integration documentation covers how to configure source and destination connectors for most common data sources.

For this tutorial, create a sample table directly in MaxCompute DataStudio:

CREATE TABLE IF NOT EXISTS marketing.campaign_subscribers (
    subscriber_id   STRING,
    email           STRING,
    phone           STRING,
    first_name      STRING,
    segment         STRING,
    last_purchase   STRING
)
LIFECYCLE 365;

-- Insert sample data
INSERT INTO marketing.campaign_subscribers VALUES
('SUB001', 'alice@example.com',   '+6591234567', 'Alice',  'high_value',    '2026-04-01'),
('SUB002', 'bob@example.com',     '+6598765432', 'Bob',    'reactivation',  '2025-12-15'),
('SUB003', 'carol@example.com',   '+6592345678', 'Carol',  'high_value',    '2026-04-10');

For your actual campaigns, this table will be populated by your regular data pipelines. The segment field is what you will use to filter the audience for each specific campaign.

Step 2: Configure the DataWorks Sync Job

Now you need a DataWorks Batch Synchronization node that reads the subscriber table from MaxCompute and writes it to an OSS bucket as a CSV file.

In the DataWorks console:

  1. Navigate to your workspace and open Data Development under the Data Development and O&M section.
  2. In the DataStudio project folder, click the new node icon and select New Node > Data Integration > Batch Synchronization.
  3. Name the node something like export_campaign_subscribers_to_oss.

Configure the Reader (MaxCompute source):

In the Batch Synchronization editor, select MaxCompute as the Reader type. Set the data source to your MaxCompute project and write a query that filters by segment:

SELECT
    subscriber_id,
    email,
    phone,
    first_name,
    segment,
    last_purchase
FROM marketing.campaign_subscribers
WHERE segment = 'high_value'

You can parameterise the segment filter using a DataWorks scheduling variable if you want a single node to serve multiple campaign types.

Configure the Writer (OSS destination):

Select OSS as the Writer type. Point it to your OSS bucket and set the output path and filename. A good naming convention uses the current date so each run produces a versioned file:

campaign-exports/high_value_subscribers_${bizdate}.csv

DataWorks automatically substitutes ${bizdate} with the business date of each scheduled run. Set the file format to CSV, enable the header row, and set the field delimiter to a comma.

The migration configuration follows six key steps: linking MaxCompute and OSS as data sources in DataWorks, then running a Batch Synchronization job that uses an ODPS Reader and OSS Writer to transfer the data.

Schedule the node:

Click the Properties tab on the right side of the node editor.

Set the scheduling cycle to Daily and choose a time that runs before your campaign send window, such as 6:00 AM. This ensures fresh audience data is always available before campaigns fire.

Save and commit the node to the production environment.

On the next scheduled run, a file named high_value_subscribers_20260506.csv will appear in your OSS bucket.

Step 3: Set Up Direct Mail

Before you can send email campaigns, Direct Mail needs a verified sender domain and at least one sender address.

Verify your domain:

  1. Log in to the Direct Mail console.
  2. Go to Email Domains and click New Domain. Enter the domain you will be sending from.
  3. Direct Mail generates a set of DNS records (SPF, DKIM, and MX). Add these to your domain registrar. DNS propagation typically takes a few hours.
  4. Return to the console and click Verify once the records are live.

Create a sender address:

Go to Sender Addresses and click Create Sender Address. Create two addresses: one for triggered emails and one for batch campaigns. For this tutorial the batch sender address is the one you will use.

Create an email template:

Batch sends in Direct Mail work by merging recipient list variables into a pre-approved template. Go to Email Templates and click New Template. Write your campaign email in HTML, using template variables for personalisation:

<html>
<body>
  <p>Hi ${name},</p>
  <p>As one of our valued customers, we wanted to share an exclusive offer with you.</p>
  <p>Your last purchase was on ${last_purchase}. It is time to treat yourself again.</p>
  <p><a href="https://yourstore.com/offers">View Your Offer</a></p>
</body>
</html>

Submit the template for review. Approval typically takes a few hours for new accounts.

Create a recipient list:

Go to Recipient Lists and create a new list. Note the list ID that Direct Mail assigns to it. You will populate this list programmatically from your OSS CSV file in the next step.

Direct Mail supports three sending interfaces: the console for manual sends, the API for scalable programmatic sends, and the SMTP interface for standard mail transfer protocol access.

Step 4: Build the Campaign Trigger Script

This Python script does three things: it reads the audience CSV from OSS, populates the Direct Mail recipient list, and triggers the batch send. Schedule this script to run after the DataWorks export job completes each morning.

import os
import io
import json
import pandas as pd
import oss2
from alibabacloud_dm20151123.client import Client as DmClient
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_dm20151123 import models as dm_models

# Configuration
ACCESS_KEY_ID     = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_ID")
ACCESS_KEY_SECRET = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
OSS_ENDPOINT      = "https://oss-ap-southeast-1.aliyuncs.com"
OSS_BUCKET_NAME   = "your-campaign-bucket"
DIRECT_MAIL_REGION = "ap-southeast-1"

# The date-stamped file written by DataWorks today
from datetime import datetime
today = datetime.now().strftime("%Y%m%d")
OSS_FILE_KEY = f"campaign-exports/high_value_subscribers_{today}.csv"


def read_audience_from_oss() -> pd.DataFrame:
    """Read today's audience CSV from OSS into a DataFrame."""
    auth   = oss2.Auth(ACCESS_KEY_ID, ACCESS_KEY_SECRET)
    bucket = oss2.Bucket(auth, OSS_ENDPOINT, OSS_BUCKET_NAME)
    obj    = bucket.get_object(OSS_FILE_KEY)
    content = obj.read()
    return pd.read_csv(io.BytesIO(content))


def build_direct_mail_client() -> DmClient:
    config = open_api_models.Config(
        access_key_id=ACCESS_KEY_ID,
        access_key_secret=ACCESS_KEY_SECRET,
        region_id=DIRECT_MAIL_REGION
    )
    return DmClient(config)


def populate_recipient_list(client: DmClient, list_id: str, df: pd.DataFrame):
    """
    Add each subscriber to the Direct Mail recipient list.
    Each recipient carries variables that the email template will use.
    """
    for _, row in df.iterrows():
        # Variables must be a JSON string matching template placeholder names
        variables = json.dumps({
            "name":          row["first_name"],
            "last_purchase": row["last_purchase"]
        })

        request = dm_models.CreateReceiverDetailRequest(
            receiver_id=list_id,
            detail=row["email"],
            data=variables
        )
        client.create_receiver_detail(request)

    print(f"Added {len(df)} recipients to list {list_id}.")


def trigger_batch_campaign(
    client:      DmClient,
    list_id:     str,
    template_id: str,
    from_alias:  str,
    subject:     str
):
    """Fire the batch campaign using the populated recipient list and template."""
    request = dm_models.BatchSendMailRequest(
        account_name=from_alias,
        address_type=1,              # 1 = list-based batch send
        receiver_addresses=list_id,
        template_name=template_id,
        subject=subject,
        reply_to_address=True,
        click_trace="0"
    )
    response = client.batch_send_mail(request)
    print(f"Campaign triggered. Response: {response.body}")


if __name__ == "__main__":
    print("Reading audience from OSS...")
    df = read_audience_from_oss()
    print(f"Loaded {len(df)} subscribers.")

    client = build_direct_mail_client()

    RECIPIENT_LIST_ID = "your-recipient-list-id"
    TEMPLATE_ID       = "your-template-name"
    FROM_ALIAS        = "campaigns@yourdomain.com"
    SUBJECT           = "Your Exclusive Offer Is Waiting"

    print("Populating recipient list...")
    populate_recipient_list(client, RECIPIENT_LIST_ID, df)

    print("Triggering campaign...")
    trigger_batch_campaign(client, RECIPIENT_LIST_ID, TEMPLATE_ID, FROM_ALIAS, SUBJECT)

A few things worth noting about this script:

Never hardcode your AccessKey credentials. Use environment variables or, better yet, Alibaba Cloud's RAM role-based authentication when running this on ECS.

The batch send API has a rate limit of 10 calls per minute for accounts outside of China mainland, so if you are processing very large recipient lists, build in a short sleep between batches of API calls.

For batch sends, you must use a pre-approved template and a recipient list; custom parameters can be injected through the variables field in each recipient record.

Step 5: Add SMS to the Pipeline

Adding SMS notifications alongside the email campaign creates a true multi-channel outreach.

Alibaba Cloud Short Message Service works through a similar API pattern: authenticate, specify the template and recipient, and call the send endpoint.

First, activate SMS in the Alibaba Cloud console, create an SMS signature, and create an SMS template. Templates in SMS must be approved before use, just like in Direct Mail. An approved template might look like:

Hi ${name}, your exclusive offer is live. Visit us today and save on your next order.
Reply STOP to unsubscribe.

Note the ${name} placeholder, which matches the variable you will pass at send time.

Here is a function that sends an individual SMS and integrates cleanly into the existing pipeline:

from alibabacloud_dysmsapi20170525.client import Client as SmsClient
from alibabacloud_dysmsapi20170525 import models as sms_models

def build_sms_client() -> SmsClient:
    config = open_api_models.Config(
        access_key_id=ACCESS_KEY_ID,
        access_key_secret=ACCESS_KEY_SECRET,
        endpoint="dysmsapi.ap-southeast-1.aliyuncs.com"
    )
    return SmsClient(config)


def send_sms_to_audience(df: pd.DataFrame, signature: str, template_code: str):
    """Send a personalised SMS to each subscriber in the audience DataFrame."""
    client = build_sms_client()

    success_count = 0
    fail_count    = 0

    for _, row in df.iterrows():
        params = json.dumps({"name": row["first_name"]})

        request = sms_models.SendSmsRequest(
            phone_numbers=row["phone"],
            sign_name=signature,
            template_code=template_code,
            template_param=params
        )

        try:
            response = client.send_sms(request)
            if response.body.code == "OK":
                success_count += 1
            else:
                print(f"SMS failed for {row['phone']}: {response.body.message}")
                fail_count += 1
        except Exception as e:
            print(f"Exception sending to {row['phone']}: {e}")
            fail_count += 1

    print(f"SMS complete. Sent: {success_count}, Failed: {fail_count}")

Add this call at the bottom of your __main__ block:

    SMS_SIGNATURE     = "YourBrandName"
    SMS_TEMPLATE_CODE = "SMS_123456789"

    print("Sending SMS notifications...")
    send_sms_to_audience(df, SMS_SIGNATURE, SMS_TEMPLATE_CODE)

The entire pipeline now sends both an email and an SMS to each subscriber in a single automated run.

Step 6: Schedule Everything End-to-End

Right now you have two independent pieces: the DataWorks sync job that creates the CSV, and the Python script that reads it and fires the campaigns.

You need to make sure the Python script only runs after DataWorks has finished writing the file.

There are two clean ways to do this on Alibaba Cloud.

Option A: DataWorks Shell Node (simpler)

Add a Shell node downstream of your Batch Synchronization node in DataWorks and set a dependency between them. DataWorks will only execute the Shell node after the sync job succeeds.

In the Shell node, call your Python script:

#!/bin/bash
python3 /home/campaign/trigger_campaign.py

With this setup, if the DataWorks export fails on any given day, the campaign trigger is automatically skipped. This prevents sending to stale or incomplete data.

Option B: Function Compute with OSS Trigger (more scalable)

Create a Function Compute function that contains your Python campaign logic.

Set an OSS trigger on the function that fires whenever a new file matching the pattern campaign-exports/*.csv is created in your bucket.

To schedule recurring exports automatically, you can configure a scheduling dependency on the Batch Synchronization node in DataWorks, which then cascades to your downstream campaign trigger automatically.

This is the more scalable option for teams running multiple campaign types simultaneously, because each new file drop automatically triggers the corresponding campaign function without any shared scheduling logic.


Step 7: Monitor Your Campaigns

The pipeline is only as good as your visibility into it. Set up monitoring at each layer.

DataWorks monitoring: In the O&M Centre inside DataWorks, you can view the run history of every scheduled node. Set up an alert that sends a notification if the export node fails. This ensures your team is notified before the campaign window opens.

Direct Mail reporting: The Direct Mail console provides a dashboard showing requests sent, successfully delivered emails, bounces, invalid addresses, and delivery rate per campaign. Check this after each send to identify deliverability issues early.

Teams responsible for monitoring large-scale outreach infrastructure and campaign performance also often explore platforms like Prep Away to improve their cloud operations, networking, and certification-related technical knowledge.

OSS file validation: Before the campaign trigger fires, add a simple check in your Python script to confirm the file exists in OSS and contains a non-zero number of rows:

def validate_audience_file(bucket, file_key: str) -> bool:
    """Confirm the audience file exists and has at least one subscriber."""
    try:
        meta = bucket.get_object_meta(file_key)
        if int(meta.headers.get("Content-Length", 0)) < 50:
            print(f"Warning: File {file_key} appears to be empty or too small.")
            return False
        return True
    except oss2.exceptions.NoSuchKey:
        print(f"Error: File {file_key} not found in OSS.")
        return False

Add a call to this function before populating the recipient list. If the file is missing or suspiciously small, exit early and alert your team.

Step 8: Handling Unsubscribes and Compliance

Any automated outreach pipeline needs to account for unsubscribes and opt-outs. This is non-negotiable for both legal compliance and sender reputation.

Email unsubscribes: Direct Mail supports a reply-to address on every campaign. Add an unsubscribe link to your email template that points to a simple form or landing page.

When a subscriber opts out, update their record in your MaxCompute table by setting an opt_out flag. Modify your DataWorks export query to exclude opted-out subscribers:

SELECT
    subscriber_id,
    email,
    phone,
    first_name,
    segment,
    last_purchase
FROM marketing.campaign_subscribers
WHERE segment = 'high_value'
  AND opt_out IS NULL

SMS opt-outs: Alibaba Cloud's SMS service handles STOP replies at the carrier level in supported markets.

Check the SMS service documentation for your specific region to understand how opt-out handling works and whether additional application-level handling is needed.

Frequency capping: Direct Mail does not enforce sending frequency limits on your behalf for a given recipient. If you are running multiple concurrent campaigns, add a last_contacted field to your subscriber table and filter out anyone contacted within the last seven days:

WHERE segment = 'high_value'
  AND opt_out IS NULL
  AND (last_contacted IS NULL OR last_contacted < DATEADD(GETDATE(), -7, 'dd'))

Update last_contacted after each successful campaign run to keep the frequency logic accurate.

What the Full Pipeline Looks Like

Putting it all together, here is the complete daily cycle your pipeline runs automatically:

  1. At 6:00 AM, DataWorks runs the Batch Synchronization node and exports today's audience segment from MaxCompute to an OSS CSV file.
  2. The downstream Shell node (or OSS-triggered Function Compute function) detects the new file.
  3. The Python script validates the file, populates the Direct Mail recipient list with personalised variables, and triggers the email campaign.
  4. The same script iterates through the audience and sends personalised SMS messages via the Short Message Service API.
  5. The Direct Mail console and your own monitoring layer capture delivery metrics.
  6. Any opt-outs captured during the day are written back to MaxCompute and excluded from tomorrow's export.

From the marketing team's perspective, the daily input is zero. Campaigns just go out.

From the engineering team's perspective, there is one pipeline to maintain rather than a collection of manual steps held together by spreadsheets.

Things to Think About Before Going to Production

Segment freshness: The pipeline uses yesterday's data if DataWorks runs at 6:00 AM using the previous business date.

If your audience needs to be based on real-time data, consider replacing the batch MaxCompute table with a Hologres table that supports live queries. DataWorks integrates with Hologres through the same Data Integration interface.

Secrets management: Store your AccessKey credentials as environment variables at a minimum.

For production workloads, use Alibaba Cloud RAM roles assigned to the ECS instance or Function Compute service running your script, so credentials are never stored in code or configuration files.

Teams managing production-scale cloud automation pipelines also often use resources like Cert Killer to strengthen their understanding of infrastructure security, cloud administration, and certification-focused operational practices.

Template approval timing: Direct Mail and SMS both require template pre-approval. Build your approval process into your campaign planning timeline. For recurring templates, you only need approval once.

Regional endpoints: Direct Mail and SMS have different API endpoints for different regions. Make sure the endpoint in your Python script matches the region where you activated the service.

Large audience lists: For audiences over 50,000 subscribers, consider batching your recipient list uploads rather than uploading all at once. This keeps individual API calls within time limits and makes error handling simpler.

Summary

You now have a working automated marketing campaign pipeline built entirely on Alibaba Cloud.

DataWorks handles the scheduled data extraction and transformation. OSS provides the staging layer between data and delivery. Direct Mail handles personalised email campaigns at scale.

And SMS rounds out the outreach with a second channel, both firing from the same audience data in the same daily run.

The architecture is modular. You can swap the segment filter in the DataWorks query to build a reactivation campaign, a post-purchase follow-up, or a seasonal promotion without changing any of the delivery infrastructure.

To take this further, combine it with Alibaba Cloud Quick BI to build a campaign performance dashboard that pulls delivery metrics alongside your business KPIs. Or connect your subscriber data to Alibaba Cloud PAI to score and rank your audience by purchase propensity before each send.

The pipeline you built today is the foundation for all of that.

Related Resources

All code in this article is written for illustrative purposes. Review the latest Alibaba Cloud SDK documentation for current method signatures and API parameters before deploying to production.

0 2 0
Share on

Kalpesh Parmar

22 posts | 4 followers

You may also like

Comments