Mailables¶
A Mailable describes an email as a class. It is also a Message, so a notification can yield it
directly, and a Mailer can send it without any notification.
from collections.abc import Mapping, Sequence
from types import MappingProxyType
from goatnotify import Attachment, Mailable
class InvoiceMail(Mailable):
subject = "Your invoice"
reply_to = ("billing@example.com",)
headers = MappingProxyType({"X-Campaign": "invoices"})
metadata = MappingProxyType({"category": "billing"})
def __init__(self, invoice: Invoice) -> None:
self.invoice = invoice
self.subject = f"Invoice {invoice.number}"
def template(self) -> str | None:
return "emails/invoice.html"
def text_template(self) -> str | None:
return "emails/invoice.txt"
def context(self) -> Mapping[str, object]:
return {"invoice": self.invoice}
def attachments(self) -> Sequence[Attachment]:
return [Attachment.from_path(self.invoice.pdf_path)]
Reference¶
| Member | Type | Default |
|---|---|---|
subject |
str |
required: an empty subject raises InvalidMessageError |
sender |
str \| Address \| None |
the mailer's sender |
to, cc, bcc, reply_to |
Sequence[str \| Address] |
(); an empty to uses the recipient's "email" route |
headers |
Mapping[str, str] |
empty |
metadata |
Mapping[str, str] |
empty; tags for providers, never sent as headers |
attachments() |
Sequence[Attachment] |
() |
html(), text() |
str \| None |
None |
template(), text_template() |
str \| None |
None |
context() |
Mapping[str, object] |
empty |
Addresses accept "ada@example.com", "Ada Lovelace <ada@example.com>" or an Address.
Body precedence¶
html()wins overtemplate(), andtext()wins overtext_template().- Templates are rendered with
context()by the mailer's template renderer. - At least one body is required; a template without a configured renderer raises
ConfigurationError.
Attachments and inline images¶
from_path guesses the MIME type from the file name. An inline attachment is referenced from the
HTML body with its file name as content id, so inline names may only use letters, digits, dots,
dashes and underscores, and must be unique within a message:
Sending without a notification¶
mailer = Mailer(SmtpTransport("smtp.example.com"), sender="noreply@example.com")
await mailer.send(InvoiceMail(invoice), to=["ada@example.com"])
message = await mailer.render(InvoiceMail(invoice), to=["ada@example.com"]) # preview
A non-empty to= replaces the mailable's own to.
Header injection protection¶
Subjects, display names, addresses, header names and header values containing a line break are
rejected with InvalidMessageError before any MIME is built. Header names must be RFC 5322
field names (printable ASCII, no space or colon), and custom headers cannot override Subject,
From, Sender, To, Cc, Bcc, Reply-To, Return-Path, Date, Content-Type,
Content-Transfer-Encoding or MIME-Version: set those through the mailable's fields.