Releases: pfptcommunity/ser-mail-api-python
v2.0.6
Added
- Added Canada region selections via
Region.CA
from ser_mail_api.v1 import *
if __name__ == '__main__':
# Default region when not specified is Region.US
client = Client("<client_id>", "<client_secret>", Region.CA)Full Changelog: v2.0.5...v2.0.6
v2.0.5
Minor change
- Fix type hints for
from_bytes - Update README.md
Added
- Add optional region selection via
Region.US,Region.EU, andRegion.AU. The default region is US when no value is specified.
from ser_mail_api.v1 import *
if __name__ == '__main__':
# Default region when not specified is Region.US
client = Client("<client_id>", "<client_secret>", Region.US)Full Changelog: v2.0.3...v2.0.5
v2.0.3
Minor change
- Update pyproject.toml to reflect correct vendor name.
- Refresh token 5 minutes before expiration time as defined in the SER API guide.
- Improve behavior of Message.header_from setter. If None is passed the HeaderFrom is removed.
Added
- Add
header_fromas optional to Message constructor.Message("Body", MailUser("sender@example.com", "Joe Sender"), MailUser("sender@example.com", "Header From"))
- Add
MessageHeadersobject to deal with from address and future properties - Add
Message.header_fromto returnMessageHeader.header_fromproperty isMailUser
Changed behavior
- Modified the
to_json()function to only generate properties if they have data assigned.
Bug fixes
- Improperly exposed class values which can lead to misuse.
- Fixed
Message.headersto returnMessageHeaderobject which can beNonesince it's optional. - Fixed
Message.header_senderto returnMessageHeader.header_fromproperty isMailUser
Full Changelog: v2.0.1...v2.0.3
v2.0.0
UPGRADE WARNING
Upgrading to this version from version 1.0.X to 2.0.0+ will cause existing code to break. Please see the full usage examples for more details.
Major changes
FileAttachmenthas been removed. Please use the factory functionAttachment.from_file()BinaryAttachmenthas been removed. Please use the factory functionsAttachment.from_bytes()- Building a base64 attachment can still be done via
Attachment(), however; the factoryAttachment.from_base64()is recommended.
Added error handling
MailUserwill now validate the email address is valid and throwValueErrorif the address is invalid.Attachmentwill validate content, filename, mime_type are not empty.
Usability improvements
When creating attachments, the library automatically attempts to determine the MIME type. This detection is based on:
- The
filenameargument when usingAttachment.from_bytesorAttachment.from_base64. - The
filepathwhen usingAttachment.from_file. - It's possible to override
filenameandmimetypefor unknown file attachments.
Setting custom Content-ID or dynamic Content-ID can be used in HTML using cid e.g. <img src="cid:logo">:
- Custom
Content-IDcan now be set directly fromAttachment()as well asAttachment.from_file(),Attachment.from_bytes(), andAttachment.from_base64() Content-IDis only set if the attachment disposition is set toDisposition.Inlineotherwise it's set toNoneand will not be present in the JSON POST.
Features
- 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, andBCCrecipients with ease. - Reply Management: Add
Reply-Toaddresses to redirect replies.
Requirements
- Python 3.9+
requestsrequests-oauth2clientpysocks- Active Proofpoint SER API credentials
Quick Start
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)Attachment MIME Type Deduction Behavior
When creating attachments, the library automatically attempts to determine the MIME type. This detection is based on:
- The
filenameargument when usingAttachment.from_bytesorAttachment.from_base64. - The
filepathwhen 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")Inline Attachments and Content-IDs
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.
Using Dynamically Generated Content-ID
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)Setting a Custom Content-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)Proxy Support
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')}HTTP Timeout Settings
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)Known Issues
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.
Limitations
- Currently, empty attachments are not supported by the API.
- No other known limitations.
Additional Resources
For more information, refer to the official Proofpoint Secure Email Relay API documentation:
[API Documentation](https://api-docs.ser.proofpoint.c...
Proofpoint Secure Email Relay Mail API Package
Please be aware that this function change will break existing code, so please be sure to make the change from add_recipient to add_to before upgrading your library.
Minor changes
- Modified example code and readme
- Changed Client.add_recipient to Client.add_to to better model the underlying REST API.
Full Changelog: v1.0.3...v1.0.4
Proofpoint Secure Email Relay Mail API Package
This change is minor to preparing to matching features of the C# version of this library.
Minor changes
- Modified example code
- Changed StreamAttachment to BinaryAttachment since streams are not really being handled
- Change Enum enum value naming for Disposition and Content
Full Changelog: v1.0.2...v1.0.3
Proofpoint Secure Email Relay Mail API Package
Minor changes
- Remove error logger for web service calls, this can be handled by catching web exceptions
- Moved to relative paths for init.py files and added missing init.py files
- Migrated to relative paths where possible
Full Changelog: v1.0.1...v1.0.2
Proofpoint Secure Email Relay Mail API Package
New features
- Proxy support SOCKS / HTTP (required testing before documenting the feature)
- Custom HTTP handler to allow raise for status if desired.
from ser_mail_api.v1 import *
if __name__ == "__main__":
client = Client(api_key_data.get("client_id"), api_key_data.get("client_secret"))
client.error_handler.raise_for_status = TrueFull Changelog: v1.0.0...v1.0.1
Proofpoint Secure Email Relay Mail API Package
Library implements all the functions of the SER Email Relay User Management API via Python.
Requirements:
- Python 3.9+
- requests
- requests-oauth2client
- pysocks
Installing the Package
You can install the tool using the following command directly from Github.
pip install git+https://github.com/pfptcommunity/ser-mail-api-python.git
or can install the tool using pip.
# When testing on Ubuntu 24.04 the following will not work:
pip install ser-mail-api
If you see 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 use install pipx or you can configure your own virtual environment and use the command referenced above.
pipx install ser-mail-api
Creating an API client object
from ser_mail_api.v1 import *
if __name__ == "__main__":
client = Client("<client_id>","<client_secret>")Sending an Email Message
import json
from ser_mail_api.v1 import *
if __name__ == "__main__":
# Load API key
with open("../ser.api_key", "r") as api_key_file:
api_key_data = json.load(api_key_file)
client = Client(api_key_data.get("client_id"), api_key_data.get("client_secret"))
# Create a new Message object
message = Message("This is a test email", MailUser("sender@proofpoint.com", "Joe Sender"))
# Add content body
message.add_content(Content("This is a test message", ContentType.TEXT))
message.add_content(Content("<b>This is a test message</b>", ContentType.HTML))
# Add Recipients
message.add_recipient(MailUser("recipient1@proofpoint.com", "Recipient 1"))
message.add_recipient(MailUser("recipient2@proofpoint.com", "Recipient 2"))
# Add CC
message.add_cc(MailUser("cc1@proofpoint.com", "Carbon Copy 1"))
message.add_cc(MailUser("cc2@proofpoint.com", "Carbon Copy 2"))
# Add BCC
message.add_bcc(MailUser("bcc1@proofpoint.com", "Blind Carbon Copy 1"))
message.add_bcc(MailUser("bcc2@proofpoint.com", "Blind Carbon Copy 2"))
# Add Base64 Encoded Attachment
message.add_attachment(Attachment("VGhpcyBpcyBhIHRlc3Qh", Disposition.ATTACHMENT, "test.txt", "text/plain"))
# Add File Attachment from Disk, if Disposition is not passed, the default is Disposition.ATTACHMENT
message.add_attachment(FileAttachment(r"C:\temp\file.csv", Disposition.ATTACHMENT))
# In the following example, we will create a byte stream from a string. This byte stream is converted
# to base64 encoding within the StreamAttachment object
text = "This is a sample text stream."
# Convert the string into bytes
byte_stream = text.encode("utf-8")
# Add Byte Stream as Attachment, if Disposition is not passed, the default is Disposition.ATTACHMENT
message.add_attachment(StreamAttachment(byte_stream,"byte_stream.txt", "text/plain", Disposition.ATTACHMENT))
result = client.send(message)
print("HTTP Status", result.get_status())
print("HTTP Reason", result.get_reason())
print("Reason:", result.reason)
print("Message ID:", result.message_id)
print("Request ID:", result.request_id)The following JSON data is a dump of the message object based on the code above.
{
"attachments": [
{
"content": "VGhpcyBpcyBhIHRlc3Qh",
"disposition": "attachment",
"filename": "test.txt",
"id": "d10205cf-a0a3-4b9e-9a57-253fd8e1c7df",
"type": "text/plain"
},
{
"content": "77u/IlVzZXIiLCJTZW50Q291bnQiLCJSZWNlaXZlZENvdW50Ig0KIm5vcmVwbHlAcHJvb2Zwb2ludC5jb20sIGxqZXJhYmVrQHBmcHQuaW8iLCIwIiwiMCINCg==",
"disposition": "attachment",
"filename": "file.csv",
"id": "f66487f5-57c2-40e0-9402-5723a85c0df0",
"type": "application/vnd.ms-excel"
},
{
"content": "VGhpcyBpcyBhIHNhbXBsZSB0ZXh0IHN0cmVhbS4=",
"disposition": "attachment",
"filename": "byte_stream.txt",
"id": "bc67d5fa-345a-4436-9979-5efa68223520",
"type": "text/plain"
}
],
"content": [
{
"body": "This is a test message",
"type": "text/plain"
},
{
"body": "<b>This is a test message</b>",
"type": "text/html"
}
],
"from": {
"email": "sender@proofpoint.com",
"name": "Joe Sender"
},
"headers": {
"from": {
"email": "sender@proofpoint.com",
"name": "Joe Sender"
}
},
"subject": "This is a test email",
"tos": [
{
"email": "recipient1@proofpoint.com",
"name": "Recipient 1"
},
{
"email": "recipient2@proofpoint.com",
"name": "Recipient 2"
}
],
"cc": [
{
"email": "cc1@proofpoint.com",
"name": "Carbon Copy 1"
},
{
"email": "cc2@proofpoint.com",
"name": "Carbon Copy 2"
}
],
"bcc": [
{
"email": "bcc1@proofpoint.com",
"name": "Blind Carbon Copy 1"
},
{
"email": "bcc2@proofpoint.com",
"name": "Blind Carbon Copy 2"
}
],
"replyTos": []
}Limitations
There are no known limitations.
For more information please see: https://api-docs.ser.proofpoint.com/docs/email-submission
Full Changelog: https://github.com/pfptcommunity/ser-mail-api-python/commits/v1.0.0