Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

mail.stream

import mail

mail exposes this as mail.stream, so import mail is enough and the names are called as mail.stream.*. import mail.stream reaches the same definitions directly.

A line-oriented connection, which is what all three mail protocols are underneath.

SMTP, IMAP and POP3 all speak in lines ending with a carriage return and a newline, mixed with runs of raw bytes whose length something else announced. LineStream puts that over any of the transports net provides, and lets one be swapped for another mid-conversation, which is what STARTTLS is.

Constants

TLS_MODES

mail.stream.TLS_MODES: list = [...]

How TLS is treated on a connection that did not start encrypted: require refuses to go on without it, prefer takes it when the server offers it, and disable does not ask.

MAX_LINE

mail.stream.MAX_LINE = 65536

Functions

connect()

mail.stream.connect(host: string, port: number, options: dict) -> LineStream

Opens a connection to a host, in TLS or in the clear.

Parameters

  • host (string)
  • port (number)
  • options (dict) — timeout in milliseconds, tls to handshake immediately, tls_config and server_name.

Returns LineStream

Raises Error if the connection or the handshake fails.

start_tls()

mail.stream.start_tls(stream, server_name: string, config) -> LineStream

Wraps a connection that started in the clear in TLS, which is what every STARTTLS comes down to.

Parameters

  • stream (LineStream)
  • server_name (string) — The name to check the certificate against.
  • config (?TlsConfig)

Returns LineStream — the same one, now encrypted.

Raises Error if the handshake fails.

endpoint()

mail.stream.endpoint(url: string, schemes: dict) -> dict

Reads a connection string into the pieces needed to open it.

Accepts scheme://[user[:password]@]host[:port], and a bare host[:port] for which the first scheme in schemes is assumed.

import mail.stream

var where = stream.endpoint('smtps://ann:secret@mail.example.com', {
  smtp: { port: 587, tls: false },
  smtps: { port: 465, tls: true },
})

echo '${where.host} ${where.port} ${where.tls} ${where.username}'
mail.example.com 465 true ann

Parameters

  • url (string)
  • schemes (dict) — Each scheme’s default port and whether it means TLS from the first byte.

Returns dict — of scheme, host, port, tls, username and password, the last two nil when the string carries none.

Raises ProtocolError if the scheme is not one of those given, or there is no host.

Classes

LineStream

class mail.stream.LineStream

A connection that reads and writes lines.

  • printable — has a @to_string(), so echo and print() show something useful

Constructor

mail.stream.LineStream(transport, secure: ?bool)

Parameters

  • transport (any) — Anything with read(), write_all(), flush() and close(). Every socket in net qualifies.
  • secure (?bool) — Whether the transport is already encrypted. Worked out from the transport when not given.

LineStream.transport()

mail.stream.LineStream.transport() -> any

The transport underneath, for the calls this does not wrap.

Returns any

LineStream.is_secure()

mail.stream.LineStream.is_secure() -> bool

Whether what goes over this connection is encrypted.

Returns bool

LineStream.upgrade()

mail.stream.LineStream.upgrade(transport, secure: ?bool) -> LineStream

Replaces the transport with another, which is what a protocol does once it has negotiated TLS over a connection that started in the clear.

Parameters

  • transport (any)
  • secure (?bool)

Returns LineStream — itself.

Raises ProtocolError if anything is still buffered, since bytes read before the handshake cannot belong after it.

LineStream.read_line()

mail.stream.LineStream.read_line(limit: ?number) -> string

Reads one line.

The line break comes off, and a lone newline is accepted in place of the pair, because servers send one often enough that refusing would be refusing real mail.

Parameters

  • limit (?number) — The longest line to accept. MAX_LINE when not given.

Returns string

Raises ConnectionClosed if the connection ends mid-line.

Raises ProtocolError if the line runs past the limit.

LineStream.read_bytes()

mail.stream.LineStream.read_bytes(count: number) -> bytes

Reads exactly count bytes, whatever they contain.

Parameters

  • count (number)

Returns bytes

Raises ConnectionClosed if the connection ends first.

LineStream.has_buffered()

mail.stream.LineStream.has_buffered() -> bool

Whether anything is waiting in the buffer, without touching the transport.

Returns bool

LineStream.write()

mail.stream.LineStream.write(data) -> LineStream

Writes bytes as they are.

Parameters

  • data (string|bytes)

Returns LineStream — itself.

LineStream.write_line()

mail.stream.LineStream.write_line(text: string) -> LineStream

Writes one line and the break that ends it.

Parameters

  • text (string)

Returns LineStream — itself.

LineStream.flush()

mail.stream.LineStream.flush() -> LineStream

Pushes anything buffered by the transport out to the peer.

Returns LineStream — itself.

LineStream.close()

mail.stream.LineStream.close() -> LineStream

Closes the connection. Closing one that is already closed does nothing.

Returns LineStream — itself.

LineStream.is_closed()

mail.stream.LineStream.is_closed() -> bool

Whether close() has been called.

Returns bool

LineStream.to_string()

mail.stream.LineStream.to_string()

2026, Richard Ore and Zuri contributors