Sovite.DSN (sovite v0.2.0)

Copy Markdown View Source

Builds delivery status notifications (RFC 3464), the messages that tell a sender their mail was not delivered (or not yet).

A notification is a multipart/report (RFC 6522) with three parts: a human-readable explanation, a message/delivery-status report for programs, and the headers of the original message (text/rfc822-headers; the body is not returned).

DSN.build(%{
  kind: :failure,
  reporting_mta: "mx.example.com",
  from: "MAILER-DAEMON@mx.example.com",
  to: "alice@example.com",
  recipients: [
    %{recipient: "bob@example.net", status: "5.1.1", remote_mta: "mx.example.net",
      diagnostic: "550 5.1.1 User unknown"}
  ],
  headers: "From: alice@example.com\r\nSubject: hi\r\n\r\n"
})

The result is a complete message with CRLF line endings. Send it with the null reverse-path (MAIL FROM:<>), as RFC 5321 §6.1 requires, so a failing notification never causes another one.

Text from remote servers (diagnostics) is untrusted: control and non-ASCII characters are replaced with ?, and long values are cut, so it cannot inject header fields or MIME boundaries.

Summary

Types

One recipient in the report.

The report.

Functions

Builds the notification message. Returns the message and its body type: :"8bitmime" if the original headers contain 8-bit bytes, otherwise :"7bit".

Types

recipient()

@type recipient() :: %{
  :recipient => String.t(),
  :status => String.t(),
  optional(:remote_mta) => String.t() | nil,
  optional(:diagnostic) => String.t() | nil,
  optional(:reason) => String.t() | nil,
  optional(:last_attempt) => DateTime.t() | nil
}

One recipient in the report.

  • :recipient - the address. Required.
  • :status - enhanced status code, such as "5.1.1". Required.
  • :remote_mta - host name of the server that gave :diagnostic.
  • :diagnostic - the remote server's SMTP reply.
  • :reason - a local explanation, used when there is no SMTP reply (for example "Host or domain name not found").
  • :last_attempt - time of the last delivery attempt.

report()

@type report() :: %{
  :kind => :failure | :delay,
  :reporting_mta => String.t(),
  :from => String.t(),
  :to => String.t(),
  :recipients => [recipient(), ...],
  optional(:headers) => binary() | nil,
  optional(:queue_id) => String.t() | nil,
  optional(:arrival_date) => DateTime.t() | nil,
  optional(:will_retry_until) => DateTime.t() | nil,
  optional(:date) => DateTime.t(),
  optional(:message_id) => String.t(),
  optional(:boundary) => String.t()
}

The report.

  • :kind - :failure (the listed recipients will never get the message) or :delay (still trying). Required.
  • :reporting_mta - this server's host name. Required.
  • :from - address in the From: field, usually MAILER-DAEMON@<host>. Required.
  • :to - the original sender. Required.
  • :recipients - at least one. Required.
  • :headers - the original message's header section.
  • :queue_id - the original message's queue ID.
  • :arrival_date - when the original message was received.
  • :will_retry_until - for :delay, when delivery will be given up.
  • :date, :message_id, :boundary - default to now, a new ID, and a random boundary.

Functions

build(report)

@spec build(report()) :: {binary(), :"7bit" | :"8bitmime"}

Builds the notification message. Returns the message and its body type: :"8bitmime" if the original headers contain 8-bit bytes, otherwise :"7bit".