Skip to content

Sender.send_message mutates caller's EmailMessage by writing From header #68

Description

@FernandoCelmer

Description

Sender.send_message writes message["From"] directly onto the caller-supplied EmailMessage when the header is absent. EmailMessage.__setitem__ appends a header rather than replacing it, so a second call with the same message object yields an RFC 5322 message with two From lines, which many SMTP servers reject. Even on the first call, the side effect violates the principle of least surprise: passing a message to send_message should not modify it.

Location

email_profile/clients/smtp/sender.py line ~82

Current Behavior

def send_message(self, message: EmailMessage, *, save_to_sent: bool = True) -> None:
    if not message.get("From"):
        message["From"] = self._session.user
    ...

Reusing the same message instance (e.g. retrying a failed send) produces:

From: original@example.com
From: account@example.com

Expected Behavior

The function should not mutate its input. Either copy the message before adding headers, or refuse to send when From is missing.

Suggested Fix

import copy

def send_message(self, message: EmailMessage, *, save_to_sent: bool = True) -> None:
    if not message.get("From"):
        message = copy.deepcopy(message)
        message["From"] = self._session.user
    ...

Or, more strictly:

if not message.get("From"):
    raise ValueError("EmailMessage requires a 'From' header.")

Impact

  • Retries after transient SMTP failures duplicate the From header and may be rejected as malformed.
  • Callers cannot safely reuse EmailMessage instances across calls or persist them between sends.
  • Hidden mutation makes the function harder to compose.

Priority

High

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghighHigh priority — incorrect behavior, performance, API breakage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions