All Products
Search
Document Center

Direct Mail:SMTP call example for Python 3.6 or later

Last Updated:Jun 02, 2026

Send emails from Python 3.6 or later using the Direct Mail SMTP endpoint.

Prerequisites

Before you begin, ensure that you have:

  • A sender address created in the Direct Mail console

  • An SMTP password generated for that sender address

  • Python 3.6 or later installed

Warning: Do not hardcode your SMTP password in source code. Store credentials in environment variables or a secrets manager and read them at runtime.

How it works

The example uses Python's built-in smtplib and email libraries to:

  1. Build a MIMEMultipart message with To, Cc, Bcc, Reply-To, and Return-Path headers.

  2. Attach an HTML body (plain-text and file attachments are available as commented-out options).

  3. Connect to smtpdm.aliyun.com on port 80 (or port 465 for SSL) and authenticate with the sender address and SMTP password.

  4. Send to all recipients in a single call and handle SMTP errors by type.

Sample code

# -*- coding:utf-8 -*-
import smtplib
import email
# import json
# import base64
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# from email.mime.image import MIMEImage
# from email.mime.base import MIMEBase
# from email.mime.application import MIMEApplication
from email.header import Header
from email.utils import formataddr
# import urllib.request
# import ssl

# The sender address created in the Direct Mail console.
username = 'XXXXXXXX'
# The SMTP password for the sender address.
password = 'XXXXXXXX'
# The reply-to address. When a recipient replies, the email goes to this address
# instead of the sender address, which does not receive replies.
replyto = 'XXXXXXXX'
# The To recipient addresses (displayed in the email header).
rcptto = ['address1@example.net', 'address2@example.net']
# The Cc recipient addresses (displayed in the email header).
rcptcc = ['address3@example.net', 'address4@example.net']
# The Bcc recipient addresses (hidden from recipients but receive the email).
rcptbcc = ['address5@example.net', 'address6@example.net']
# All recipients. A single send cannot exceed 60 addresses.
receivers = rcptto + rcptcc + rcptbcc

# Build the message.
msg = MIMEMultipart('alternative')
msg['Subject'] = Header('Custom email subject')
msg['From'] = formataddr(["Custom sender nickname", username])  # Nickname + sender address (or proxy sender)
msg['To'] = ",".join(rcptto)
msg['Cc'] = ",".join(rcptcc)
msg['Reply-to'] = replyto  # Address that receives replies. The recipient must support standard protocols.
msg['Return-Path'] = 'test@example.net'  # Address that receives bounces. The recipient must support standard protocols.
msg['Message-id'] = email.utils.make_msgid()  # Unique identifier per RFC 5322, e.g. <uniquestring@example.com>.
msg['Date'] = email.utils.formatdate()

# To enable email tracking, uncomment the following section.
# A tag is required and must be created in the console at least 10 minutes before use.
# tagName = 'xxxxxxx'
#
# trace = {
#     "OpenTrace": '1',  # Enable open tracking.
#     "LinkTrace": '1',  # Enable link-click tracking.
#     "TagName": tagName
# }
# jsonTrace = json.dumps(trace)
# base64Trace = str(base64.b64encode(jsonTrace.encode('utf-8')), 'utf-8')
# msg.add_header("X-AliDM-Trace", base64Trace)

# To add a plain-text fallback for mail clients that do not render HTML, uncomment the following.
# textplain = MIMEText('Custom plain text part', _subtype='plain', _charset='UTF-8')
# msg.attach(textplain)

# HTML body.
texthtml = MIMEText('Custom HTML hypertext part', _subtype='html', _charset='UTF-8')
msg.attach(texthtml)

# To attach a local file, uncomment the following.
# files = [r'C:\Users\Downloads\test1.jpg', r'C:\Users\Downloads\test2.jpg']
# for t in files:
#     filename = t.rsplit('/', 1)[1]
#     part_attach1 = MIMEApplication(open(t, 'rb').read())
#     part_attach1.add_header('Content-Disposition', 'attachment', filename=filename)
#     msg.attach(part_attach1)

# To attach a file from a URL (e.g., an OSS object), uncomment the following.
# files = [r'https://example.oss-cn-shanghai.aliyuncs.com/xxxxxxxxxxx.png']
# for t in files:
#     filename = t.rsplit('/', 1)[1]
#     response = urllib.request.urlopen(t)
#     part_attach1 = MIMEApplication(response.read())
#     part_attach1.add_header('Content-Disposition', 'attachment', filename=filename)
#     msg.attach(part_attach1)

# Send the email.
try:
    # To use SSL, replace the line below with:
    # client = smtplib.SMTP_SSL('smtpdm.aliyun.com', 465)
    #
    # If the SSL handshake fails on Python 3.10 or 3.11, use a custom context:
    # ctxt = ssl.create_default_context()
    # ctxt.set_ciphers('DEFAULT')
    # client = smtplib.SMTP_SSL('smtpdm.aliyun.com', 465, context=ctxt)

    # Standard SMTP on port 80 (port 25 is also supported).
    client = smtplib.SMTP('smtpdm.aliyun.com', 80)
    client.set_debuglevel(0)  # Set to 1 to enable debug output.
    # The sender address and authentication address must match.
    client.login(username, password)
    client.sendmail(username, receivers, msg.as_string())
    client.quit()
    print('Email sent successfully!')
except smtplib.SMTPConnectError as e:
    print('Failed to send email. Connection failed:', e.smtp_code, e.smtp_error)
except smtplib.SMTPAuthenticationError as e:
    print('Failed to send email. Authentication error:', e.smtp_code, e.smtp_error)
except smtplib.SMTPSenderRefused as e:
    print('Failed to send email. Sender refused:', e.smtp_code, e.smtp_error)
except smtplib.SMTPRecipientsRefused as e:
    print('Failed to send email. Recipients refused:', e.smtp_code, e.smtp_error)
except smtplib.SMTPDataError as e:
    print('Failed to send email. Data reception refused:', e.smtp_code, e.smtp_error)
except smtplib.SMTPException as e:
    print('Failed to send email:', str(e))
except Exception as e:
    print('Exception while sending email:', str(e))

Key parameters

Parameter

Description

username

The sender address created in the Direct Mail console. Used as the SMTP login and the sendmail FROM argument — all three must match.

password

The SMTP password for the sender address. This is not your Alibaba Cloud account password.

replyto

The address that receives replies. Independent of the sender address; the sender address itself does not receive replies.

rcptto / rcptcc / rcptbcc

To, Cc, and Bcc recipient lists. The combined total across all three cannot exceed 60 addresses per send.

smtpdm.aliyun.com

The Direct Mail SMTP endpoint. Use port 80 or 25 for plain SMTP, or port 465 for SSL.

Optional features

The example includes commented-out code for the following features. Uncomment the relevant sections to enable them.

  • Plain-text body: Add a text/plain alternative part alongside the HTML body. Mail clients that do not render HTML fall back to the plain-text part.

  • Local file attachment: Read a file from disk and attach it using MIMEApplication.

  • URL attachment: Fetch a file from a URL (for example, an OSS object) and attach it without saving to disk first.

  • SSL connection: Replace smtplib.SMTP with smtplib.SMTP_SSL on port 465. If the SSL handshake fails on Python 3.10 or 3.11, create a custom SSL context with ctxt.set_ciphers('DEFAULT').

  • Email tracking: Set the X-AliDM-Trace header with a Base64-encoded JSON payload to enable open tracking and link-click tracking. Requires a tag created in the Direct Mail console (available 10 minutes after creation).