Skip to content

Core API Reference

The exmailer core module provides the primary interface for interacting with Microsoft Exchange.

ExchangeEmailer Class

The main controller for email operations. It is designed to be used as a context manager to ensure connections are properly closed.

Methods

__init__(self, config: dict[str, Any] | None = None) -> None

Initializes the emailer. If no config is provided, it attempts auto-discovery via environment variables or local files.

ExchangeEmailer

ExchangeEmailer(config_path: str | None = None, config: dict[str, Any] | None = None, verbose: bool = False)

Send emails via Microsoft Exchange server with flexible HTML template support.

Initialize the Exchange emailer.

Parameters:

Name Type Description Default
config_path str | None

Path to JSON/YAML configuration file

None
config dict[str, Any] | None

Direct configuration dictionary (highest priority)

None
verbose bool

Enable verbose logging

False

Examples:

emailer = ExchangeEmailer(config={
    "domain": "corp",
    "username": "john.doe",
    "password": "secret123",
    "server": "mail.corp.com",
    "email_domain": "corp.com"
})

Method 2: Config file

emailer = ExchangeEmailer(config_path="~/.config/exmailer/config.json")

Method 3: Auto-discovery (looks in default locations)

emailer = ExchangeEmailer()
Source code in exmailer/core.py
def __init__(
    self,
    config_path: str | None = None,
    config: dict[str, Any] | None = None,  # NEW parameter
    verbose: bool = False,
):
    """
    Initialize the Exchange emailer.

    Args:
        config_path: Path to JSON/YAML configuration file
        config: Direct configuration dictionary (highest priority)
        verbose: Enable verbose logging

    Examples:
        # Method 1: Programmatic config (recommended for scripts)
        ```python
        emailer = ExchangeEmailer(config={
            "domain": "corp",
            "username": "john.doe",
            "password": "secret123",
            "server": "mail.corp.com",
            "email_domain": "corp.com"
        })
        ```

        # Method 2: Config file
        ```python
        emailer = ExchangeEmailer(config_path="~/.config/exmailer/config.json")
        ```

        # Method 3: Auto-discovery (looks in default locations)
        ```python
        emailer = ExchangeEmailer()
        ```
    """
    self.verbose = verbose
    self.config = load_config(config_path=config_path, config_dict=config)
    self._patch_exchangelib_adapter()

    if verbose:  # pragma: no cover
        logging.basicConfig(
            level=logging.DEBUG,
            format="%(asctime)s %(levelname)s %(message)s",
            filename="exchange_debug.log",
        )

    self.account = self._connect_to_exchange()

send_email(...) -> bool

Sends an email through the Exchange server.

send_email

send_email(subject: str, body: str, recipients: Sequence[str], attachments: Sequence[str] | None = None, cc_recipients: Sequence[str] | None = None, bcc_recipients: Sequence[str] | None = None, template: str | TemplateType | None = TemplateType.PERSIAN, template_vars: dict[str, Any] | None = None, importance: Literal['Low', 'Normal', 'High'] = 'Normal') -> bool

Send an email with optional attachments.

Parameters:

Name Type Description Default
subject str

Email subject

required
body str

Email body content

required
recipients Sequence[str]

List of recipient email addresses

required
attachments Sequence[str] | None

List of file paths to attach

None
cc_recipients Sequence[str] | None

List of CC recipient email addresses

None
bcc_recipients Sequence[str] | None

List of BCC recipient email addresses

None
template str | TemplateType | None

Template to use. Options: - TemplateType.PERSIAN: Persian RTL template - TemplateType.DEFAULT: English LTR template - TemplateType.PLAIN: Plain text (no template) - str: Custom template name registered via register_custom_template() - None: Use plain text (no template)

PERSIAN
template_vars dict[str, Any] | None

Variables to replace in template (e.g., {"date": "14/08/1404"})

None

Returns:

Type Description
bool

True if email was sent successfully

Examples:

>>> # Using built-in Persian template
>>> emailer.send_email(
...     subject="سلام",
...     body="متن پیام",
...     recipients=["user@example.com"],
...     template=TemplateType.PERSIAN
... )
>>> # Using built-in English template
>>> emailer.send_email(
...     subject="Hello",
...     body="Message content",
...     recipients=["user@example.com"],
...     template=TemplateType.DEFAULT
... )
>>> # Using plain text (no template)
>>> emailer.send_email(
...     subject="Hello",
...     body="Plain message",
...     recipients=["user@example.com"],
...     template=None
... )
>>> # Using custom template
>>> emailer.send_email(
...     subject="Newsletter",
...     body="Content here",
...     recipients=["user@example.com"],
...     template="my_custom_template"
... )
Source code in exmailer/core.py
def send_email(
    self,
    subject: str,
    body: str,
    recipients: Sequence[str],
    attachments: Sequence[str] | None = None,
    cc_recipients: Sequence[str] | None = None,
    bcc_recipients: Sequence[str] | None = None,
    template: str | TemplateType | None = TemplateType.PERSIAN,
    template_vars: dict[str, Any] | None = None,
    importance: Literal["Low", "Normal", "High"] = "Normal",
) -> bool:
    """
    Send an email with optional attachments.

    Args:
        subject: Email subject
        body: Email body content
        recipients: List of recipient email addresses
        attachments: List of file paths to attach
        cc_recipients: List of CC recipient email addresses
        bcc_recipients: List of BCC recipient email addresses
        template: Template to use. Options:
            - TemplateType.PERSIAN: Persian RTL template
            - TemplateType.DEFAULT: English LTR template
            - TemplateType.PLAIN: Plain text (no template)
            - str: Custom template name registered via register_custom_template()
            - None: Use plain text (no template)
        template_vars: Variables to replace in template (e.g., {"date": "14/08/1404"})

    Returns:
        True if email was sent successfully

    Examples:
        >>> # Using built-in Persian template
        >>> emailer.send_email(
        ...     subject="سلام",
        ...     body="متن پیام",
        ...     recipients=["user@example.com"],
        ...     template=TemplateType.PERSIAN
        ... )

        >>> # Using built-in English template
        >>> emailer.send_email(
        ...     subject="Hello",
        ...     body="Message content",
        ...     recipients=["user@example.com"],
        ...     template=TemplateType.DEFAULT
        ... )

        >>> # Using plain text (no template)
        >>> emailer.send_email(
        ...     subject="Hello",
        ...     body="Plain message",
        ...     recipients=["user@example.com"],
        ...     template=None
        ... )

        >>> # Using custom template
        >>> emailer.send_email(
        ...     subject="Newsletter",
        ...     body="Content here",
        ...     recipients=["user@example.com"],
        ...     template="my_custom_template"
        ... )
    """
    try:
        # Get the appropriate template
        ## Apply template variables if provided
        template_vars = template_vars or {}
        template_vars["body"] = body.format(**template_vars)

        if template is None or template == TemplateType.PLAIN:
            # No template - use body as-is
            formatted_body = body.format(**template_vars)
        else:
            # Get template (handles both TemplateType enum and string names)
            template_html = get_template(template)

            # Format body with template
            formatted_body = template_html.format(**template_vars)

        # Create message
        msg = Message(
            account=self.account,
            subject=subject,
            body=HTMLBody(formatted_body),
            to_recipients=[Mailbox(email_address=email) for email in recipients],
            cc_recipients=cc_recipients or [],
            bcc_recipients=bcc_recipients or [],
            importance=importance,
        )

        # Add CC/BCC recipients if provided
        if cc_recipients:
            msg.cc_recipients = [Mailbox(email_address=email) for email in cc_recipients]
        if bcc_recipients:
            msg.bcc_recipients = [Mailbox(email_address=email) for email in bcc_recipients]

        # Process attachments
        if attachments:
            validated_attachments = validate_attachments(attachments)
            for attachment in validated_attachments:
                try:
                    with open(attachment["path"], "rb") as f:
                        content = f.read()

                    file_attachment = FileAttachment(
                        name=attachment["name"],
                        content=content,
                        content_type=attachment["content_type"],
                    )
                    msg.attach(file_attachment)
                    logger.info(
                        f"Attached: {attachment['name']} ({attachment['size'] // 1024} KB)"
                    )

                except Exception as e:  # pragma: no cover
                    logger.error(f"Failed to attach {attachment['path']}: {e!s}")
                    if self.verbose:
                        print(f"⚠️ Failed to attach {attachment['path']}: {e!s}")

        # Send email
        save_copy = self.config.get("save_copy", True)
        try:
            msg.send(save_copy=save_copy)
        except Exception as e:
            raise SendError(f"Failed to send email: {e!s}") from e  # ← Wrap exception

        logger.info(f"✅ Email sent successfully to {', '.join(recipients)}")
        if self.verbose:  # pragma: no cover
            print(f"✅ Email sent successfully to {', '.join(recipients)}")

        return True

    except Exception as e:
        logger.error(f"❌ Failed to send email: {e!s}")
        if self.verbose:  # pragma: no cover
            print(f"❌ Failed to send email: {e!s}")
        raise
Parameter Type Default Description
subject str Required The email subject line.
body str Required The HTML or plain text body.
recipients list[str] Required List of primary recipient addresses.
cc_recipients list[str] \| None None List of CC addresses.
bcc_recipients list[str] \| None None List of BCC addresses.
template TemplateType \| str \| None TemplateType.DEFAULT The template to wrap the body in.
template_vars dict[str, Any] \| None None Variables for f-string style replacement.

Template Management

register_custom_template(name: str, template_string: str) -> None

Registers a new HTML template for global use within the application session.