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):
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.
There are several reasons to use the REST API:
REST API Requirements:
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.
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. |
# 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'
# 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"] = "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
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 ...
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'])
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 DirectMail Product Page
Alibaba DirectMail Documentation
Alibaba DirectMail Public parameters
Alibaba DirectMail Request parameters
Alibaba DirectMail Signature
Content Delivery Acceleration and Cost Reduction with P2P CDN (PCDN)
2,599 posts | 762 followers
FollowAlibaba Clouder - May 20, 2019
Alibaba Clouder - June 1, 2018
Alibaba Clouder - May 21, 2019
Alibaba Clouder - July 1, 2018
Alibaba Clouder - June 16, 2020
Alibaba Clouder - May 29, 2018
2,599 posts | 762 followers
FollowElastic and secure virtual cloud servers to cater all your cloud hosting needs.
Learn MoreLearn More
Learn More
More Posts by Alibaba Clouder