Skip to content

v3.0.0

Latest

Choose a tag to compare

@robert-virkus robert-virkus released this 27 Oct 06:59

v3.0.0

⛓️‍💥 Breaking changes

  • STARTTLS is now enforced. With SocketType.starttls or plain, IMAP and SMTP used to send credentials in clear text if the server did not advertise STARTTLS, so a man-in-the-middle only had to strip that capability. The client now refuses to authenticate unless plainNoStartTls was explicitly chosen. Capabilities from before the TLS upgrade are discarded, and the certificate is checked against the configured host name.
  • Streams replace event_bus. Events are now delivered through Dart streams instead of the event_bus package.

🔒 Security fixes

A security review in September 2026 (#296) fixed several serious problems. Upgrading is strongly recommended.

  • STARTTLS is now enforced. With SocketType.starttls or plain, IMAP and SMTP used to send credentials in clear text if the server did not advertise STARTTLS, so a man-in-the-middle only had to strip that capability. The client now refuses to authenticate unless plainNoStartTls was explicitly chosen. Capabilities from before the TLS upgrade are discarded, and the certificate is checked against the configured host name.
  • SMTP: DATA payload could be cut short. Two consecutive lines consisting of a single period ended the DATA phase early, and the rest of the message was run as SMTP commands on the authenticated session. Every line starting with a period is now dot-stuffed correctly. Lines such as .NET also no longer lose their leading period.
  • Header and SMTP envelope injection is blocked. CR/LF is stripped from header values, so a subject, display name, file name or custom header can no longer start another header. Email addresses with control characters, whitespace, angle brackets or list separators are refused. The address parser handles escaped quotes correctly and no longer crashes on emoji.
  • IMAP command injection is blocked. User names, passwords, mailbox names and search terms are now quoted and escaped according to RFC 3501. Credentials containing ", \, non-ASCII characters or line breaks previously produced malformed or injectable commands.
  • Bcc recipients could leak. A long Bcc header is folded over several lines, and only its first line was removed, so every To/Cc recipient received the rest of the Bcc list.
    • The DATA command was fixed by David Hobley (#294).
    • The BDAT command, which is preferred whenever a server announces CHUNKING, had the same problem and is fixed as well. The Bcc header is now removed only from the header section, so a body line starting with Bcc: is left alone.
  • Credentials no longer appear in logs. toString() of MailAccount, MailServerConfig and OauthToken now hides passwords and tokens. toJson() is unchanged.
  • Safer MIME parsing. Multipart delimiters are only accepted at the start of a line, so a text line cannot smuggle in a part that other clients and scanners do not see. Nesting is limited to 50 levels.
  • Safer auto-discovery. Fetching configurations has a timeout and a 1 MiB size limit and no longer follows https-to-http redirects. Results obtained over plain http are flagged through ClientConfig.isFromInsecureSource.
  • Secure random IDs. Message-IDs and multipart boundaries use Random.secure() where available.
  • Exact plus-alias matching. ad+min@example.com is no longer treated as admin@example.com.

✨ New features

  • Streams replace event_bus. Events are now delivered through Dart streams instead of the event_bus package.
  • IMAP NAMESPACE extension (RFC 2342). New ImapClient.namespace() returns the personal, other users' and shared namespaces. Contributed by Dmitry Protasov (#295).
  • APPEND with an explicit INTERNALDATE and from raw bytes. Copied messages keep their original date, and messages fetched with BODY[] can be appended without corruption through the new appendMessageBytes. Contributed by David Hobley (#292).
  • Mutual TLS (mTLS). ImapClient, SmtpClient and ClientBase accept an optional SecurityContext for client certificate authentication. This change also fixes a missing onBadCertificate when upgrading to SSL. Contributed by icd360sevofficial (#273).
  • idleStart(waitForContinuation: true). The returned future waits until the server confirms IDLE (RFC 2177), which is useful when handing the connection off to another process. It fails if the connection is lost first. Contributed by BrLumen (#274).
  • Gmail extensions in FETCH. FetchParser now understands X-GM-THRID, X-GM-MSGID and X-GM-LABELS. Contributed by dev10m-adv (#275).
  • POP3 with OAuth. PopClient.authenticateWithOAuth2() uses AUTH XOAUTH2. PopClient also accepts a SecurityContext.

🐛 Bug fixes

Messages could be lost, deleted or delivered incorrectly

  • Wrong message deleted. deleteMessages() and the move/trash fallbacks used a plain STORE for UID sequences, so a different message could be flagged and permanently removed. They now use UID STORE and, where available, UID EXPUNGE limited to the affected messages. Where supported, UNSELECT replaces CLOSE, so messages deleted with canUndo are no longer expunged as a side effect.
  • SMTP ignored rejections. Negative replies to MAIL FROM, RCPT TO, DATA and BDAT were ignored. A message could be partially delivered while the caller saw success. The transaction is now aborted with RSET, the connection stays usable, and the call fails with the rejecting reply.
  • Invalid envelope addresses are reported as SmtpException, so MailClient.sendMessage fails with a MailException instead of an uncaught ArgumentError.
  • POP3. Polling compared message counts the wrong way round and never found new messages. Retrieved messages had no sequence id and could not be deleted or re-fetched, and undelete and store() were broken. A failed retrieve no longer makes the next poll skip the remaining new messages.
  • Quoted-printable attachments were returned undecoded (=25PDF=2D1=2E7 instead of %PDF-1.7). They are now decoded to their bytes. Contributed by David Hobley (#287).
  • Empty body for servers using bare LF line endings. A fetched BODY[] literal is now normalised on the raw bytes, without corrupting non-UTF-8 charsets. Contributed by David Hobley (#293).

Connections hanging or breaking

  • Reconnect deadlock. MailClient.reconnect() and resume() could hang forever and disconnect() could not abort them. The retry interval now grows exponentially, and an aborted reconnect no longer drops events permanently.
  • Operations hanging on connection problems. Connecting, and pending IMAP, SMTP and POP commands, now fail instead of waiting forever when the connection is lost, the server stays silent or a write fails.
    • David Hobley fixed the IMAP case (#288).
    • Failed pending tasks report the plain error message instead of nested "connection lost: …" text.
  • IMAP survives malformed server responses instead of leaving every later command queued forever. Literal sizes are validated and capped at 128 MiB (configurable), and parsing is bounds-checked.
  • SMTP: bare reply codes such as 250<CRLF> no longer throw and hang the pending command. Contributed by BrLumen (#285).
  • IMAP IDLE no longer leaks an unhandled error when it is still queued during a disconnect, which could terminate an isolate with errorsAreFatal. Contributed by BrLumen (#302).
  • Reconnecting clients reset their response parsers, so a half-received reply from the old connection no longer corrupts the first replies of the next one. Manually connected sockets no longer create an unawaited greeting completer.
  • SMTP and POP multi-line replies are framed correctly. A multi-line EHLO reply split across chunks is no longer truncated, and LIST/UIDL on an empty POP3 mailbox now completes.
  • OAuth. Rotated refresh tokens are kept, concurrent refreshes are serialised, and the token is checked for expiry before the SMTP connection is authenticated.

Mailbox names

  • Parentheses. Mailbox names such as INBOX.Financial.Audit(s) work in extended LIST responses and in commands. Contributed by David Hobley (#289).
  • Unquoted mailbox names in STATUS responses, as sent by Dovecot, are parsed. Previously MESSAGES, UNSEEN and UIDNEXT stayed at 0. Contributed by David Hobley (#290).
  • HIGHESTMODSEQ from a STATUS response is recorded. Contributed by David Hobley (#291).
  • No double encoding of the target mailbox path for APPEND, COPY and MOVE, which servers rejected with e.g. NO [TRYCREATE]. Contributed by BrLumen (#298).
  • Encoding unless UTF8=ACCEPT is enabled. selectMailboxByPath() encodes names in modified UTF-7, and UTF-8 is only sent once UTF8=ACCEPT has been enabled (RFC 6855). Gmail otherwise answers BAD Could not parse command. Contributed by BrLumen (#300).

Encoding and parsing

  • Date headers. Half-hour zones west of Greenwich were rendered as -03-30, and UTC as -0000. Two- and three-digit years now follow RFC 5322.
  • Header encoding.
    • Non-ASCII display names are rendered as bare RFC 2047 encoded-words, not inside quoted strings.
    • Encoded words are labelled UTF-8.
    • The Q and B encoders no longer break on astral characters such as emoji.
    • Header folding never splits an encoded word.
    • Folding no longer happens inside long email addresses or directly after an opening angle bracket. Contributed by nogringo (#272).
  • Lenient MIME decoding. Quoted-printable and base64 decoders no longer throw on truncated or invalid input, and undecodable encoded words are kept verbatim. Binary bodies are no longer corrupted by a UTF-8 round trip. Header parameters are split correctly according to RFC 2231.
  • Bare LF line endings are tolerated when parsing MIME text. Contributed by nogringo.
  • Auto-discovery. UsernameType.emailLocalPart now returns the local part instead of the domain, and forceSslConnection also updates the generic preferred servers.
  • DKIM signing. The signer now hashes the whole canonicalized body and signs From, To, Cc, Subject, Date, Message-ID, MIME-Version and Content-Type. Building a message twice no longer duplicates the text part.
  • SMTP AUTH PLAIN/LOGIN credentials are encoded as UTF-8 (RFC 4616), so umlauts and non-Latin-1 characters work.

⚡ Performance

  • Fixed an O(n²) slowdown in MailCodec.wrapText(). Contributed by nogringo (#276).
  • POP3 multi-line reply detection scans only new bytes instead of the whole buffer on every chunk.

🧹 Maintenance

  • Shared the STARTTLS enforcement and the write-failure handling between the IMAP, POP and SMTP clients.
  • Simplified how the IMAP parser finds the next literal marker.
  • Corrected the package name and upgraded dependencies.
  • Resolved eight dart analyze issues (nogringo).
  • Added tests for quoted-printable edge cases, the SMTP and POP write-failure path, and the new IMAP features.
  • Updated the analysis options, example, readme, docs and changelog.

Contributors

Contributor Contributions
Robert Virkus Security review, reliability and codec fixes, event streams, maintenance
David Hobley APPEND with INTERNALDATE and raw bytes, Bcc leak fix, six IMAP, codec and parsing fixes
BrLumen idleStart continuation flag, mailbox path encoding fixes, SMTP bare reply codes, IDLE error leak
nogringo Header folding fixes, bare LF tolerance, wrapText() performance, analyzer cleanup
Dmitry Protasov IMAP NAMESPACE extension
icd360sevofficial mTLS SecurityContext support
dev10m-adv Gmail extension fields in FetchParser