This library implements all the functions of the SER Email Relay API via Python.
- Python 3.9+
requests
requests-oauth2client
pysocks
- Active Proofpoint SER API credentials
You can install the tool using the following command directly from GitHub:
pip install git+https://github.com/pfptcommunity/ser-mail-api-python.git
Alternatively, you can install the tool using pip:
# Note: This may not work on Ubuntu 24.04:
pip install ser-mail-api
If you encounter an error similar to the following:
error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.
If you wish to install a non-Debian-packaged Python package,
create a virtual environment using python3 -m venv path/to/venv.
Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
sure you have python3-full installed.
If you wish to install a non-Debian packaged Python application,
it may be easiest to use pipx install xyz, which will manage a
virtual environment for you. Make sure you have pipx installed.
See /usr/share/doc/python3.12/README.venv for more information.
note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.
You should install pipx
or configure your own virtual environment and use the command referenced above:
pipx install ser-mail-api
- Send Emails: Easily compose and send emails with minimal code.
- Support for Attachments:
- Attach files from disk
- Encode attachments as Base64
- Send byte[] attachments
- Support for Inline HTML Content:
- Using the syntax
<img src="cid:logo">
- Content-ID can be set manually or auto-generated
- Using the syntax
- HTML & Plain Text Content: Supports both plain text and HTML email bodies.
- Recipient Management: Add
To
,CC
, andBCC
recipients with ease. - Reply Management: Add
Reply-To
addresses to redirect replies.
import json
from ser_mail_api.v1 import *
if __name__ == "__main__":
client = Client("<client_id>", "<client_secret>")
# Create a new Message object
message = Message("This is a test email", MailUser("sender@example.com", "Joe Sender"))
# Add text content body
message.add_content(Content("This is a test message", ContentType.Text))
# Add HTML content body, with embedded image
message.add_content(Content("<b>This is a test message</b><br><img src=\"cid:logo\">", ContentType.Html))
# Create an inline attachment from disk and set the cid
message.add_attachment(Attachment.from_file("C:/temp/logo.png", Disposition.Inline, "logo"))
# Add recipients
message.add_to(MailUser("recipient1@example.com", "Recipient 1"))
message.add_to(MailUser("recipient2@example.com", "Recipient 2"))
# Add CC
message.add_cc(MailUser("cc1@example.com", "CC Recipient 1"))
message.add_cc(MailUser("cc2@example.com", "CC Recipient 2"))
# Add BCC
message.add_bcc(MailUser("bcc1@example.com", "BCC Recipient 1"))
message.add_bcc(MailUser("bcc2@example.com", "BCC Recipient 2"))
# Add attachments
message.add_attachment(Attachment.from_base64("VGhpcyBpcyBhIHRlc3Qh", "test.txt"))
message.add_attachment(Attachment.from_file("C:/temp/file.csv"))
message.add_attachment(Attachment.from_bytes(b"Sample bytes", "bytes.txt", "text/plain"))
# Set one or more Reply-To addresses
message.add_reply_to(MailUser("noreply@proofpoint.com", "No Reply"))
# Send the email
result = client.send(message)
print("HTTP Response: {}/{}".format(result.get_status(), result.get_reason()))
print("Reason:", result.reason)
print("Message ID:", result.message_id)
print("Request ID:", result.request_id)
When creating attachments, the library automatically attempts to determine the MIME type. This detection is based on:
- The
filename
argument when usingAttachment.from_bytes
orAttachment.from_base64
. - The
filepath
when usingAttachment.from_file
.
If the MIME type cannot be determined, an exception will be raised.
from ser_mail_api.v1 import *
if __name__ == "__main__":
# Create an attachment from disk; the MIME type will be "application/vnd.ms-excel", and disposition will be "Disposition.Attachment"
Attachment.from_file("C:/temp/file.csv")
# This will throw an error, as the MIME type is unknown
Attachment.from_file("C:/temp/file.unknown")
# Create an attachment and specify the type information. The disposition will be "Disposition.Attachment", filename will be unknown.txt, and MIME type "text/plain"
Attachment.from_file("C:/temp/file.unknown", filename="unknown.txt")
# Create an attachment and specify the type information. The disposition will be "Disposition.Attachment", filename will be file.unknown, and MIME type "text/plain"
Attachment.from_file("C:/temp/file.unknown", mime_type="text/plain")
When creating attachments, they are Disposition.Attachment
by default. To properly reference a Content-ID (e.g.,
<img src="cid:logo">
), you must explicitly set the attachment disposition to Disposition.Inline
.
If the attachment type is set to Disposition.Inline
, a default unique Content-ID will be generated.
The example below demonstrates how to create inline content with a dynamically generated Content-ID inside an HTML message body.
from ser_mail_api.v1 import *
if __name__ == "__main__":
client = Client("<client_id>", "<client_secret>")
# Create a new Message object
message = Message("This is a test email", MailUser("sender@example.com", "Joe Sender"))
# Create an inline attachment with dynamically generated Content-ID
logo = Attachment.from_file("C:/temp/logo.png", Disposition.Inline)
# Add HTML content body, with embedded image
message.add_content(Content(f"<b>This is a test message</b><br><img src=\"cid:{logo.cid}\">", ContentType.Html))
# Add the attachment to the message
message.add_attachment(logo)
# Add recipients
message.add_to(MailUser("recipient1@example.com", "Recipient 1"))
# Send the email
result = client.send(message)
print("HTTP Response: {}/{}".format(result.get_status(), result.get_reason()))
print("Reason:", result.reason)
print("Message ID:", result.message_id)
print("Request ID:", result.request_id)
The example below demonstrates how to create inline content with a custom Content-ID inside an HTML message body.
from ser_mail_api.v1 import *
if __name__ == "__main__":
client = Client("<client_id>", "<client_secret>")
# Create a new Message object
message = Message("This is a test email", MailUser("sender@example.com", "Joe Sender"))
# Add an inline attachment with a custom Content-ID
message.add_attachment(Attachment.from_file("C:/temp/logo.png", Disposition.Inline, "logo"))
# Add HTML content body, with embedded image
message.add_content(Content(f"<b>This is a test message</b><br><img src=\"cid:logo\">", ContentType.Html))
# Add recipients
message.add_to(MailUser("recipient1@example.com", "Recipient 1"))
# Send the email
result = client.send(message)
print("HTTP Response: {}/{}".format(result.get_status(), result.get_reason()))
print("Reason:", result.reason)
print("Message ID:", result.message_id)
print("Request ID:", result.request_id)
Socks5 Proxy Example:
from ser_mail_api.v1 import *
if __name__ == '__main__':
client = Client("<client_id>", "<client_secret>")
credentials = "{}:{}@".format("proxyuser", "proxypass")
client._session.proxies = {'https': "{}://{}{}:{}".format('socks5', credentials, '<your_proxy>', '8128')}
HTTP Proxy Example (Squid):
from ser_mail_api.v1 import *
if __name__ == '__main__':
client = Client("<client_id>", "<client_secret>")
credentials = "{}:{}@".format("proxyuser", "proxypass")
client._session.proxies = {'https': "{}://{}{}:{}".format('http', credentials, '<your_proxy>', '3128')}
from ser_mail_api.v1 import *
if __name__ == '__main__':
client = Client("<client_id>", "<client_secret>")
# Timeout in seconds, connect timeout
client.timeout = 600
# Timeout advanced, connect / read timeout
client.timeout = (3.05, 27)
There is a known issue where empty file content results in a 400 Bad Request error.
{
"content": "",
"disposition": "attachment",
"filename": "empty.txt",
"id": "1ed38149-70b2-4476-84a1-83e73913d43c",
"type": "text/plain"
}
🔹 API Response:
Status Code: 400/BadRequest
Message ID:
Reason: attachments[0].content is required
Request ID: fe9a1acf60a20c9d90bed843f6530156
Raw JSON: {"request_id":"fe9a1acf60a20c9d90bed843f6530156","reason":"attachments[0].content is required"}
This issue has been reported to Proofpoint Product Management.
- The Proofpoint API currently does not support empty file attachments.
- If an empty file is sent, you will receive a 400 Bad Request error.
For more information, refer to the official Proofpoint Secure Email Relay API documentation:
API Documentation