API reference¶
The reference is generated from the source with mkdocstrings.
Root package¶
mxraven ¶
The official Python SDK for the mxRaven mail runtime.
The root package exposes SMTP submission. The webhook and feedback runtime
surfaces live in :mod:mxraven.webhook and :mod:mxraven.feedback.
Control-plane administration (tenants, domains, suppressions, analytics) is out of scope and is served by the separate mxRaven control-plane SDK.
Attachment
dataclass
¶
Attachment(
filename: str = "",
content_type: str = "",
data: bytes = b"",
inline: bool = False,
content_id: str = "",
)
A message attachment.
Attributes:
| Name | Type | Description |
|---|---|---|
filename |
str
|
The attachment filename. It may be empty. |
content_type |
str
|
The media type; defaults to |
data |
bytes
|
The raw attachment content. The caller retains ownership; it is not mutated. |
inline |
bool
|
Whether the attachment is meant for inline display, for example
an image referenced by |
content_id |
str
|
The inline content identifier, ignored unless |
Client ¶
Client(
host: str,
*,
port: int = DEFAULT_ADDRESS_PORT,
username: str,
secret: str,
tls: SSLContext | None = None,
pool_size: int = _DEFAULT_POOL_SIZE,
connect_timeout: float = _DEFAULT_CONNECT_TIMEOUT,
read_timeout: float = _DEFAULT_READ_TIMEOUT,
write_timeout: float = _DEFAULT_WRITE_TIMEOUT,
local_name: str | None = None,
starttls: bool = True,
require_tls: bool = True,
implicit_tls: bool = False,
local_address: str | None = None,
auth_mechanisms: Sequence[str] = (),
)
Submits mail to the mxRaven SMTP submission service.
A client maintains a bounded pool of authenticated connections. Use it as
a context manager or call :meth:close to release them.
Example
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host
|
str
|
The submission server host; a port must not be included. |
required |
port
|
int
|
The submission server port. Defaults to |
DEFAULT_ADDRESS_PORT
|
username
|
str
|
The submission API key username. |
required |
secret
|
str
|
The submission API key secret. |
required |
tls
|
SSLContext | None
|
A custom STARTTLS |
None
|
pool_size
|
int
|
The maximum number of pooled connections. Defaults to |
_DEFAULT_POOL_SIZE
|
connect_timeout
|
float
|
The TCP connect timeout in seconds. |
_DEFAULT_CONNECT_TIMEOUT
|
read_timeout
|
float
|
The per-reply read timeout in seconds. |
_DEFAULT_READ_TIMEOUT
|
write_timeout
|
float
|
The per-write timeout in seconds. |
_DEFAULT_WRITE_TIMEOUT
|
local_name
|
str | None
|
The name sent in |
None
|
starttls
|
bool
|
Whether to issue |
True
|
require_tls
|
bool
|
Whether to fail when |
True
|
implicit_tls
|
bool
|
Whether the connection uses TLS from the first byte
(SMTPS, typically port 465). When true, |
False
|
local_address
|
str | None
|
An optional local bind address, |
None
|
auth_mechanisms
|
Sequence[str]
|
Preferred SASL mechanisms in order. When empty, the
client prefers |
()
|
Raises:
| Type | Description |
|---|---|
ValueError
|
A required argument is missing or invalid. |
Initialize the client and its connection pool.
Source code in src/mxraven/client.py
__enter__ ¶
__exit__ ¶
close ¶
send ¶
Submit a composed message and return the server's result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
Message
|
The message to submit. It may be sent more than once. |
required |
Returns:
| Type | Description |
|---|---|
Result
|
The server's result for the submission, including the mxRaven |
Result
|
|
Raises:
| Type | Description |
|---|---|
TypeError
|
|
ValueError
|
The message cannot be built. |
SMTPError
|
The server rejected a command. |
SMTPTransactionError
|
Every recipient was rejected; the
per-recipient detail is on the exception's |
Source code in src/mxraven/client.py
send_raw ¶
send_raw(
envelope: Envelope,
data: bytes | bytearray | memoryview | Iterable[bytes],
*,
prefer_bdat: bool = False,
) -> Result
Stream an already serialized RFC 5322 message with an explicit envelope.
The message is not parsed, so the caller is responsible for RFC 5322 correctness. Prefer this for large or pre-rendered messages.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
envelope
|
Envelope
|
The SMTP envelope, independent of the message headers. |
required |
data
|
bytes | bytearray | memoryview | Iterable[bytes]
|
The complete message bytes, or an iterable of chunks that is
streamed through |
required |
prefer_bdat
|
bool
|
Whether to use |
False
|
Returns:
| Type | Description |
|---|---|
Result
|
The server's result for the submission. |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
ValueError
|
The envelope has no recipients or an address is invalid. |
SMTPError
|
The server rejected a command. |
SMTPTransactionError
|
Every recipient was rejected. |
Source code in src/mxraven/client.py
DecodeError ¶
Bases: NotificationError
A notification body that does not match the SDK's payload model.
This normally means the SDK is older than the server that produced the event. The event is terminated rather than redelivered, because redelivery can never succeed. When an error handler is configured, it receives the error and consumption continues; otherwise consumption stops and this error is raised.
Initialize the error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject
|
str
|
The notification subject the undecodable body was published to. |
required |
message
|
str
|
A short description of the decode failure. |
''
|
Source code in src/mxraven/errors.py
DeliveryBy
dataclass
¶
DeliveryBy(
seconds: int,
mode: DeliveryByMode = NOTIFY,
trace: bool = False,
)
An RFC 2852 DELIVERBY request.
Attributes:
| Name | Type | Description |
|---|---|---|
seconds |
int
|
The delivery interval in seconds. It must be positive when
|
mode |
DeliveryByMode
|
How the server handles a message that misses the deadline. |
trace |
bool
|
Whether to request a trace of the delivery. |
DeliveryByMode ¶
Bases: StrEnum
How a server handles a message that misses its DELIVERBY deadline.
The values are the RFC 2852 wire values.
Envelope
dataclass
¶
Envelope(
sender: str = "",
recipients: tuple[str, ...] = (),
require_tls: bool = False,
delivery_by: DeliveryBy | None = None,
)
The SMTP envelope for a raw message.
Attributes:
| Name | Type | Description |
|---|---|---|
sender |
str
|
The envelope sender. An empty value requests a null reverse-path, which is appropriate for bounce messages. |
recipients |
tuple[str, ...]
|
At least one envelope recipient. |
require_tls |
bool
|
Whether to request that TLS be used on every delivery hop
(RFC 8689). The session must already be encrypted and the service
must advertise |
delivery_by |
DeliveryBy | None
|
An RFC 2852 |
FeedbackError ¶
Bases: MxRavenError
The feedback service returned a non-success response.
Initialize the error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
status_code
|
int
|
The HTTP response status. |
required |
message
|
str
|
The service's |
''
|
Source code in src/mxraven/errors.py
Header
dataclass
¶
A custom message header.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The header field name. |
value |
str
|
The header field value. It must not contain a line break. |
InvalidSignatureError ¶
Bases: WebhookError
A webhook request's HMAC signature did not match.
Message ¶
A mutable, chainable builder for an email message.
Chained methods return the receiver. Set both :meth:text and :meth:html
to produce a multipart/alternative body; attachments produce a
multipart/mixed body. Bcc recipients are added to the envelope but
not to the headers.
Example
Create an empty message builder.
Source code in src/mxraven/message.py
attach ¶
attach(attachment: Attachment) -> Self
Append an attachment.
Note
The attachment data is retained by reference until the message is sent. Callers must not mutate it in the meantime.
Source code in src/mxraven/message.py
attach_file ¶
Append a file attachment with an optional explicit content type.
attach_inline ¶
Append an inline attachment referenced by content_id from HTML.
Source code in src/mxraven/message.py
bcc ¶
build ¶
Serialize the message into bytes and its submission envelope.
Returns:
| Type | Description |
|---|---|
BuiltMessage
|
The serialized message and envelope. |
Raises:
| Type | Description |
|---|---|
ValueError
|
The message has no |
Source code in src/mxraven/message.py
cc ¶
date ¶
Set the Date header. When unset, the time of building is used.
A naive when is interpreted as UTC.
deliver_by ¶
deliver_by(request: DeliveryBy) -> Self
Request delivery within a bounded window (RFC 2852).
The submission service must advertise DELIVERBY, otherwise sending
raises :class:mxraven.SMTPCapabilityError.
Source code in src/mxraven/message.py
from_ ¶
Set the envelope sender and the From header.
The name is from_ because from is a Python keyword. The address may
include a display name, for example Acme <noreply@acme.example>.
Source code in src/mxraven/message.py
header ¶
html ¶
in_reply_to ¶
message_id ¶
null_sender ¶
Use a null reverse-path while keeping the From header.
Intended for bounce and other auto-generated messages. A From header
is still required for a valid message.
Source code in src/mxraven/message.py
references ¶
reply_to ¶
require_tls ¶
Require TLS for every hop that handles the message (RFC 8689).
The session must already be encrypted and the submission service must
advertise REQUIRETLS; otherwise sending raises
:class:mxraven.SMTPError before the message is transmitted.
Source code in src/mxraven/message.py
sender ¶
subject ¶
text ¶
MimeError ¶
Bases: MxRavenError
A MIME message cannot be parsed within the parser's limits.
Malformed message content never raises; the parser returns the best interpretation it can. Only structurally unbounded input raises, so an untrusted message cannot exhaust the stack or memory.
MxRavenError ¶
Bases: Exception
Base class for every error raised by the mxRaven SDK.
NotificationError ¶
Bases: MxRavenError
A failure connecting to or consuming the client notification stream.
The underlying transport or JetStream failure is preserved as
:attr:__cause__. Most connection failures are transient and are retried
automatically; this error is only raised when an operation cannot continue.
RecipientResult
dataclass
¶
RecipientResult(
address: str,
accepted: bool,
error: MxRavenError | None = None,
)
Whether a single envelope recipient was accepted.
Attributes:
| Name | Type | Description |
|---|---|---|
address |
str
|
The recipient address as submitted, without angle brackets. |
accepted |
bool
|
Whether the server accepted the recipient. |
error |
MxRavenError | None
|
The rejection reason when |
Result
dataclass
¶
Result(
message_ref: str = "",
code: int = 0,
message: str = "",
recipients: tuple[RecipientResult, ...] = (),
)
The outcome of a submission.
Attributes:
| Name | Type | Description |
|---|---|---|
message_ref |
str
|
The mxRaven message reference parsed from the final
|
code |
int
|
The final SMTP reply code. |
message |
str
|
The final SMTP reply text. It is intended for humans and is not stable. |
recipients |
tuple[RecipientResult, ...]
|
Per-recipient acceptance, in envelope order. |
SMTPCapabilityError ¶
Bases: MxRavenError
The server does not support a feature the message requires.
SMTPConnectionError ¶
Bases: MxRavenError
A connection could not be established or was lost.
The underlying socket or TLS failure is preserved as :attr:__cause__.
SMTPError ¶
Bases: MxRavenError
An SMTP reply that rejected a command.
Inspect :attr:code or :attr:enhanced_code to make a delivery decision.
:attr:message is the server's human-readable text and is not stable.
Initialize the error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
int
|
The three-digit SMTP reply code. |
required |
message
|
str
|
The server's reply text. |
required |
enhanced_code
|
str
|
The RFC 3463 |
''
|
Source code in src/mxraven/errors.py
SMTPProtocolError ¶
Bases: MxRavenError
The server sent a reply the client could not parse.
SMTPTimeoutError ¶
Bases: SMTPConnectionError
An SMTP operation exceeded its deadline.
SMTPTransactionError ¶
SMTPTransactionError(message: str, result: Result)
Bases: MxRavenError
Every envelope recipient was rejected.
The per-recipient detail is available on :attr:result.
Initialize the error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
A human-readable summary. |
required |
result
|
Result
|
The transaction result, including each recipient's outcome. |
required |
Source code in src/mxraven/errors.py
WebhookError ¶
Bases: MxRavenError
Base class for webhook verification and decoding failures.
Asynchronous client¶
aio ¶
Asynchronous mxRaven mail runtime.
Exposes :class:mxraven.aio.Client, the asyncio counterpart of
:class:mxraven.Client, with the same semantics and error surface.
Client ¶
Client(
host: str,
*,
port: int = DEFAULT_ADDRESS_PORT,
username: str,
secret: str,
tls: SSLContext | None = None,
pool_size: int = _DEFAULT_POOL_SIZE,
connect_timeout: float = _DEFAULT_CONNECT_TIMEOUT,
read_timeout: float = _DEFAULT_READ_TIMEOUT,
write_timeout: float = _DEFAULT_WRITE_TIMEOUT,
local_name: str | None = None,
starttls: bool = True,
require_tls: bool = True,
implicit_tls: bool = False,
local_address: str | None = None,
auth_mechanisms: Sequence[str] = (),
)
Submits mail to the mxRaven SMTP submission service using asyncio.
A client maintains a bounded pool of authenticated connections. Use it as
an asynchronous context manager or call :meth:close.
Example
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host
|
str
|
The submission server host; a port must not be included. |
required |
port
|
int
|
The submission server port. Defaults to |
DEFAULT_ADDRESS_PORT
|
username
|
str
|
The submission API key username. |
required |
secret
|
str
|
The submission API key secret. |
required |
tls
|
SSLContext | None
|
A custom STARTTLS |
None
|
pool_size
|
int
|
The maximum number of pooled connections. Defaults to |
_DEFAULT_POOL_SIZE
|
connect_timeout
|
float
|
The TCP connect timeout in seconds. |
_DEFAULT_CONNECT_TIMEOUT
|
read_timeout
|
float
|
The per-reply read timeout in seconds. |
_DEFAULT_READ_TIMEOUT
|
write_timeout
|
float
|
The per-write timeout in seconds. |
_DEFAULT_WRITE_TIMEOUT
|
local_name
|
str | None
|
The name sent in |
None
|
starttls
|
bool
|
Whether to issue |
True
|
require_tls
|
bool
|
Whether to fail when |
True
|
implicit_tls
|
bool
|
Whether the connection uses TLS from the first byte
(SMTPS, typically port 465). When true, |
False
|
local_address
|
str | None
|
An optional local bind address, |
None
|
auth_mechanisms
|
Sequence[str]
|
Preferred SASL mechanisms in order. |
()
|
Raises:
| Type | Description |
|---|---|
ValueError
|
A required argument is missing or invalid. |
Initialize the client and its connection pool.
Source code in src/mxraven/aio/client.py
__aenter__
async
¶
__aexit__
async
¶
close
async
¶
send
async
¶
Submit a composed message and return the server's result.
Raises:
| Type | Description |
|---|---|
TypeError
|
|
ValueError
|
The message cannot be built. |
SMTPError
|
The server rejected a command. |
SMTPTransactionError
|
Every recipient was rejected. |
Source code in src/mxraven/aio/client.py
send_raw
async
¶
send_raw(
envelope: Envelope,
data: bytes
| bytearray
| memoryview
| Iterable[bytes]
| AsyncIterable[bytes],
*,
prefer_bdat: bool = False,
) -> Result
Stream an already serialized RFC 5322 message with an explicit envelope.
Raises:
| Type | Description |
|---|---|
TypeError
|
|
ValueError
|
The envelope has no recipients or an address is invalid. |
SMTPError
|
The server rejected a command. |
SMTPTransactionError
|
Every recipient was rejected. |
Source code in src/mxraven/aio/client.py
Webhooks¶
webhook ¶
mxRaven webhook verification and decoding.
Verifies the X-MxRaven-* HMAC-SHA256 signature over the canonical request
string and decodes DELIVER_WEBHOOK (inbound email) and NOTIFY_WEBHOOK
(SMTP or object-storage delivery status) payloads.
Example
DeliveryStatus
dataclass
¶
DeliveryStatus(
task_id: str = "",
tenant_id: str = "",
listener_id: str = "",
message_ref: str = "",
status: str = "",
attempt: int = 0,
accepted_at_utc: int = 0,
occurred_at_utc: int = 0,
source_ip: str = "",
destination_domain: str = "",
remote_host: str = "",
smtp_code: int = 0,
enhanced_status_code: str = "",
reason: str = "",
remote_response: str = "",
next_retry_at_utc: int = 0,
correlation_task_id: str = "",
)
The SMTP NOTIFY_WEBHOOK payload.
Envelope
dataclass
¶
The SMTP envelope of a message.
Event
dataclass
¶
Event(
type: EventType,
inbound_email: InboundEmail | None = None,
delivery_status: DeliveryStatus | None = None,
storage_status: StorageStatus | None = None,
)
A decoded webhook delivery.
Exactly one of inbound_email, delivery_status, or storage_status is
set, matching type.
EventType ¶
Bases: StrEnum
The shape of a decoded webhook payload.
InboundEmail
dataclass
¶
InboundEmail(
event_type: str = "",
task_id: str = "",
tenant_id: str = "",
listener_id: str = "",
message_ref: str = "",
attempt: int = 0,
accepted_at_utc: int = 0,
occurred_at_utc: int = 0,
routing_decision: RoutingDecision | None = None,
verdicts: Verdicts | None = None,
envelope: Envelope = Envelope(),
message: MessageSummary = MessageSummary(),
headers: tuple[HeaderField, ...] = (),
raw_email: RawEmail = RawEmail(),
)
The DELIVER_WEBHOOK payload: a complete inbound message.
InvalidSignatureError ¶
Bases: WebhookError
A webhook request's HMAC signature did not match.
MessageSummary
dataclass
¶
MessageSummary(
subject: str = "",
from_: tuple[str, ...] = (),
to: tuple[str, ...] = (),
cc: tuple[str, ...] = (),
message_id: str = "",
date: str = "",
)
A summary of the parsed message headers.
RawEmail
dataclass
¶
RawEmail(
url: str = "",
token_type: str = "",
access_token: str = "",
expires_at_utc: int = 0,
size_bytes: int = 0,
sha256_hex: str = "",
content_type: str = "",
)
Time-limited access to the raw RFC 822 message.
fetch ¶
Download and verify the raw message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float
|
The request timeout in seconds. |
60.0
|
transport
|
SyncTransport | None
|
An optional transport to use instead of the default. |
None
|
Returns:
| Type | Description |
|---|---|
bytes
|
The verified raw message bytes. |
Raises:
| Type | Description |
|---|---|
WebhookError
|
The URL or token is missing, the response is not 200, the size does not match, or the SHA-256 digest does not match. |
Note
The raw-email access token is a secret; do not log it.
Source code in src/mxraven/webhook/payload.py
fetch_async
async
¶
Asynchronously download and verify the raw message.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float
|
The request timeout in seconds. |
60.0
|
transport
|
AsyncTransport | None
|
An optional transport to use instead of the default. |
None
|
Returns:
| Type | Description |
|---|---|
bytes
|
The verified raw message bytes. |
Raises:
| Type | Description |
|---|---|
WebhookError
|
The URL or token is missing, the response is not 200, the size does not match, or the SHA-256 digest does not match. |
Note
The raw-email access token is a secret; do not log it.
Source code in src/mxraven/webhook/payload.py
RoutingDecision
dataclass
¶
RoutingDecision(
terminal_action: str = "",
matched_rule_id: str = "",
used_listener_default: bool = False,
)
The final routing outcome for an inbound message.
Attributes:
| Name | Type | Description |
|---|---|---|
terminal_action |
str
|
The final terminal action. Compare it against the
:class: |
matched_rule_id |
str
|
The rule that selected the action, when one matched. |
used_listener_default |
bool
|
Whether the listener default was used. |
StatusOutcome ¶
Bases: StrEnum
The lifecycle state reported by a delivery status webhook.
The set is open on the wire: a status this SDK does not know is still decoded and preserved as its raw string.
StorageStatus
dataclass
¶
StorageStatus(
event_type: str = "",
task_id: str = "",
tenant_id: str = "",
listener_id: str = "",
message_ref: str = "",
status: str = "",
attempt: int = 0,
accepted_at_utc: int = 0,
occurred_at_utc: int = 0,
storage_ref: str = "",
bucket_name: str = "",
object_key: str = "",
endpoint_host: str = "",
status_code: int = 0,
error_code: str = "",
message: str = "",
next_retry_at_utc: int = 0,
)
The object-storage NOTIFY_WEBHOOK payload.
TerminalAction ¶
Bases: StrEnum
The final routing action recorded for an inbound message.
Verdicts
dataclass
¶
Verdicts(
action: str = "",
score: float = 0.0,
required_score: float = 0.0,
is_spam: bool = False,
has_malware: bool = False,
malware_names: tuple[str, ...] = (),
is_skipped: bool = False,
error: str = "",
)
Spam and malware scan results.
Verifier ¶
Verifier(
*,
secret: str = "",
keys: Mapping[str, str] | None = None,
tolerance: float = _DEFAULT_TOLERANCE,
max_body_bytes: int = _DEFAULT_MAX_BODY,
clock: Callable[[], float] | None = None,
)
Verifies the signature of an mxRaven webhook request.
A verifier is safe for concurrent use once constructed. Configure it with a signing secret, with per-key secrets for rotation, or both.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
secret
|
str
|
The signing secret, used as literal bytes and never decoded. |
''
|
keys
|
Mapping[str, str] | None
|
Per-key secrets for rotation, keyed by signing key ID. |
None
|
tolerance
|
float
|
The maximum accepted clock skew in seconds; |
_DEFAULT_TOLERANCE
|
max_body_bytes
|
int
|
The maximum body size in bytes. Defaults to 1 MiB. |
_DEFAULT_MAX_BODY
|
clock
|
Callable[[], float] | None
|
A callable returning the current Unix time; used for testing. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
No secret was supplied or an option is invalid. |
Initialize the verifier.
Source code in src/mxraven/webhook/verifier.py
verify ¶
verify(request: WebhookRequest) -> None
Verify a request's signature.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
WebhookRequest
|
The inbound request. |
required |
Raises:
| Type | Description |
|---|---|
InvalidSignatureError
|
The signature does not match. |
WebhookError
|
A required header is missing, the timestamp is stale, or the body exceeds the configured limit. |
Source code in src/mxraven/webhook/verifier.py
verify_and_decode ¶
verify_and_decode(request: WebhookRequest) -> Event
Verify a request and decode its payload.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
WebhookRequest
|
The inbound request. |
required |
Returns:
| Type | Description |
|---|---|
Event
|
The decoded event. |
Raises:
| Type | Description |
|---|---|
InvalidSignatureError
|
The signature does not match. |
WebhookError
|
Verification or decoding failed. |
Source code in src/mxraven/webhook/verifier.py
WebhookError ¶
Bases: MxRavenError
Base class for webhook verification and decoding failures.
WebhookRequest
dataclass
¶
The parts of an inbound HTTP request the signature covers.
Header names are stored lowercased so lookups are case-insensitive.
Attributes:
| Name | Type | Description |
|---|---|---|
method |
str
|
The HTTP method. |
url |
str
|
The public URL the caller used, including scheme and host. |
headers |
Mapping[str, str]
|
The request headers keyed by lowercase name. |
body |
bytes
|
The exact raw request body bytes. |
create
classmethod
¶
create(
method: str,
url: str,
headers: Mapping[str, str],
body: bytes | bytearray | memoryview = b"",
) -> WebhookRequest
Build a request, normalizing header names and body type.
Source code in src/mxraven/webhook/request.py
from_asgi
classmethod
¶
from_asgi(
scope: Mapping[str, object], body: bytes
) -> WebhookRequest
Build a request from an ASGI scope and pre-read body.
Source code in src/mxraven/webhook/request.py
from_wsgi
classmethod
¶
from_wsgi(environ: Mapping[str, object]) -> WebhookRequest
Build a request from a WSGI environ.
Note
The signature covers the exact raw body and the public URL, so read the body here rather than letting a framework parse it first.
Source code in src/mxraven/webhook/request.py
MIME parsing¶
mime ¶
Inbound email parsing for mxRaven deliveries.
mxRaven webhook payloads describe inbound mail as JSON, and the full message
remains available as raw RFC 5322 bytes behind the payload's raw_email
reference. :func:parse_email turns those bytes into headers, addresses,
bodies, and decoded attachments; :func:fetch_and_parse_raw_email downloads,
verifies, and parses them in one step.
Example
from mxraven.mime import fetch_and_parse_raw_email
from mxraven.webhook import Verifier, EventType
verifier = Verifier(secret=signing_secret)
event = verifier.verify_and_decode(request)
if event.type is EventType.INBOUND_EMAIL and event.inbound_email is not None:
email = fetch_and_parse_raw_email(event.inbound_email.raw_email)
print(email.subject, len(email.attachments))
The parser is defensive: malformed messages yield the best available
interpretation instead of an error, and unknown charsets fall back to UTF-8 and
then Windows-1252. It understands multipart/mixed,
multipart/alternative, and multipart/related, decodes base64 and
quoted-printable transfer encodings, decodes RFC 2047 encoded words, and
reassembles RFC 2231 parameters. message/rfc822 parts are surfaced as
attachments rather than parsed recursively. Only unbounded input -- very deep
nesting or thousands of parts -- is rejected.
MimeError ¶
Bases: MxRavenError
A MIME message cannot be parsed within the parser's limits.
Malformed message content never raises; the parser returns the best interpretation it can. Only structurally unbounded input raises, so an untrusted message cannot exhaust the stack or memory.
ParsedAddress
dataclass
¶
One mailbox from a parsed address header.
Attributes:
| Name | Type | Description |
|---|---|---|
address |
str
|
The bare |
name |
str
|
The display name, with RFC 2047 encoded words decoded, when one was present. |
ParsedAttachment
dataclass
¶
ParsedAttachment(
content_type: str,
content: bytes,
filename: str = "",
content_id: str = "",
inline: bool = False,
)
One non-body MIME part.
Attributes:
| Name | Type | Description |
|---|---|---|
content_type |
str
|
The media type, for example |
content |
bytes
|
The decoded attachment bytes. It is a fresh copy that the caller owns and may mutate. |
filename |
str
|
The decoded filename, when one was present. |
content_id |
str
|
The |
inline |
bool
|
Whether the part is marked |
ParsedEmail
dataclass
¶
ParsedEmail(
headers: tuple[ParsedHeader, ...] = (),
subject: str = "",
from_: tuple[ParsedAddress, ...] = (),
to: tuple[ParsedAddress, ...] = (),
cc: tuple[ParsedAddress, ...] = (),
bcc: tuple[ParsedAddress, ...] = (),
reply_to: tuple[ParsedAddress, ...] = (),
message_id: str = "",
date: datetime | None = None,
text: str = "",
html: str = "",
attachments: tuple[ParsedAttachment, ...] = (),
)
A parsed inbound RFC 5322 / MIME message.
An empty value means the corresponding header or body was absent.
Attributes:
| Name | Type | Description |
|---|---|---|
headers |
tuple[ParsedHeader, ...]
|
Every header field, in order, with continuation lines unfolded. Values are not decoded. |
subject |
str
|
The decoded |
from_ |
tuple[ParsedAddress, ...]
|
The |
to |
tuple[ParsedAddress, ...]
|
The |
cc |
tuple[ParsedAddress, ...]
|
The |
bcc |
tuple[ParsedAddress, ...]
|
The |
reply_to |
tuple[ParsedAddress, ...]
|
The |
message_id |
str
|
The |
date |
datetime | None
|
The parsed |
text |
str
|
The decoded first |
html |
str
|
The decoded first |
attachments |
tuple[ParsedAttachment, ...]
|
Every non-body part, with transfer encoding and filename
decoding applied. |
ParsedHeader
dataclass
¶
One header field from a parsed message.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The field name as it appeared in the message. |
value |
str
|
The unfolded field value. Encoded words are not decoded; use
:attr: |
fetch_and_parse_raw_email ¶
fetch_and_parse_raw_email(
raw: RawEmail,
*,
timeout: float = _DEFAULT_FETCH_TIMEOUT,
transport: SyncTransport | None = None,
) -> ParsedEmail
Download the raw message an inbound-email payload references and parse it.
The download verifies the declared size and SHA-256 digest and is bounded
exactly like :meth:mxraven.webhook.RawEmail.fetch; the payload's
short-lived bearer token is sent in the Authorization header and must
not be logged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
RawEmail
|
The |
required |
timeout
|
float
|
The request timeout in seconds. |
_DEFAULT_FETCH_TIMEOUT
|
transport
|
SyncTransport | None
|
An optional transport to use instead of the default. |
None
|
Returns:
| Type | Description |
|---|---|
ParsedEmail
|
The parsed message. Attachment content is a fresh copy. |
Raises:
| Type | Description |
|---|---|
WebhookError
|
The download failed or the size or digest did not match. |
MimeNestingError
|
The message nests deeper than the parser allows. |
MimePartsError
|
The message has more MIME entities than the parser allows. |
Source code in src/mxraven/mime/fetch.py
parse_email ¶
parse_email(
data: bytes | bytearray | memoryview,
) -> ParsedEmail
Parse an inbound RFC 5322 / MIME message.
The parser never validates the message against the grammar and never raises
on malformed content; it extracts what it can. Multipart mixed,
alternative, and related structures are traversed, base64 and
quoted-printable transfer encodings are decoded, RFC 2047 encoded words
in structured headers are decoded, and RFC 2231 parameters are reassembled.
message/rfc822 parts are surfaced as attachments rather than parsed
recursively.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
bytes | bytearray | memoryview
|
The raw message bytes. |
required |
Returns:
| Type | Description |
|---|---|
ParsedEmail
|
The parsed message. Attachment content is a fresh copy. |
Raises:
| Type | Description |
|---|---|
MimeNestingError
|
The message nests deeper than the parser allows. |
MimePartsError
|
The message has more MIME entities than the parser allows. |
Example
Source code in src/mxraven/mime/parse.py
Feedback¶
feedback ¶
mxRaven recipient feedback.
Teaches the spam filter with tenant-supplied raw messages and performs RFC 8058 one-click unsubscribes.
AsyncClient ¶
AsyncClient(
base_url: str,
*,
username: str = "",
secret: str = "",
timeout: float = _DEFAULT_TIMEOUT,
transport: AsyncTransport | None = None,
)
Calls the mxRaven feedback service using asyncio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_url
|
str
|
The feedback service base URL. |
required |
username
|
str
|
The submission API key username, used for learning. |
''
|
secret
|
str
|
The submission API key secret, used for learning. |
''
|
timeout
|
float
|
The request timeout in seconds. Defaults to |
_DEFAULT_TIMEOUT
|
transport
|
AsyncTransport | None
|
An optional asynchronous HTTP transport. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Initialize the client.
Source code in src/mxraven/feedback/client.py
learn
async
¶
learn(
disposition: Disposition,
raw_mime: bytes | bytearray | memoryview,
) -> LearningResult
Submit one training example asynchronously.
Source code in src/mxraven/feedback/client.py
learn_ham
async
¶
learn_ham(
raw_mime: bytes | bytearray | memoryview,
) -> LearningResult
Teach the spam filter that raw_mime is not spam.
learn_spam
async
¶
learn_spam(
raw_mime: bytes | bytearray | memoryview,
) -> LearningResult
unsubscribe
async
¶
Perform an RFC 8058 one-click unsubscribe for token.
Source code in src/mxraven/feedback/client.py
Client ¶
Client(
base_url: str,
*,
username: str = "",
secret: str = "",
timeout: float = _DEFAULT_TIMEOUT,
transport: SyncTransport | None = None,
)
Calls the mxRaven feedback service synchronously.
A client is safe for concurrent use.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_url
|
str
|
The feedback service base URL, for example
|
required |
username
|
str
|
The submission API key username, used for learning. |
''
|
secret
|
str
|
The submission API key secret, used for learning. |
''
|
timeout
|
float
|
The request timeout in seconds. Defaults to |
_DEFAULT_TIMEOUT
|
transport
|
SyncTransport | None
|
An optional HTTP transport to use instead of the default. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Initialize the client.
Source code in src/mxraven/feedback/client.py
learn ¶
learn(
disposition: Disposition,
raw_mime: bytes | bytearray | memoryview,
) -> LearningResult
Submit one training example.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
disposition
|
Disposition
|
The training label. |
required |
raw_mime
|
bytes | bytearray | memoryview
|
The exact raw RFC 822 bytes mxRaven processed; the service matches them against stored evidence by SHA-256. |
required |
Returns:
| Type | Description |
|---|---|
LearningResult
|
The service's learning result. |
Raises:
| Type | Description |
|---|---|
ValueError
|
Credentials are missing or the disposition is invalid. |
FeedbackError
|
The service returned a non-success response. |
Source code in src/mxraven/feedback/client.py
learn_ham ¶
learn_ham(
raw_mime: bytes | bytearray | memoryview,
) -> LearningResult
learn_spam ¶
learn_spam(
raw_mime: bytes | bytearray | memoryview,
) -> LearningResult
unsubscribe ¶
Perform an RFC 8058 one-click unsubscribe for token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
The unsubscribe token from the |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
The token is empty. |
FeedbackError
|
The service returned a non-success response. |
Source code in src/mxraven/feedback/client.py
Disposition ¶
Bases: StrEnum
The training label for a message.
FeedbackError ¶
Bases: MxRavenError
The feedback service returned a non-success response.
Initialize the error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
status_code
|
int
|
The HTTP response status. |
required |
message
|
str
|
The service's |
''
|
Source code in src/mxraven/errors.py
LearningResult
dataclass
¶
LearningResult(
status: str = "",
disposition: str = "",
tenant_id: str = "",
listener_id: str = "",
matched_hash_kind: str = "",
)
The outcome of a successful learning request.
Attributes:
| Name | Type | Description |
|---|---|---|
status |
str
|
The service status, normally |
disposition |
str
|
The training label that was applied. |
tenant_id |
str
|
The tenant that owns the matched message. |
listener_id |
str
|
The listener that processed the matched message. |
matched_hash_kind |
str
|
Which stored hash matched the submitted bytes. |
Notifications¶
notify ¶
mxRaven client notification events.
Consumes the durable mail events a listener publishes over NATS JetStream,
using the same API key as SMTP submission. The transport requires the optional
notify extra; install mxraven[notify].
Example
from mxraven.notify import NotificationClient
with NotificationClient.connect(
url="wss://nats.mxraven.com",
username="mxr_tx_ab12cd34ef56",
secret=api_key_secret,
listener=listener_id,
) as client:
for delivery in client.events():
if delivery.event.kind == "accepted":
handle(delivery.event.accepted)
delivery.ack()
break
NotificationHandler
module-attribute
¶
Processes one notification event synchronously.
Returning None acknowledges the event. Raising leaves it unacknowledged so
the server redelivers it with exponential backoff. Returning
:data:STOP_CONSUMING acknowledges the event and stops consumption.
NotificationResult
module-attribute
¶
What a notification handler returns: None to acknowledge, or
:data:STOP_CONSUMING to acknowledge and stop.
STOP_CONSUMING
module-attribute
¶
STOP_CONSUMING: Final[StopConsuming] = StopConsuming()
Return this from a handler to acknowledge the current event and stop.
AttemptChannel ¶
Bases: StrEnum
The egress worker that produced an :class:AttemptEvent.
The set is open on the wire: a channel this SDK does not know is preserved
as a raw string on :attr:AttemptEvent.channel, which still compares equal
to the matching member.
AttemptEvent
dataclass
¶
AttemptEvent(
channel: str = "",
status: str = "",
event_type: str = "",
task_id: str = "",
tenant_id: str = "",
listener_id: str = "",
message_ref: str = "",
correlation_id: str = "",
attempt: int = 0,
accepted_at_utc: int = 0,
occurred_at_utc: int = 0,
source_ip: str = "",
destination_domain: str = "",
remote_host: str = "",
smtp_code: int = 0,
enhanced_status_code: str = "",
reason: str = "",
remote_response: str = "",
correlation_task_id: str = "",
storage_ref: str = "",
bucket_name: str = "",
object_key: str = "",
endpoint_host: str = "",
error_code: str = "",
status_code: int = 0,
message: str = "",
next_retry_at_utc: int = 0,
retry_after_ms: int = 0,
target_url_host: str = "",
webhook_ref: str = "",
signing_key_kid: str = "",
raw_token_ttl_seconds: int = 0,
)
One delivery attempt for an accepted message.
:attr:channel names the egress worker that produced the attempt; the
remaining fields describe that worker's protocol and default when they do
not apply. A rule that chains side-effect actions reports each action it
triggers, so one message can produce more than one attempt event. Every
attempt carries a :attr:correlation_id that points at the task_id of
the accepted event for the message it stems from.
Attributes:
| Name | Type | Description |
|---|---|---|
event_type |
str
|
The producer's event-type marker when one is set. SMTP attempt events omit it. |
channel |
str
|
The egress worker that produced the attempt. Compare it
against :class: |
task_id |
str
|
The delivery task. For a submission listener it equals the
accepted event's |
tenant_id |
str
|
The owning tenant. |
listener_id |
str
|
The source listener. |
correlation_id |
str
|
The |
status |
str
|
The attempt outcome, one of :class: |
attempt |
int
|
The delivery attempt number, starting at 1. |
accepted_at_utc |
int
|
The Unix time, in seconds, when mxRaven accepted the message. Zero when the producer does not report it. |
occurred_at_utc |
int
|
The Unix time, in seconds, when the attempt was built. |
source_ip |
str
|
The SMTP egress source address, when known. |
destination_domain |
str
|
The recipient domain for an SMTP attempt. |
remote_host |
str
|
The remote MTA hostname, when known. |
smtp_code |
int
|
The remote SMTP reply code. |
enhanced_status_code |
str
|
The RFC 3463 enhanced status code. |
reason |
str
|
The bounded, machine-readable delivery classification. |
remote_response |
str
|
The remote reply text, truncated by the server. |
correlation_task_id |
str
|
Links a generated DSN back to its original task. |
storage_ref |
str
|
The storage integration identifier for an |
bucket_name |
str
|
The destination bucket for an |
object_key |
str
|
The stored object key for an |
endpoint_host |
str
|
The storage endpoint host for an |
error_code |
str
|
The storage provider error code, when one was returned. |
status_code |
int
|
The storage or webhook HTTP response status. |
message |
str
|
The failure description, truncated by the server. |
next_retry_at_utc |
int
|
The Unix time, in seconds, of the next attempt when the attempt will be retried. |
retry_after_ms |
int
|
The webhook retry-after hint, in milliseconds. |
target_url_host |
str
|
The webhook target host. |
webhook_ref |
str
|
The webhook endpoint identifier. |
signing_key_kid |
str
|
The key ID used to sign the webhook attempt. |
raw_token_ttl_seconds |
int
|
The raw-message token lifetime the webhook attempt used. |
DecodeError ¶
Bases: NotificationError
A notification body that does not match the SDK's payload model.
This normally means the SDK is older than the server that produced the event. The event is terminated rather than redelivered, because redelivery can never succeed. When an error handler is configured, it receives the error and consumption continues; otherwise consumption stops and this error is raised.
Initialize the error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject
|
str
|
The notification subject the undecodable body was published to. |
required |
message
|
str
|
A short description of the decode failure. |
''
|
Source code in src/mxraven/errors.py
InboundEmail
dataclass
¶
InboundEmail(
event_type: str = "",
task_id: str = "",
tenant_id: str = "",
listener_id: str = "",
message_ref: str = "",
attempt: int = 0,
accepted_at_utc: int = 0,
occurred_at_utc: int = 0,
routing_decision: RoutingDecision | None = None,
verdicts: Verdicts | None = None,
envelope: Envelope = Envelope(),
message: MessageSummary = MessageSummary(),
headers: tuple[HeaderField, ...] = (),
raw_email: RawEmail = RawEmail(),
)
The DELIVER_WEBHOOK payload: a complete inbound message.
NotificationClient ¶
NotificationClient(
*,
url: str,
username: str,
secret: str,
listener: str,
consumer: str | None = None,
connect_timeout: float = DEFAULT_CONNECT_TIMEOUT,
tls: SSLContext | None = None,
on_error: ErrorHandler | None = None,
)
Consumes one listener's client notification events synchronously.
Notifications are off by default; enable them for the listener with the mxRaven administrative API first, which creates the durable consumer this client reads from. The connection authenticates with the listener's ordinary API key and is granted access only to that listener's consumer.
A client is safe for concurrent use. A consume call and an events
iterator share the durable consumer with any other client using the same
API key: events are distributed between them, not duplicated to each.
This client owns a private asyncio event loop on a worker thread. Handlers
run on a worker thread, not the calling thread, so a slow handler does not
block close from another thread. Do not call close from the thread that
is blocked in consume.
Use :meth:connect to build a connected client; do not construct one
directly. A client is a context manager.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The client notification tier WebSocket URL, for example
|
required |
username
|
str
|
The listener API key prefix, for example
|
required |
secret
|
str
|
The listener API key secret. Treat it as a secret and do not log it. |
required |
listener
|
str
|
The listener whose events are consumed. It must not contain
|
required |
consumer
|
str | None
|
Overrides the durable consumer name. Defaults to
|
None
|
connect_timeout
|
float
|
The connection deadline in seconds. Defaults to
|
DEFAULT_CONNECT_TIMEOUT
|
tls
|
SSLContext | None
|
An optional TLS context for |
None
|
on_error
|
ErrorHandler | None
|
Receives non-fatal connection and decode errors. It runs on
the worker thread and must return promptly. When omitted, an
undecodable event stops consumption with a |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
An option is missing or invalid. |
Validate the options. The client is not connected until connect.
Source code in src/mxraven/notify/client.py
__enter__ ¶
__exit__ ¶
close ¶
Release the client's connection.
Active consume calls and iterators stop, and events they did not
acknowledge are redelivered later. Safe to call more than once.
Source code in src/mxraven/notify/client.py
connect
classmethod
¶
connect(
*,
url: str,
username: str,
secret: str,
listener: str,
consumer: str | None = None,
connect_timeout: float = DEFAULT_CONNECT_TIMEOUT,
tls: SSLContext | None = None,
on_error: ErrorHandler | None = None,
) -> NotificationClient
Connect to the notification tier and return a client.
The initial connection fails here rather than on the first event. After a successful connection the transport reconnects indefinitely when the tier is unreachable, and the consumer resumes from its acknowledgement floor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The notification tier WebSocket URL ( |
required |
username
|
str
|
The listener API key prefix. |
required |
secret
|
str
|
The listener API key secret. |
required |
listener
|
str
|
The listener whose events are consumed. |
required |
consumer
|
str | None
|
An optional durable consumer name override. |
None
|
connect_timeout
|
float
|
The connection deadline in seconds. |
DEFAULT_CONNECT_TIMEOUT
|
tls
|
SSLContext | None
|
An optional TLS context for |
None
|
on_error
|
ErrorHandler | None
|
An optional non-fatal error handler. |
None
|
Returns:
| Type | Description |
|---|---|
NotificationClient
|
A connected client. |
Raises:
| Type | Description |
|---|---|
ValueError
|
An option is missing or invalid. |
NotificationError
|
The connection failed, the API key was rejected,
or the optional |
Source code in src/mxraven/notify/client.py
consume ¶
Consume events until stopped, closed, or the handler stops.
A handler that returns None acknowledges the event. A handler that
raises leaves it unacknowledged so the server redelivers it after a
delay that doubles with each attempt up to 30 seconds. A handler that
returns STOP_CONSUMING acknowledges the event and stops. An
undecodable event is terminated so it cannot poison the stream, and
either stops consumption with a DecodeError or is reported to
on_error and skipped.
This call blocks until consumption stops. Call :meth:close from
another thread, or return STOP_CONSUMING from the handler, to stop it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
NotificationHandler
|
Processes one event at a time on a worker thread. |
required |
Raises:
| Type | Description |
|---|---|
NotificationError
|
The client is closed, consumption is already running, or consumption failed. |
DecodeError
|
An event could not be decoded and no error handler is configured. |
Source code in src/mxraven/notify/client.py
events ¶
events() -> NotificationIterator
Return an iterator for manual acknowledgement.
The caller acknowledges each event with
:meth:~NotificationDelivery.ack, nak, or term. Stopping iteration
leaves unacknowledged events for redelivery after the consumer's
acknowledgement timeout.
Source code in src/mxraven/notify/client.py
NotificationDelivery ¶
NotificationDelivery(
runner: LoopRunner, delivery: NotificationDelivery
)
One event yielded by a :class:NotificationIterator.
Exactly one of ack, nak, or term must be called for the event;
calling more than one raises NotificationError.
Wrap an asynchronous delivery for blocking settlement.
Source code in src/mxraven/notify/client.py
NotificationError ¶
Bases: MxRavenError
A failure connecting to or consuming the client notification stream.
The underlying transport or JetStream failure is preserved as
:attr:__cause__. Most connection failures are transient and are retried
automatically; this error is only raised when an operation cannot continue.
NotificationEvent
dataclass
¶
NotificationEvent(
kind: NotificationKind,
accepted: InboundEmail | None = None,
attempt: AttemptEvent | None = None,
)
A decoded client notification.
Exactly one of :attr:accepted or :attr:attempt is set, matching
:attr:kind.
NotificationIterator ¶
NotificationIterator(
runner: LoopRunner, iterator: NotificationIterator
)
An iterator over a listener's notification events.
Use for and break when finished; the iterator stops pulling and
leaves unacknowledged events for redelivery. Iteration also stops when the
client's connection closes.
Wrap an asynchronous iterator for blocking iteration.
Source code in src/mxraven/notify/client.py
__iter__ ¶
__next__ ¶
__next__() -> NotificationDelivery
Yield the next delivery, or stop iteration.
Source code in src/mxraven/notify/client.py
NotificationKind ¶
Bases: StrEnum
The kind of notification a :class:NotificationEvent carries.
StopConsuming ¶
The type of the :data:STOP_CONSUMING sentinel.
Do not construct it directly; return the exported :data:STOP_CONSUMING
singleton instead.
decode_notification ¶
decode_notification(
subject: str, body: bytes
) -> NotificationEvent
Decode one notification body.
The event kind is the final token of subject. When the subject is
unavailable or unrecognized, the body shape is probed instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject
|
str
|
The notification subject the body was published to. |
required |
body
|
bytes
|
The raw JSON body bytes. |
required |
Returns:
| Type | Description |
|---|---|
NotificationEvent
|
The decoded notification. |
Raises:
| Type | Description |
|---|---|
DecodeError
|
The body is not valid JSON, has a field of the wrong type, or cannot be identified. |
Source code in src/mxraven/notify/event.py
Asynchronous notifications¶
notify ¶
Asynchronous mxRaven client notification events.
The asyncio counterpart of :mod:mxraven.notify, with the same payloads and
error surface. The transport requires the optional notify extra; install
mxraven[notify].
NotificationHandler
module-attribute
¶
Processes one notification event synchronously.
Returning None acknowledges the event. Raising leaves it unacknowledged so
the server redelivers it with exponential backoff. Returning
:data:STOP_CONSUMING acknowledges the event and stops consumption.
NotificationResult
module-attribute
¶
What a notification handler returns: None to acknowledge, or
:data:STOP_CONSUMING to acknowledge and stop.
STOP_CONSUMING
module-attribute
¶
STOP_CONSUMING: Final[StopConsuming] = StopConsuming()
Return this from a handler to acknowledge the current event and stop.
AttemptChannel ¶
Bases: StrEnum
The egress worker that produced an :class:AttemptEvent.
The set is open on the wire: a channel this SDK does not know is preserved
as a raw string on :attr:AttemptEvent.channel, which still compares equal
to the matching member.
AttemptEvent
dataclass
¶
AttemptEvent(
channel: str = "",
status: str = "",
event_type: str = "",
task_id: str = "",
tenant_id: str = "",
listener_id: str = "",
message_ref: str = "",
correlation_id: str = "",
attempt: int = 0,
accepted_at_utc: int = 0,
occurred_at_utc: int = 0,
source_ip: str = "",
destination_domain: str = "",
remote_host: str = "",
smtp_code: int = 0,
enhanced_status_code: str = "",
reason: str = "",
remote_response: str = "",
correlation_task_id: str = "",
storage_ref: str = "",
bucket_name: str = "",
object_key: str = "",
endpoint_host: str = "",
error_code: str = "",
status_code: int = 0,
message: str = "",
next_retry_at_utc: int = 0,
retry_after_ms: int = 0,
target_url_host: str = "",
webhook_ref: str = "",
signing_key_kid: str = "",
raw_token_ttl_seconds: int = 0,
)
One delivery attempt for an accepted message.
:attr:channel names the egress worker that produced the attempt; the
remaining fields describe that worker's protocol and default when they do
not apply. A rule that chains side-effect actions reports each action it
triggers, so one message can produce more than one attempt event. Every
attempt carries a :attr:correlation_id that points at the task_id of
the accepted event for the message it stems from.
Attributes:
| Name | Type | Description |
|---|---|---|
event_type |
str
|
The producer's event-type marker when one is set. SMTP attempt events omit it. |
channel |
str
|
The egress worker that produced the attempt. Compare it
against :class: |
task_id |
str
|
The delivery task. For a submission listener it equals the
accepted event's |
tenant_id |
str
|
The owning tenant. |
listener_id |
str
|
The source listener. |
correlation_id |
str
|
The |
status |
str
|
The attempt outcome, one of :class: |
attempt |
int
|
The delivery attempt number, starting at 1. |
accepted_at_utc |
int
|
The Unix time, in seconds, when mxRaven accepted the message. Zero when the producer does not report it. |
occurred_at_utc |
int
|
The Unix time, in seconds, when the attempt was built. |
source_ip |
str
|
The SMTP egress source address, when known. |
destination_domain |
str
|
The recipient domain for an SMTP attempt. |
remote_host |
str
|
The remote MTA hostname, when known. |
smtp_code |
int
|
The remote SMTP reply code. |
enhanced_status_code |
str
|
The RFC 3463 enhanced status code. |
reason |
str
|
The bounded, machine-readable delivery classification. |
remote_response |
str
|
The remote reply text, truncated by the server. |
correlation_task_id |
str
|
Links a generated DSN back to its original task. |
storage_ref |
str
|
The storage integration identifier for an |
bucket_name |
str
|
The destination bucket for an |
object_key |
str
|
The stored object key for an |
endpoint_host |
str
|
The storage endpoint host for an |
error_code |
str
|
The storage provider error code, when one was returned. |
status_code |
int
|
The storage or webhook HTTP response status. |
message |
str
|
The failure description, truncated by the server. |
next_retry_at_utc |
int
|
The Unix time, in seconds, of the next attempt when the attempt will be retried. |
retry_after_ms |
int
|
The webhook retry-after hint, in milliseconds. |
target_url_host |
str
|
The webhook target host. |
webhook_ref |
str
|
The webhook endpoint identifier. |
signing_key_kid |
str
|
The key ID used to sign the webhook attempt. |
raw_token_ttl_seconds |
int
|
The raw-message token lifetime the webhook attempt used. |
DecodeError ¶
Bases: NotificationError
A notification body that does not match the SDK's payload model.
This normally means the SDK is older than the server that produced the event. The event is terminated rather than redelivered, because redelivery can never succeed. When an error handler is configured, it receives the error and consumption continues; otherwise consumption stops and this error is raised.
Initialize the error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject
|
str
|
The notification subject the undecodable body was published to. |
required |
message
|
str
|
A short description of the decode failure. |
''
|
Source code in src/mxraven/errors.py
InboundEmail
dataclass
¶
InboundEmail(
event_type: str = "",
task_id: str = "",
tenant_id: str = "",
listener_id: str = "",
message_ref: str = "",
attempt: int = 0,
accepted_at_utc: int = 0,
occurred_at_utc: int = 0,
routing_decision: RoutingDecision | None = None,
verdicts: Verdicts | None = None,
envelope: Envelope = Envelope(),
message: MessageSummary = MessageSummary(),
headers: tuple[HeaderField, ...] = (),
raw_email: RawEmail = RawEmail(),
)
The DELIVER_WEBHOOK payload: a complete inbound message.
NotificationClient ¶
NotificationClient(
*,
url: str,
username: str,
secret: str,
listener: str,
consumer: str | None = None,
connect_timeout: float = DEFAULT_CONNECT_TIMEOUT,
tls: SSLContext | None = None,
on_error: ErrorHandler | None = None,
)
Consumes one listener's client notification events asynchronously.
Notifications are off by default; enable them for the listener with the mxRaven administrative API first, which creates the durable consumer this client reads from. The connection authenticates with the listener's ordinary API key and is granted access only to that listener's consumer.
A client is safe for concurrent use. A consume call and an events
iterator share the durable consumer with any other client using the same
API key: events are distributed between them, not duplicated to each.
Use :meth:connect to build a connected client; do not construct one
directly. A client is an async context manager.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The client notification tier WebSocket URL, for example
|
required |
username
|
str
|
The listener API key prefix, for example
|
required |
secret
|
str
|
The listener API key secret. Treat it as a secret and do not log it. |
required |
listener
|
str
|
The listener whose events are consumed. It must not contain
|
required |
consumer
|
str | None
|
Overrides the durable consumer name. Defaults to
|
None
|
connect_timeout
|
float
|
The connection deadline in seconds. Defaults to
|
DEFAULT_CONNECT_TIMEOUT
|
tls
|
SSLContext | None
|
An optional TLS context for |
None
|
on_error
|
ErrorHandler | None
|
Receives non-fatal connection and decode errors. When omitted,
an undecodable event stops consumption with a |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
An option is missing or invalid. |
Validate the options. The client is not connected until connect.
Source code in src/mxraven/aio/notify/client.py
__aenter__
async
¶
__aexit__
async
¶
close
async
¶
Release the client's connection.
Active consume calls and iterators stop, and events they did not
acknowledge are redelivered later. Safe to call more than once.
Source code in src/mxraven/aio/notify/client.py
connect
async
classmethod
¶
connect(
*,
url: str,
username: str,
secret: str,
listener: str,
consumer: str | None = None,
connect_timeout: float = DEFAULT_CONNECT_TIMEOUT,
tls: SSLContext | None = None,
on_error: ErrorHandler | None = None,
) -> NotificationClient
Connect to the notification tier and return a client.
The initial connection fails here rather than on the first event. After a successful connection the transport reconnects indefinitely when the tier is unreachable, and the consumer resumes from its acknowledgement floor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The notification tier WebSocket URL ( |
required |
username
|
str
|
The listener API key prefix. |
required |
secret
|
str
|
The listener API key secret. |
required |
listener
|
str
|
The listener whose events are consumed. |
required |
consumer
|
str | None
|
An optional durable consumer name override. |
None
|
connect_timeout
|
float
|
The connection deadline in seconds. |
DEFAULT_CONNECT_TIMEOUT
|
tls
|
SSLContext | None
|
An optional TLS context for |
None
|
on_error
|
ErrorHandler | None
|
An optional non-fatal error handler. |
None
|
Returns:
| Type | Description |
|---|---|
NotificationClient
|
A connected client. |
Raises:
| Type | Description |
|---|---|
ValueError
|
An option is missing or invalid. |
NotificationError
|
The connection failed, the API key was rejected,
or the optional |
Source code in src/mxraven/aio/notify/client.py
consume
async
¶
Consume events until stopped, closed, or the handler stops.
A handler that returns None acknowledges the event. A handler that
raises leaves it unacknowledged so the server redelivers it after a
delay that doubles with each attempt up to 30 seconds. A handler that
returns STOP_CONSUMING acknowledges the event and stops. An
undecodable event is terminated so it cannot poison the stream, and
either stops consumption with a DecodeError or is reported to
on_error and skipped.
Cancellation is a normal stop. A permanently lost consumer or
connection raises NotificationError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
NotificationHandler
|
Processes one event at a time. It may be a coroutine function or a plain callable. |
required |
Raises:
| Type | Description |
|---|---|
NotificationError
|
The client is closed, or consumption failed. |
DecodeError
|
An event could not be decoded and no error handler is configured. |
Source code in src/mxraven/aio/notify/client.py
events ¶
events() -> NotificationIterator
Return an async iterator for manual acknowledgement.
The caller acknowledges each event with
:meth:~NotificationDelivery.ack, nak, or term. Stopping iteration
leaves unacknowledged events for redelivery after the consumer's
acknowledgement timeout.
Source code in src/mxraven/aio/notify/client.py
NotificationDelivery ¶
NotificationDelivery(
client: NotificationClient,
message: Message,
event: NotificationEvent,
)
One event yielded by a :class:NotificationIterator.
Exactly one of ack, nak, or term must be called for the event; calling
more than one raises NotificationError. An event that is never settled is
redelivered after the consumer's acknowledgement timeout.
Wrap a decoded event for manual settlement.
Source code in src/mxraven/aio/notify/client.py
ack
async
¶
Acknowledge the event so it is not redelivered.
Raises:
| Type | Description |
|---|---|
NotificationError
|
The event was already settled. |
Source code in src/mxraven/aio/notify/client.py
nak
async
¶
Negatively acknowledge the event so the server redelivers it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
delay
|
float | None
|
An optional redelivery delay in seconds. When omitted or zero, the server redelivers immediately. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
NotificationError
|
The event was already settled. |
Source code in src/mxraven/aio/notify/client.py
term
async
¶
Tell the server never to redeliver the event.
Raises:
| Type | Description |
|---|---|
NotificationError
|
The event was already settled. |
Source code in src/mxraven/aio/notify/client.py
NotificationError ¶
Bases: MxRavenError
A failure connecting to or consuming the client notification stream.
The underlying transport or JetStream failure is preserved as
:attr:__cause__. Most connection failures are transient and are retried
automatically; this error is only raised when an operation cannot continue.
NotificationEvent
dataclass
¶
NotificationEvent(
kind: NotificationKind,
accepted: InboundEmail | None = None,
attempt: AttemptEvent | None = None,
)
A decoded client notification.
Exactly one of :attr:accepted or :attr:attempt is set, matching
:attr:kind.
NotificationIterator ¶
NotificationIterator(client: NotificationClient)
An async iterator over a listener's notification events.
Use async for and break when finished; the iterator stops pulling
and leaves unacknowledged events for redelivery. Iteration also stops when
the client's connection closes.
Build an iterator over client.
Source code in src/mxraven/aio/notify/client.py
__aiter__ ¶
__anext__
async
¶
__anext__() -> NotificationDelivery
Yield the next delivery, or stop iteration.
Source code in src/mxraven/aio/notify/client.py
aclose
async
¶
Stop iterating. Safe to call more than once.
NotificationKind ¶
Bases: StrEnum
The kind of notification a :class:NotificationEvent carries.
StopConsuming ¶
The type of the :data:STOP_CONSUMING sentinel.
Do not construct it directly; return the exported :data:STOP_CONSUMING
singleton instead.
decode_notification ¶
decode_notification(
subject: str, body: bytes
) -> NotificationEvent
Decode one notification body.
The event kind is the final token of subject. When the subject is
unavailable or unrecognized, the body shape is probed instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subject
|
str
|
The notification subject the body was published to. |
required |
body
|
bytes
|
The raw JSON body bytes. |
required |
Returns:
| Type | Description |
|---|---|
NotificationEvent
|
The decoded notification. |
Raises:
| Type | Description |
|---|---|
DecodeError
|
The body is not valid JSON, has a field of the wrong type, or cannot be identified. |
Source code in src/mxraven/notify/event.py
Asynchronous MIME parsing¶
mime ¶
Asynchronous inbound email parsing for mxRaven deliveries.
The asyncio counterpart of :mod:mxraven.mime. :func:parse_email is pure and
shared with the synchronous surface; :func:fetch_and_parse_raw_email
downloads asynchronously.
MimeError ¶
Bases: MxRavenError
A MIME message cannot be parsed within the parser's limits.
Malformed message content never raises; the parser returns the best interpretation it can. Only structurally unbounded input raises, so an untrusted message cannot exhaust the stack or memory.
ParsedAddress
dataclass
¶
One mailbox from a parsed address header.
Attributes:
| Name | Type | Description |
|---|---|---|
address |
str
|
The bare |
name |
str
|
The display name, with RFC 2047 encoded words decoded, when one was present. |
ParsedAttachment
dataclass
¶
ParsedAttachment(
content_type: str,
content: bytes,
filename: str = "",
content_id: str = "",
inline: bool = False,
)
One non-body MIME part.
Attributes:
| Name | Type | Description |
|---|---|---|
content_type |
str
|
The media type, for example |
content |
bytes
|
The decoded attachment bytes. It is a fresh copy that the caller owns and may mutate. |
filename |
str
|
The decoded filename, when one was present. |
content_id |
str
|
The |
inline |
bool
|
Whether the part is marked |
ParsedEmail
dataclass
¶
ParsedEmail(
headers: tuple[ParsedHeader, ...] = (),
subject: str = "",
from_: tuple[ParsedAddress, ...] = (),
to: tuple[ParsedAddress, ...] = (),
cc: tuple[ParsedAddress, ...] = (),
bcc: tuple[ParsedAddress, ...] = (),
reply_to: tuple[ParsedAddress, ...] = (),
message_id: str = "",
date: datetime | None = None,
text: str = "",
html: str = "",
attachments: tuple[ParsedAttachment, ...] = (),
)
A parsed inbound RFC 5322 / MIME message.
An empty value means the corresponding header or body was absent.
Attributes:
| Name | Type | Description |
|---|---|---|
headers |
tuple[ParsedHeader, ...]
|
Every header field, in order, with continuation lines unfolded. Values are not decoded. |
subject |
str
|
The decoded |
from_ |
tuple[ParsedAddress, ...]
|
The |
to |
tuple[ParsedAddress, ...]
|
The |
cc |
tuple[ParsedAddress, ...]
|
The |
bcc |
tuple[ParsedAddress, ...]
|
The |
reply_to |
tuple[ParsedAddress, ...]
|
The |
message_id |
str
|
The |
date |
datetime | None
|
The parsed |
text |
str
|
The decoded first |
html |
str
|
The decoded first |
attachments |
tuple[ParsedAttachment, ...]
|
Every non-body part, with transfer encoding and filename
decoding applied. |
ParsedHeader
dataclass
¶
One header field from a parsed message.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The field name as it appeared in the message. |
value |
str
|
The unfolded field value. Encoded words are not decoded; use
:attr: |
fetch_and_parse_raw_email
async
¶
fetch_and_parse_raw_email(
raw: RawEmail,
*,
timeout: float = _DEFAULT_FETCH_TIMEOUT,
transport: AsyncTransport | None = None,
) -> ParsedEmail
Download and parse an inbound-email payload's raw message.
The asynchronous counterpart of
:func:mxraven.mime.fetch_and_parse_raw_email; the download verifies the
declared size and SHA-256 digest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
RawEmail
|
The |
required |
timeout
|
float
|
The request timeout in seconds. |
_DEFAULT_FETCH_TIMEOUT
|
transport
|
AsyncTransport | None
|
An optional asynchronous transport to use instead of the default. |
None
|
Returns:
| Type | Description |
|---|---|
ParsedEmail
|
The parsed message. Attachment content is a fresh copy. |
Raises:
| Type | Description |
|---|---|
WebhookError
|
The download failed or the size or digest did not match. |
MimeNestingError
|
The message nests deeper than the parser allows. |
MimePartsError
|
The message has more MIME entities than the parser allows. |
Source code in src/mxraven/aio/mime.py
parse_email ¶
parse_email(
data: bytes | bytearray | memoryview,
) -> ParsedEmail
Parse an inbound RFC 5322 / MIME message.
The parser never validates the message against the grammar and never raises
on malformed content; it extracts what it can. Multipart mixed,
alternative, and related structures are traversed, base64 and
quoted-printable transfer encodings are decoded, RFC 2047 encoded words
in structured headers are decoded, and RFC 2231 parameters are reassembled.
message/rfc822 parts are surfaced as attachments rather than parsed
recursively.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
bytes | bytearray | memoryview
|
The raw message bytes. |
required |
Returns:
| Type | Description |
|---|---|
ParsedEmail
|
The parsed message. Attachment content is a fresh copy. |
Raises:
| Type | Description |
|---|---|
MimeNestingError
|
The message nests deeper than the parser allows. |
MimePartsError
|
The message has more MIME entities than the parser allows. |