Module mail2beyond.connectors.smtp

Creates the built-in 'smtp' connector that can be used to forward SMTP message to an upstream SMTP server.

Expand source code
"""Creates the built-in 'smtp' connector that can be used to forward SMTP message to an upstream SMTP server."""

import smtplib
import socket

from mail2beyond import framework


class Connector(framework.BaseConnector):
    """Defines a connector that forwards received messages to an upstream SMTP server for actual delivery."""
    name = "smtp"

    def submit(self, parser):
        """Overwrites the submit() method to forward the received SMTP message to an upstream SMTP server."""
        # Setup the SMTP client with or without TLS, depending on the configuration
        if self.config.get("smtp_use_tls", False):
            client = smtplib.SMTP_SSL(self.config.get("smtp_host"), self.config.get("smtp_port"))
        else:
            client = smtplib.SMTP(self.config.get("smtp_host"), self.config.get("smtp_port"))

        # Add login credentials to the client if configured
        if self.config.get("smtp_use_login", False):
            client.login(self.config["smtp_login_user"], self.config["smtp_login_password"])

        # Gather information about SMTP connection
        proto = "smtps" if self.config.get("smtp_use_tls", False) else "smtp"
        host = self.config.get("smtp_host")
        port = self.config.get("smtp_port")

        # Send the SMTP message
        try:
            client.sendmail(
                parser.mail.envelope.mail_from,
                parser.mail.envelope.rcpt_tos,
                parser.mail.envelope.content.decode()
            )
            client.close()
            self.log.debug(f"connector '{self}' successfully forwarded message to {proto}://{host}:{port}")
        except Exception as sendmail_error:
            self.log.error(f"connector '{self}' failed to forward message - {sendmail_error}")
            client.close()
            raise sendmail_error

    def pre_submit(self, parser):
        """Overwrites the pre_submit() method to ensure required configuration is set."""
        # Require an 'smtp_host' value to be specified
        if "smtp_host" not in self.config:
            raise framework.Error(f"connector '{self}' requires config value 'smtp_host'")

        # Ensure 'smtp_host' is a str
        if not isinstance(self.config.get("smtp_host"), str):
            raise framework.Error(f"connector '{self}' config value 'smtp_host' must be type 'str'")

        # Require 'smtp_host' to be a valid IP address or hostname
        try:
            socket.gethostbyname(self.config.get("smtp_host"))
        except socket.gaierror as err:
            raise framework.Error(f"connector '{self}' contains invalid 'smtp_host' : {err}")

        # Require an 'smtp_port' value to be specified
        if "smtp_port" not in self.config:
            raise framework.Error(f"connector '{self}' requires config value 'smtp_port'")

        # Require a 'smtp_port' to be integer between 1 adn 65535
        if not isinstance(self.config.get("smtp_port"), int) or 1 > self.config.get("smtp_port") > 65535:
            raise framework.Error(f"connector '{self}' requires config value 'smtp_port' to be between 1 and 65535")

        # Only check the 'smtp_use_tls' value if it is specified otherwise the default will be assumed.
        if "smtp_use_tls" in self.config:
            # Ensure 'smtp_use_tls' is a bool
            if not isinstance(self.config.get("smtp_use_tls"), bool):
                raise framework.Error(f"connector '{self}' config value 'smtp_use_tls' must be type 'bool'")

        # Only check the 'smtp_use_login' requirements if it is specified, otherwise no login will be applied
        if self.config.get("smtp_use_login", False):
            # Require an 'smtp_login_user' value to be specified
            if "smtp_login_user" not in self.config:
                raise framework.Error(f"connector '{self}' requires config value 'smtp_login_user'")

            # Ensure 'smtp_login_user' is a str
            if not isinstance(self.config.get("smtp_login_user"), str):
                raise framework.Error(f"connector '{self}' config value 'smtp_login_user' must be type 'str'")

            # Require an 'smtp_login_password' value to be specified
            if "smtp_login_password" not in self.config:
                raise framework.Error(f"connector '{self}' requires config value 'smtp_login_password'")

            # Ensure 'smtp_login_password' is a str
            if not isinstance(self.config.get("smtp_login_password"), str):
                raise framework.Error(f"connector '{self}' config value 'smtp_login_password' must be type 'str'")

Classes

class Connector (**kwargs)

Defines a connector that forwards received messages to an upstream SMTP server for actual delivery.

Initialize the object with required attributes.

Notes

Any arguments passed in when this object is created will be stored in the 'config' attribute of the object.

Expand source code
class Connector(framework.BaseConnector):
    """Defines a connector that forwards received messages to an upstream SMTP server for actual delivery."""
    name = "smtp"

    def submit(self, parser):
        """Overwrites the submit() method to forward the received SMTP message to an upstream SMTP server."""
        # Setup the SMTP client with or without TLS, depending on the configuration
        if self.config.get("smtp_use_tls", False):
            client = smtplib.SMTP_SSL(self.config.get("smtp_host"), self.config.get("smtp_port"))
        else:
            client = smtplib.SMTP(self.config.get("smtp_host"), self.config.get("smtp_port"))

        # Add login credentials to the client if configured
        if self.config.get("smtp_use_login", False):
            client.login(self.config["smtp_login_user"], self.config["smtp_login_password"])

        # Gather information about SMTP connection
        proto = "smtps" if self.config.get("smtp_use_tls", False) else "smtp"
        host = self.config.get("smtp_host")
        port = self.config.get("smtp_port")

        # Send the SMTP message
        try:
            client.sendmail(
                parser.mail.envelope.mail_from,
                parser.mail.envelope.rcpt_tos,
                parser.mail.envelope.content.decode()
            )
            client.close()
            self.log.debug(f"connector '{self}' successfully forwarded message to {proto}://{host}:{port}")
        except Exception as sendmail_error:
            self.log.error(f"connector '{self}' failed to forward message - {sendmail_error}")
            client.close()
            raise sendmail_error

    def pre_submit(self, parser):
        """Overwrites the pre_submit() method to ensure required configuration is set."""
        # Require an 'smtp_host' value to be specified
        if "smtp_host" not in self.config:
            raise framework.Error(f"connector '{self}' requires config value 'smtp_host'")

        # Ensure 'smtp_host' is a str
        if not isinstance(self.config.get("smtp_host"), str):
            raise framework.Error(f"connector '{self}' config value 'smtp_host' must be type 'str'")

        # Require 'smtp_host' to be a valid IP address or hostname
        try:
            socket.gethostbyname(self.config.get("smtp_host"))
        except socket.gaierror as err:
            raise framework.Error(f"connector '{self}' contains invalid 'smtp_host' : {err}")

        # Require an 'smtp_port' value to be specified
        if "smtp_port" not in self.config:
            raise framework.Error(f"connector '{self}' requires config value 'smtp_port'")

        # Require a 'smtp_port' to be integer between 1 adn 65535
        if not isinstance(self.config.get("smtp_port"), int) or 1 > self.config.get("smtp_port") > 65535:
            raise framework.Error(f"connector '{self}' requires config value 'smtp_port' to be between 1 and 65535")

        # Only check the 'smtp_use_tls' value if it is specified otherwise the default will be assumed.
        if "smtp_use_tls" in self.config:
            # Ensure 'smtp_use_tls' is a bool
            if not isinstance(self.config.get("smtp_use_tls"), bool):
                raise framework.Error(f"connector '{self}' config value 'smtp_use_tls' must be type 'bool'")

        # Only check the 'smtp_use_login' requirements if it is specified, otherwise no login will be applied
        if self.config.get("smtp_use_login", False):
            # Require an 'smtp_login_user' value to be specified
            if "smtp_login_user" not in self.config:
                raise framework.Error(f"connector '{self}' requires config value 'smtp_login_user'")

            # Ensure 'smtp_login_user' is a str
            if not isinstance(self.config.get("smtp_login_user"), str):
                raise framework.Error(f"connector '{self}' config value 'smtp_login_user' must be type 'str'")

            # Require an 'smtp_login_password' value to be specified
            if "smtp_login_password" not in self.config:
                raise framework.Error(f"connector '{self}' requires config value 'smtp_login_password'")

            # Ensure 'smtp_login_password' is a str
            if not isinstance(self.config.get("smtp_login_password"), str):
                raise framework.Error(f"connector '{self}' config value 'smtp_login_password' must be type 'str'")

Ancestors

Class variables

var name

Methods

def pre_submit(self, parser)

Overwrites the pre_submit() method to ensure required configuration is set.

Expand source code
def pre_submit(self, parser):
    """Overwrites the pre_submit() method to ensure required configuration is set."""
    # Require an 'smtp_host' value to be specified
    if "smtp_host" not in self.config:
        raise framework.Error(f"connector '{self}' requires config value 'smtp_host'")

    # Ensure 'smtp_host' is a str
    if not isinstance(self.config.get("smtp_host"), str):
        raise framework.Error(f"connector '{self}' config value 'smtp_host' must be type 'str'")

    # Require 'smtp_host' to be a valid IP address or hostname
    try:
        socket.gethostbyname(self.config.get("smtp_host"))
    except socket.gaierror as err:
        raise framework.Error(f"connector '{self}' contains invalid 'smtp_host' : {err}")

    # Require an 'smtp_port' value to be specified
    if "smtp_port" not in self.config:
        raise framework.Error(f"connector '{self}' requires config value 'smtp_port'")

    # Require a 'smtp_port' to be integer between 1 adn 65535
    if not isinstance(self.config.get("smtp_port"), int) or 1 > self.config.get("smtp_port") > 65535:
        raise framework.Error(f"connector '{self}' requires config value 'smtp_port' to be between 1 and 65535")

    # Only check the 'smtp_use_tls' value if it is specified otherwise the default will be assumed.
    if "smtp_use_tls" in self.config:
        # Ensure 'smtp_use_tls' is a bool
        if not isinstance(self.config.get("smtp_use_tls"), bool):
            raise framework.Error(f"connector '{self}' config value 'smtp_use_tls' must be type 'bool'")

    # Only check the 'smtp_use_login' requirements if it is specified, otherwise no login will be applied
    if self.config.get("smtp_use_login", False):
        # Require an 'smtp_login_user' value to be specified
        if "smtp_login_user" not in self.config:
            raise framework.Error(f"connector '{self}' requires config value 'smtp_login_user'")

        # Ensure 'smtp_login_user' is a str
        if not isinstance(self.config.get("smtp_login_user"), str):
            raise framework.Error(f"connector '{self}' config value 'smtp_login_user' must be type 'str'")

        # Require an 'smtp_login_password' value to be specified
        if "smtp_login_password" not in self.config:
            raise framework.Error(f"connector '{self}' requires config value 'smtp_login_password'")

        # Ensure 'smtp_login_password' is a str
        if not isinstance(self.config.get("smtp_login_password"), str):
            raise framework.Error(f"connector '{self}' config value 'smtp_login_password' must be type 'str'")
def submit(self, parser)

Overwrites the submit() method to forward the received SMTP message to an upstream SMTP server.

Expand source code
def submit(self, parser):
    """Overwrites the submit() method to forward the received SMTP message to an upstream SMTP server."""
    # Setup the SMTP client with or without TLS, depending on the configuration
    if self.config.get("smtp_use_tls", False):
        client = smtplib.SMTP_SSL(self.config.get("smtp_host"), self.config.get("smtp_port"))
    else:
        client = smtplib.SMTP(self.config.get("smtp_host"), self.config.get("smtp_port"))

    # Add login credentials to the client if configured
    if self.config.get("smtp_use_login", False):
        client.login(self.config["smtp_login_user"], self.config["smtp_login_password"])

    # Gather information about SMTP connection
    proto = "smtps" if self.config.get("smtp_use_tls", False) else "smtp"
    host = self.config.get("smtp_host")
    port = self.config.get("smtp_port")

    # Send the SMTP message
    try:
        client.sendmail(
            parser.mail.envelope.mail_from,
            parser.mail.envelope.rcpt_tos,
            parser.mail.envelope.content.decode()
        )
        client.close()
        self.log.debug(f"connector '{self}' successfully forwarded message to {proto}://{host}:{port}")
    except Exception as sendmail_error:
        self.log.error(f"connector '{self}' failed to forward message - {sendmail_error}")
        client.close()
        raise sendmail_error

Inherited members