mailers


Namemailers JSON
Version 3.0.5 PyPI version JSON
download
home_pagehttps://github.com/alex-oleshkevich/mailers
SummaryEmail delivery for asyncio.
upload_time2024-04-23 17:30:40
maintainerNone
docs_urlNone
authoralex.oleshkevich
requires_python<4.0,>=3.8
licenseMIT
keywords asyncio email mailer mail
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Mailers for asyncio

![PyPI](https://img.shields.io/pypi/v/mailers)
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/alex-oleshkevich/mailers/Lint%20and%20test)
![GitHub](https://img.shields.io/github/license/alex-oleshkevich/mailers)
![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/pypi/mailers)
![PyPI - Downloads](https://img.shields.io/pypi/dm/mailers)
![GitHub Release Date](https://img.shields.io/github/release-date/alex-oleshkevich/mailers)
![Lines of code](https://img.shields.io/tokei/lines/github/alex-oleshkevich/mailers)

## Features

* fully typed
* full utf-8 support
* async and sync sending
* pluggable transports
* multiple built-in transports including: SMTP, file, null, in-memory, streaming, and console.
* message preprocessors
* embeddables
* attachments (with async and sync interfaces)
* message signing via Signer interface (DKIM bundled)
* message encryption via Encrypter interface
* trio support via anyio (the library itself, some backends may not be compatible)
* fallback transports
* global From address
* templated emails

## Usage

```bash
pip install mailers[smtp]
```

Then create mailer:

```python
from mailers import Mailer

mailer = Mailer("smtp://user:password@localhost:25?timeout=2")
await mailer.send_message(
    to="user@localhost", from_address="from@localhost", subject="Hello", text="World!"
)
```

## Compose messages

If you need more control over the message, you can use `Email` object to construct email message and then send it
using `mailer.send` method.

```python
from mailers import Email, Mailer

message = Email(
    to="user@localhost",
    from_address="from@example.tld",
    cc="cc@example.com",
    bcc=["bcc@example.com"],
    text="Hello world!",
    html="<b>Hello world!</b>",
)
mailer = Mailer("smtp://")
await mailer.send(message)
```

### Global From address

Instead of setting "From" header in every message, you can set it mailer-wide. Use `from_address` argument of Mailer
class:

```python
mailer = Mailer(from_address="sender@localhost")
```

The mailer will set From header with the given value to all messages that do not container From or Sender headers.

## Using Jinja templates

> Requires `jinja2` package installed

You can use Jinja to render templates. This way, your `text` and `html` can be rendered from a template.

Use `TemplatedMailer` instead of default `Mailer` and set a `jinja2.Environment` instance.
Then, call `send_templated_message`.

```python
import jinja2

from mailers import TemplatedMailer

env = jinja2.Environment(loader=jinja2.FileSystemLoader(["templates"]))
mailer = TemplatedMailer("smtp://", env)
mailer.send_templated_message(
    to="...",
    subject="Hello",
    text_template="mail.txt",
    html_template="mail.html",
    template_context={"user": "root"},
)
```

## Attachments

Use `attach`, `attach_from_path`, `attach_from_path_sync` methods to attach files.

```python
from mailers import Email

message = Email(
    to="user@localhost", from_address="from@example.tld", text="Hello world!"
)

# attachments can be added on demand
await message.attach_from_path("file.txt")

# or use blocking sync version
message.attach_from_path_sync("file.txt")

# attach from variable
message.attach("CONTENTS", "file.txt", "text/plain")
```

## Embedding files

In the same way as with attachments, you can inline file into your messages. This is commonly used to display embedded
images in the HTML body. Here are method you can use `embed`, `embed_from_path`, `embed_from_path_sync`.

```python
from mailers import Email

message = Email(
    to="user@localhost",
    from_address="from@example.tld",
    html='Render me <img src="cid:img1">',
)

await message.embed_from_path(path="/path/to/image.png", name="img1")
```

Note, that you have to add HTML part to embed files. Otherwise, they will be ignored.

## Message signatures

You can sign messages (e.g. with DKIM) by passing `signer` argument to the `Mailer` instance.

```python
signer = MySigner()
mailer = Mailer(..., signer=signer)
```

### DKIM signing

> Requires `dkimpy` package installed

You may wish to add DKIM signature to your messages to prevent them from being put into the spam folder.

Note, you need to install [`dkimpy`](https://pypi.org/project/dkimpy/) package before using this feature.

```python
from mailers import Mailer
from mailers.signers.dkim import DKIMSigner

signer = DKIMSigner(selector="default", private_key_path="/path/to/key.pem")

# or you can put key content using private_key argument
signer = DKIMSigner(selector="default", private_key="PRIVATE KEY GOES here...")

mailer = Mailer("smtp://", signer=signer)
```

Now all outgoing messages will be signed with DKIM method.

The plugin signs "From", "To", "Subject" headers by default. Use "headers" argument to override it.

## Custom signers

Extend `mailers.Signer` class and implement `sign` method:

```python
from email.message import Message
from mailers import Signer


class MySigner(Signer):
    def sign(self, message: Message) -> Message:
        # message signing code here...
        return message
```

## Encrypters

When encrypting a message, the entire message (including attachments) is encrypted using a certificate. Therefore, only
the recipients that have the corresponding private key can read the original message contents.

````python
encrypter = MyEncrypter()
mailer = Mailer(..., encrypter=encrypter)
````

Now all message content will be encrypted.

## Custom encrypters

Extend `mailers.Encrypter` class and implement `encrypt` method:

```python
from email.message import Message
from mailers import Encrypter


class MyEncrypter(Encrypter):
    def encrypt(self, message: Message) -> Message:
        # message encrypting code here...
        return message
```

## High Availability

Use `MultiTransport` to provide a fallback transport. By default, the first transport is used but if it fails to send
the message, it will retry sending using next configured transport.

```python
from mailers import Mailer, MultiTransport, SMTPTransport

primary_transport = SMTPTransport()
fallback_transport = SMTPTransport()

mailer = Mailer(MultiTransport([primary_transport, fallback_transport]))
```

## Preprocessors

Preprocessors are function that mailer calls before sending. Preprocessors are simple functions that modify message
contents.

Below you see an example preprocessor:

```python
from email.message import EmailMessage

from mailers import Mailer


def attach_html_preprocessor(message: EmailMessage) -> EmailMessage:
    message.add_alternative(b"This is HTML body", subtype="html", charset="utf-8")
    return message


mailer = Mailer(preprocessors=[attach_html_preprocessor])
```

### CSS inliner

> Requires `toronado` package installed

Out of the box we provide `mailers.preprocessors.css_inliner` utility that converts CSS classes into inline styles.

## Transports

### SMTP transport

> Requires `aiosmtplib` package installed

Send messages via third-party SMTP servers.

**Class:** `mailers.transports.SMTPTransport`
**directory** `smtp://user:pass@hostname:port?timeout=&use_tls=1`
**Options:**

* `host` (string, default "localhost") - SMTP server host
* `port` (string, default "25") - SMTP server port
* `user` (string) - SMTP server login
* `password` (string) - SMTP server login password
* `use_tls` (string, choices: "yes", "1", "on", "true") - use TLS
* `timeout` (int) - connection timeout
* `cert_file` (string) - path to certificate file
* `key_file` (string) - path to key file

### File transport

Write outgoing messages into a directory in EML format.

**Class:** `mailers.transports.FileTransport`
**DSN:** `file:///tmp/mails`
**Options:**

* `directory` (string) path to a directory

### Null transport

Discards outgoing messages. Takes no action on send.

**Class:** `mailers.transports.NullTransport`
**DSN:** `null://`

### Memory transport

Keeps all outgoing messages in memory. Good for testing.

**Class:** `mailers.transports.InMemoryTransport`
**DSN:** `memory://`
**Options:**

* `storage` (list of strings) - outgoing message container

You can access the mailbox via ".mailbox" attribute.

Example:

```python
from mailers import Mailer, InMemoryTransport, Email

transport = InMemoryTransport([])
mailer = Mailer(transport)

await mailer.send(Email(...))
assert len(transport.mailbox) == 1  # here are all outgoing messages
```

### Streaming transport

Writes all messages into a writable stream. Ok for local development.

**Class:** `mailers.transports.StreamTransport`
**DSN:** unsupported
**Options:**

* `output` (typing.IO) - a writable stream

Example:

```python
import io
from mailers import Mailer, StreamTransport

transport = StreamTransport(output=io.StringIO())
mailer = Mailer(transport)
```

### Console transport

This is a preconfigured subclass of streaming transport. Writes to `sys.stderr` by default.

**Class:** `mailers.transports.ConsoleTransport`
**DSN:** `console://`
**Options:**

* `output` (typing.IO) - a writeable stream

### Multi transport

The purpose of this transport is to provide a developer an option to provide a fallback transport.
You can configure several channels and `MultiTransport` will guarantee that at least one will deliver the message.

**Class:** `mailers.transports.MultiTransport`
**DSN:** `-`
**Options:**

* `transports` (list[Transport]) - subtransports

### Custom transports.

Each transport must extend `mailers.transports.Transport` base class.

```python
from email.message import Message
from mailers import Mailer, Transport


class PrintTransport(Transport):
    async def send(self, message: Message) -> None:
        print(str(message))


mailer = Mailer(PrintTransport())
```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/alex-oleshkevich/mailers",
    "name": "mailers",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.8",
    "maintainer_email": null,
    "keywords": "asyncio, email, mailer, mail",
    "author": "alex.oleshkevich",
    "author_email": "alex.oleshkevich@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/b7/5d/1a713b153d9f30adde9f4846aea816fe8f655b87705b3b858f09a7bd0e6f/mailers-3.0.5.tar.gz",
    "platform": null,
    "description": "# Mailers for asyncio\n\n![PyPI](https://img.shields.io/pypi/v/mailers)\n![GitHub Workflow Status](https://img.shields.io/github/workflow/status/alex-oleshkevich/mailers/Lint%20and%20test)\n![GitHub](https://img.shields.io/github/license/alex-oleshkevich/mailers)\n![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/pypi/mailers)\n![PyPI - Downloads](https://img.shields.io/pypi/dm/mailers)\n![GitHub Release Date](https://img.shields.io/github/release-date/alex-oleshkevich/mailers)\n![Lines of code](https://img.shields.io/tokei/lines/github/alex-oleshkevich/mailers)\n\n## Features\n\n* fully typed\n* full utf-8 support\n* async and sync sending\n* pluggable transports\n* multiple built-in transports including: SMTP, file, null, in-memory, streaming, and console.\n* message preprocessors\n* embeddables\n* attachments (with async and sync interfaces)\n* message signing via Signer interface (DKIM bundled)\n* message encryption via Encrypter interface\n* trio support via anyio (the library itself, some backends may not be compatible)\n* fallback transports\n* global From address\n* templated emails\n\n## Usage\n\n```bash\npip install mailers[smtp]\n```\n\nThen create mailer:\n\n```python\nfrom mailers import Mailer\n\nmailer = Mailer(\"smtp://user:password@localhost:25?timeout=2\")\nawait mailer.send_message(\n    to=\"user@localhost\", from_address=\"from@localhost\", subject=\"Hello\", text=\"World!\"\n)\n```\n\n## Compose messages\n\nIf you need more control over the message, you can use `Email` object to construct email message and then send it\nusing `mailer.send` method.\n\n```python\nfrom mailers import Email, Mailer\n\nmessage = Email(\n    to=\"user@localhost\",\n    from_address=\"from@example.tld\",\n    cc=\"cc@example.com\",\n    bcc=[\"bcc@example.com\"],\n    text=\"Hello world!\",\n    html=\"<b>Hello world!</b>\",\n)\nmailer = Mailer(\"smtp://\")\nawait mailer.send(message)\n```\n\n### Global From address\n\nInstead of setting \"From\" header in every message, you can set it mailer-wide. Use `from_address` argument of Mailer\nclass:\n\n```python\nmailer = Mailer(from_address=\"sender@localhost\")\n```\n\nThe mailer will set From header with the given value to all messages that do not container From or Sender headers.\n\n## Using Jinja templates\n\n> Requires `jinja2` package installed\n\nYou can use Jinja to render templates. This way, your `text` and `html` can be rendered from a template.\n\nUse `TemplatedMailer` instead of default `Mailer` and set a `jinja2.Environment` instance.\nThen, call `send_templated_message`.\n\n```python\nimport jinja2\n\nfrom mailers import TemplatedMailer\n\nenv = jinja2.Environment(loader=jinja2.FileSystemLoader([\"templates\"]))\nmailer = TemplatedMailer(\"smtp://\", env)\nmailer.send_templated_message(\n    to=\"...\",\n    subject=\"Hello\",\n    text_template=\"mail.txt\",\n    html_template=\"mail.html\",\n    template_context={\"user\": \"root\"},\n)\n```\n\n## Attachments\n\nUse `attach`, `attach_from_path`, `attach_from_path_sync` methods to attach files.\n\n```python\nfrom mailers import Email\n\nmessage = Email(\n    to=\"user@localhost\", from_address=\"from@example.tld\", text=\"Hello world!\"\n)\n\n# attachments can be added on demand\nawait message.attach_from_path(\"file.txt\")\n\n# or use blocking sync version\nmessage.attach_from_path_sync(\"file.txt\")\n\n# attach from variable\nmessage.attach(\"CONTENTS\", \"file.txt\", \"text/plain\")\n```\n\n## Embedding files\n\nIn the same way as with attachments, you can inline file into your messages. This is commonly used to display embedded\nimages in the HTML body. Here are method you can use `embed`, `embed_from_path`, `embed_from_path_sync`.\n\n```python\nfrom mailers import Email\n\nmessage = Email(\n    to=\"user@localhost\",\n    from_address=\"from@example.tld\",\n    html='Render me <img src=\"cid:img1\">',\n)\n\nawait message.embed_from_path(path=\"/path/to/image.png\", name=\"img1\")\n```\n\nNote, that you have to add HTML part to embed files. Otherwise, they will be ignored.\n\n## Message signatures\n\nYou can sign messages (e.g. with DKIM) by passing `signer` argument to the `Mailer` instance.\n\n```python\nsigner = MySigner()\nmailer = Mailer(..., signer=signer)\n```\n\n### DKIM signing\n\n> Requires `dkimpy` package installed\n\nYou may wish to add DKIM signature to your messages to prevent them from being put into the spam folder.\n\nNote, you need to install [`dkimpy`](https://pypi.org/project/dkimpy/) package before using this feature.\n\n```python\nfrom mailers import Mailer\nfrom mailers.signers.dkim import DKIMSigner\n\nsigner = DKIMSigner(selector=\"default\", private_key_path=\"/path/to/key.pem\")\n\n# or you can put key content using private_key argument\nsigner = DKIMSigner(selector=\"default\", private_key=\"PRIVATE KEY GOES here...\")\n\nmailer = Mailer(\"smtp://\", signer=signer)\n```\n\nNow all outgoing messages will be signed with DKIM method.\n\nThe plugin signs \"From\", \"To\", \"Subject\" headers by default. Use \"headers\" argument to override it.\n\n## Custom signers\n\nExtend `mailers.Signer` class and implement `sign` method:\n\n```python\nfrom email.message import Message\nfrom mailers import Signer\n\n\nclass MySigner(Signer):\n    def sign(self, message: Message) -> Message:\n        # message signing code here...\n        return message\n```\n\n## Encrypters\n\nWhen encrypting a message, the entire message (including attachments) is encrypted using a certificate. Therefore, only\nthe recipients that have the corresponding private key can read the original message contents.\n\n````python\nencrypter = MyEncrypter()\nmailer = Mailer(..., encrypter=encrypter)\n````\n\nNow all message content will be encrypted.\n\n## Custom encrypters\n\nExtend `mailers.Encrypter` class and implement `encrypt` method:\n\n```python\nfrom email.message import Message\nfrom mailers import Encrypter\n\n\nclass MyEncrypter(Encrypter):\n    def encrypt(self, message: Message) -> Message:\n        # message encrypting code here...\n        return message\n```\n\n## High Availability\n\nUse `MultiTransport` to provide a fallback transport. By default, the first transport is used but if it fails to send\nthe message, it will retry sending using next configured transport.\n\n```python\nfrom mailers import Mailer, MultiTransport, SMTPTransport\n\nprimary_transport = SMTPTransport()\nfallback_transport = SMTPTransport()\n\nmailer = Mailer(MultiTransport([primary_transport, fallback_transport]))\n```\n\n## Preprocessors\n\nPreprocessors are function that mailer calls before sending. Preprocessors are simple functions that modify message\ncontents.\n\nBelow you see an example preprocessor:\n\n```python\nfrom email.message import EmailMessage\n\nfrom mailers import Mailer\n\n\ndef attach_html_preprocessor(message: EmailMessage) -> EmailMessage:\n    message.add_alternative(b\"This is HTML body\", subtype=\"html\", charset=\"utf-8\")\n    return message\n\n\nmailer = Mailer(preprocessors=[attach_html_preprocessor])\n```\n\n### CSS inliner\n\n> Requires `toronado` package installed\n\nOut of the box we provide `mailers.preprocessors.css_inliner` utility that converts CSS classes into inline styles.\n\n## Transports\n\n### SMTP transport\n\n> Requires `aiosmtplib` package installed\n\nSend messages via third-party SMTP servers.\n\n**Class:** `mailers.transports.SMTPTransport`\n**directory** `smtp://user:pass@hostname:port?timeout=&use_tls=1`\n**Options:**\n\n* `host` (string, default \"localhost\") - SMTP server host\n* `port` (string, default \"25\") - SMTP server port\n* `user` (string) - SMTP server login\n* `password` (string) - SMTP server login password\n* `use_tls` (string, choices: \"yes\", \"1\", \"on\", \"true\") - use TLS\n* `timeout` (int) - connection timeout\n* `cert_file` (string) - path to certificate file\n* `key_file` (string) - path to key file\n\n### File transport\n\nWrite outgoing messages into a directory in EML format.\n\n**Class:** `mailers.transports.FileTransport`\n**DSN:** `file:///tmp/mails`\n**Options:**\n\n* `directory` (string) path to a directory\n\n### Null transport\n\nDiscards outgoing messages. Takes no action on send.\n\n**Class:** `mailers.transports.NullTransport`\n**DSN:** `null://`\n\n### Memory transport\n\nKeeps all outgoing messages in memory. Good for testing.\n\n**Class:** `mailers.transports.InMemoryTransport`\n**DSN:** `memory://`\n**Options:**\n\n* `storage` (list of strings) - outgoing message container\n\nYou can access the mailbox via \".mailbox\" attribute.\n\nExample:\n\n```python\nfrom mailers import Mailer, InMemoryTransport, Email\n\ntransport = InMemoryTransport([])\nmailer = Mailer(transport)\n\nawait mailer.send(Email(...))\nassert len(transport.mailbox) == 1  # here are all outgoing messages\n```\n\n### Streaming transport\n\nWrites all messages into a writable stream. Ok for local development.\n\n**Class:** `mailers.transports.StreamTransport`\n**DSN:** unsupported\n**Options:**\n\n* `output` (typing.IO) - a writable stream\n\nExample:\n\n```python\nimport io\nfrom mailers import Mailer, StreamTransport\n\ntransport = StreamTransport(output=io.StringIO())\nmailer = Mailer(transport)\n```\n\n### Console transport\n\nThis is a preconfigured subclass of streaming transport. Writes to `sys.stderr` by default.\n\n**Class:** `mailers.transports.ConsoleTransport`\n**DSN:** `console://`\n**Options:**\n\n* `output` (typing.IO) - a writeable stream\n\n### Multi transport\n\nThe purpose of this transport is to provide a developer an option to provide a fallback transport.\nYou can configure several channels and `MultiTransport` will guarantee that at least one will deliver the message.\n\n**Class:** `mailers.transports.MultiTransport`\n**DSN:** `-`\n**Options:**\n\n* `transports` (list[Transport]) - subtransports\n\n### Custom transports.\n\nEach transport must extend `mailers.transports.Transport` base class.\n\n```python\nfrom email.message import Message\nfrom mailers import Mailer, Transport\n\n\nclass PrintTransport(Transport):\n    async def send(self, message: Message) -> None:\n        print(str(message))\n\n\nmailer = Mailer(PrintTransport())\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Email delivery for asyncio.",
    "version": "3.0.5",
    "project_urls": {
        "Documentation": "https://github.com/alex-oleshkevich/mailers",
        "Homepage": "https://github.com/alex-oleshkevich/mailers",
        "Repository": "https://github.com/alex-oleshkevich/mailers.git"
    },
    "split_keywords": [
        "asyncio",
        " email",
        " mailer",
        " mail"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "59edfadd2807db8548836f33dfa749112aee533631eaa1c2f14823d1d90af489",
                "md5": "a3d6d453301423cf6a91170047487bce",
                "sha256": "564de4e4d8b5d56605b0c9a991c092d077fb85cfc4fdc2ed3960dcb51165ebf3"
            },
            "downloads": -1,
            "filename": "mailers-3.0.5-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "a3d6d453301423cf6a91170047487bce",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.8",
            "size": 18518,
            "upload_time": "2024-04-23T17:30:39",
            "upload_time_iso_8601": "2024-04-23T17:30:39.580664Z",
            "url": "https://files.pythonhosted.org/packages/59/ed/fadd2807db8548836f33dfa749112aee533631eaa1c2f14823d1d90af489/mailers-3.0.5-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b75d1a713b153d9f30adde9f4846aea816fe8f655b87705b3b858f09a7bd0e6f",
                "md5": "72cfaf8374cbc2b373b4db41dac8ed5f",
                "sha256": "9eb4d177fb85453869f779779249c5849c18bdb56fd5f6558c7b6a32ff90640f"
            },
            "downloads": -1,
            "filename": "mailers-3.0.5.tar.gz",
            "has_sig": false,
            "md5_digest": "72cfaf8374cbc2b373b4db41dac8ed5f",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.8",
            "size": 15976,
            "upload_time": "2024-04-23T17:30:40",
            "upload_time_iso_8601": "2024-04-23T17:30:40.799468Z",
            "url": "https://files.pythonhosted.org/packages/b7/5d/1a713b153d9f30adde9f4846aea816fe8f655b87705b3b858f09a7bd0e6f/mailers-3.0.5.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-23 17:30:40",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "alex-oleshkevich",
    "github_project": "mailers",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "mailers"
}
        
Elapsed time: 0.24766s