Skip to content

Releases: pfptcommunity/ser-mail-api-python

v2.0.6

04 Sep 14:36
fba3cdd

Choose a tag to compare

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

03 Sep 01:36
6641b5f

Choose a tag to compare

Minor change

  • Fix type hints for from_bytes
  • Update README.md

Added

  • Add optional region selection via Region.US, Region.EU, and Region.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

06 Feb 23:27
1d75868

Choose a tag to compare

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_from as optional to Message constructor.
    • Message("Body", MailUser("sender@example.com", "Joe Sender"), MailUser("sender@example.com", "Header From"))
  • Add MessageHeaders object to deal with from address and future properties
  • Add Message.header_from to return MessageHeader.header_from property is MailUser

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.headers to return MessageHeader object which can be None since it's optional.
  • Fixed Message.header_sender to return MessageHeader.header_from property is MailUser

Full Changelog: v2.0.1...v2.0.3

v2.0.0

05 Feb 05:14
31c5bed

Choose a tag to compare

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

  • FileAttachment has been removed. Please use the factory function Attachment.from_file()
  • BinaryAttachment has been removed. Please use the factory functions Attachment.from_bytes()
  • Building a base64 attachment can still be done via Attachment() , however; the factory Attachment.from_base64() is recommended.

Added error handling

  • MailUser will now validate the email address is valid and throw ValueError if the address is invalid.
  • Attachment will 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 filename argument when using Attachment.from_bytes or Attachment.from_base64.
  • The filepath when using Attachment.from_file.
  • It's possible to override filename and mimetype for 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-ID can now be set directly from Attachment() as well as Attachment.from_file(), Attachment.from_bytes(), and Attachment.from_base64()
  • Content-ID is only set if the attachment disposition is set to Disposition.Inline otherwise it's set to None and 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
  • HTML & Plain Text Content: Supports both plain text and HTML email bodies.
  • Recipient Management: Add To, CC, and BCC recipients with ease.
  • Reply Management: Add Reply-To addresses to redirect replies.

Requirements

  • Python 3.9+
  • requests
  • requests-oauth2client
  • pysocks
  • 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 filename argument when using Attachment.from_bytes or Attachment.from_base64.
  • The filepath when using Attachment.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...

Read more

Proofpoint Secure Email Relay Mail API Package

31 Jan 04:22
10b8da8

Choose a tag to compare

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

30 Jan 04:03
97cae46

Choose a tag to compare

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

27 Jan 17:40
5455909

Choose a tag to compare

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

27 Jan 04:54
df55d9a

Choose a tag to compare

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 = True

Full Changelog: v1.0.0...v1.0.1

Proofpoint Secure Email Relay Mail API Package

17 Jan 17:31
ffb5ab3

Choose a tag to compare

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