×
Community Blog Cloud DevOps Cookbook Part 4 – API Gateway and DirectMail with Python 3.0

Cloud DevOps Cookbook Part 4 – API Gateway and DirectMail with Python 3.0

In this article, we will be exploring Alibaba Cloud DirectMail for DevOps using REST API in Python.

By John Hanley, Alibaba Cloud Tech Share Author. Tech Share is Alibaba Cloud’s incentive program to encourage the sharing of technical knowledge and best practices within the cloud community.

My favorite language for rapid prototyping and learning a new cloud service is Python. Python makes most tasks so simple that I can test a new API in minutes. This allows me to focus on learning and not on the details of how an API works in a particular language. However, for enterprise level code where a mistake could cost millions of dollars, I use C++.

The Alibaba SDK for DirectMail does not support Python. It does support PHP, Java and C#. A very common use case for DirectMail is delivering emails for dynamic and static websites. For dynamic websites PHP is perfect. For static websites, integrating Function Compute with DirectMail is perfect. However, Function Compute does not support PHP. This means that you develop in one language on your desktop and another in the cloud.

This led me to the idea of using the DirectMail REST API. This means that I can use Python and learn how to use the REST API and in particular how to create the Signature that is not documented very well. Nothing like picking a difficult challenge to really learn a new service.

DirectMail supports three types of interfaces (not all are available in all languages):

  1. Alibaba REST API
  2. Alibaba Cloud SDK
  3. Alibaba SMTP Interface

In the previous article, I showed an example of using the SDK (Function Compute) in Python. In this article I will focus on the DirectMail REST API and include a real working example in Python.

Alibaba Cloud DirectMail REST API

There are several reasons to use the REST API:

  1. Understand the low level api, parameters and errors.
  2. Smaller code space.
  3. Faster load and execution time.
  4. Fewer dependencies.
  5. Not require that the SDK libraries be installed on the target system.
  6. The SDK is not available in your target language.

REST API Requirements:

  1. You need to understand HTTPS, HTTP headers and HTTP bodies.
  2. Understand the differences between HTTP HEAD, DELETE, GET, POST and PUT methods.
  3. Understand how to send data for each HTTP method.
  4. Understand the API parameters that must be included with each HTTP method.
  5. Understand how to sign the HTTP request.

Alibaba Cloud Public Parameters

Link to Alibaba documentation on DirectMail Public parameters.

Name Type Required Description
Format String No Type of response values. JSON and XML are supported. XML is the default format. The example will use JSON.
Version String Yes API version number. The format is in the form of YYYY-MM-DD. The version number is 2015-11-23 if RegionID is cn-hangzhou. The version number is 2017-06-22 if RegionID is not cn-hangzhou, such as ap-southeast-1. The example will use 2017-06-22.
AccessKeyId String Yes The AccessKeyId that Alibaba Cloud issues to a user for accessing services.
SecurityToken String Depends If you are using an access key defined for a user, then this parameters is not required. If you are using a role, then this is the security token passed to the function as part of the context.credentials object.
Signature String Yes The signature result string. See Signature for the signature calculation method.
SignatureMethod String Yes The signature method. HMAC-SHA1 is currently supported.
Timestamp String Yes The request's timestamp. The date format follows the ISO8601standard and adopts the UTC time. Format: YYYY-MM-DDThh:mm:ssZ. Example: 2015-11-23T04:00:00Z (for 12:00:00 on November 23, 2015, Beijing time).
SignatureVersion String Yes Signature algorithm version. The current version is 1.0.
SignatureNonce String Yes A unique random number. It is used to prevent replay attacks. Different random numbers must be used for different requests.
RegionId String Yes Data center information. cn-hangzhou, ap-southeast-1 and ap-southeast-2 are currently supported.

Comments on the above parameters:

SignatureNonce: This parameter had me guessing for a while. I assumed at first that this was a salt value used during HMAC_SHA1 signing. Turns out that this is a string that is created by uuid.uuid4() and included in the headers. If you repeat this string value in a subsequent command, the command will be rejected.

AccessKeyId: Create a new RAM user with permissions just for DirectMail. You will then need both the Access Key and the Access Key Secret for REST API.

Timestamp: Is important that your system's date and time are correct. If this time is different from the Alibaba service, then the request will be rejected. If possible use a time service such as NTP for your system.

Alibaba Cloud DirectMail Request Parameters

Link to Alibaba documentation on DirectMail Request parameters.

Name Type Required Description
Action String Required Operation interface name, a system required parameter. Value: SingleSendMail.
AccountName String Required The sender address configured in the console.
ReplyToAddress Boolean Required The reply-to address (The status must be “verified”) configured in the console.
AddressType Number Required Value range: 0-1. 0 indicates a random account, and 1 indicates the sender address.
ToAddress String Required The recipient address. Multiple addresses can be separated by commas, and a maximum of 100 addresses are supported.
FromAlias String Optional Sender nickname. The length of the nickname must be shorter than 15 characters. For example, the sender nickname is set to Daisy, and the sender address is test@example.com. Recipients see the address of Daisy test@example.com.
Subject String Optional Subject (recommended).
HtmlBody String Optional Email body in HTML.
TextBody String Optional Email body in text.
ClickTrace String Optional Value range: 0-1. 1 indicates enabling recipient tracking. 0 indicates that not enabling recipient tracking. The default value of this parameter is 0.

Calculating the correct dates

# Correctly formatted date and time
now = datetime.datetime.utcnow()

# Date used by the HTTP "Date" header
date = now.strftime("%a, %d %b %Y %H:%M:%S GMT")

# Date used by the API parameter "Timestamp"
utc = now.isoformat(timespec='seconds') + 'Z'

The Host and Endpoint for this example (Singapore --> ap-southeast-1)

# HTTP Host header
host = "dm.ap-southeast-1.aliyuncs.com"

# URL for POST
url = "https://dm.ap-southeast-1.aliyuncs.com/"

Building the parameter list for Public parameters

parameters = {}

# Add the DirectMail public request parameters
parameters["Format"] = "json"
parameters["AccessKeyId"] = credentials['AccessKey']
parameters["SignatureMethod"] = "HMAC-SHA1"
parameters["SignatureType"] = ""
parameters["SignatureVersion"] = "1.0"
parameters["SignatureNonce"] = get_uuid()
parameters["Timestamp"] = utc
parameters["Version"] = "2017-06-22"
parameters["RegionId"] = "ap-southeast-1"

Building the parameter list for Request parameters

# Add parameters that are always set
parameters["Action"] = "SingleSendMail"
parameters["AddressType"] = "1"
parameters["ReplyToAddress"] = "true"

# Add the DirectMail API parameters
parameters["AccountName"] = dm_account
parameters["FromAlias"] = dm_alias
parameters["ToAddress"] = to_list
parameters["Subject"] = subject
parameters["HtmlBody"] = body
parameters["textBody"] = body_text

Build the request string for the signing process

def build_request_string(table):
    """ Build canonical list """
    items = sorted(iter(table.items()), key=lambda d: d[0])
    enc = my_urlencode(items)
    return enc

Notes on the request string:

The request string parameters must be sorted first. Then the string is url encoded. This means that each key/value pair is appended with the & character.

The final result looks like this example but much longer:

AccessKeyId=LTAIQlgy6erobert&AccountName=test%40test.com&Action=SingleSendMail ...

Create the Signature from the Request String

Link to Alibaba documentation on DirectMail Signature.

# Build the request string for the signing process
params = build_request_string(parameters)

# Create the actual string to sign (method = "POST")
stringToSign = method + "&%2F&" + percentEncode(params)

Signature = sign(stringToSign, credentials['AccessKeySecret'])

Complete Program to Call DirectMail REST API and Send an Email

Download sendEmail.zip.

############################################################
# Version 1.00
# Date Created: 2018-05-26
# Last Update:  2018-05-27
# https://www.neoprime.io
# Copyright (c) 2018, NeoPrime, LLC
############################################################

""" Alibaba Cloud DirectMail REST API With Signing """

import base64
import datetime
import hmac
import hashlib
import urllib
import uuid
import json
import requests

# My library for processing Alibaba Cloud Services (ACS) credentials
import mycred_acs

# From the DirectMail Console
dm_account = "<enter your value here>"
dm_alias = "<enter your value here>"

debug = 0

def set_connection_logging():
    """ Enable HTTP connection logging """
    if debug is 0:
        return

    import logging
    import http.client as http_client

    http_client.HTTPConnection.debuglevel = 1

    # You must initialize logging, otherwise you'll not see debug output.
    logging.basicConfig()
    logging.getLogger().setLevel(logging.DEBUG)
    requests_log = logging.getLogger("requests.packages.urllib3")
    requests_log.setLevel(logging.DEBUG)
    requests_log.propagate = True
    return

def get_uuid():
    """ return a uuid as a signing nonce """
    return str(uuid.uuid4())

def percentEncode(path):
    """ Encode a URL """
    res = urllib.parse.quote(path)
    res = res.replace('+', '%20')
    res = res.replace('*', '%2A')
    res = res.replace('%7E', '~')
    return res

def my_urlencode(query):
    """ Encode a Query """
    res = urllib.parse.urlencode(query)
    res = res.replace('+', '%20')
    res = res.replace('*', '%2A')
    res = res.replace('%7E', '~')
    return res

def build_request_string(table):
    """ Build canonical list """
    items = sorted(iter(table.items()), key=lambda d: d[0])
    enc = my_urlencode(items)
    return enc

def sign(string, secret):
    """ Sign REST API Request """
    nsecret = secret + '&'

    h = hmac.new(
        bytes(nsecret, "utf-8"),
        bytes(string, "utf-8"),
        hashlib.sha1)

    #sig = base64.b64encode(h.digest())
    sig = str(base64.encodebytes(h.digest()).strip(), "utf-8")
    return sig

def sendEmail(credentials, subject, body, body_text, to_list):
    """ Send an email using Alibaba DirectMail """
    # HTTP Method
    method = "POST"

    # Correctly formatted date and time
    now = datetime.datetime.utcnow()
    date = now.strftime("%a, %d %b %Y %H:%M:%S GMT")
    utc = now.isoformat(timespec='seconds') + 'Z'

    # HTTP Host header
    host = "dm.ap-southeast-1.aliyuncs.com"

    # URL for POST
    url = "https://dm.ap-southeast-1.aliyuncs.com/"

    parameters = {}

    # Add the DirectMail public request parameters
    parameters["Format"] = "json"
    parameters["AccessKeyId"] = credentials['AccessKey']
    parameters["SignatureMethod"] = "HMAC-SHA1"
    parameters["SignatureType"] = ""
    parameters["SignatureVersion"] = "1.0"
    parameters["SignatureNonce"] = get_uuid()
    parameters["Timestamp"] = utc
    #parameters["Version"] = "2015-11-23"
    parameters["Version"] = "2017-06-22"
    parameters["RegionId"] = "ap-southeast-1"

    # Add parameters that are always set
    parameters["Action"] = "SingleSendMail"
    parameters["AddressType"] = "1"
    parameters["ReplyToAddress"] = "true"

    # Add the DirectMail API parameters
    parameters["AccountName"] = dm_account
    parameters["FromAlias"] = dm_alias
    parameters["ToAddress"] = to_list
    parameters["Subject"] = subject
    parameters["HtmlBody"] = body
    parameters["textBody"] = body_text

    # Build the request string for the signing process
    params = build_request_string(parameters)

    # Create the actual string to sign
    stringToSign = method + "&%2F&" + percentEncode(params)

    #print("String to Sign")
    #print(stringToSign)

    Signature = sign(stringToSign, credentials['AccessKeySecret'])
    #print("Signature", Signature)

    parameters["Signature"] = Signature

    headers = {
        'Date': date,
        'Host': host
    }

    set_connection_logging()

    print("Sending Email to", parameters["ToAddress"])
    r = requests.post(url, data=parameters, headers=headers)

    if r.status_code != 200:
        print("Error: Email Send Failed:", r.status_code)
        print(r.text)
        return 1

    #print(r.text)

    result = json.loads(r.text)
    print("Success: Request ID:", result['RequestId'])

    return 0

# Load the Alibaba Cloud Credentials (AccessKey)
cred = mycred_acs.LoadCredentials()

dm_subject = "Welcome to Alibaba Cloud DirectMail"

dm_body = "<h2>Welcome to Alibaba Cloud DirectMail<h2>You are receiving this email as part of a test program.<br /><br />Click for <a href='https://www.neoprime.io/info/alibaba/'>more information<a>.<br /><br /><a href='https://www.alibabacloud.com/'><img src='https://www.neoprime.io/info/alibaba/img/alibaba-600x263.png' alt='Alibaba' width='700'><a>"

dm_body_text = "Welcome to Alibaba Cloud DirectMail\nYou are receiving this email as part of a test program."

dm_to_list = "test@test.com, test2@test.com

sendEmail(cred, dm_subject, dm_body, dm_body_text, dm_to_list)

Execute with Python 3.x: python sendEmail.py

Alibaba Documentation

Alibaba DirectMail Product Page
Alibaba DirectMail Documentation
Alibaba DirectMail Public parameters
Alibaba DirectMail Request parameters
Alibaba DirectMail Signature

0 1 0
Share on

Alibaba Clouder

2,605 posts | 747 followers

You may also like

Comments