• v0.34.0 86d7fe738b

    joeyr released this 2026-08-23 10:36:15 -05:00 | 100 commits to main since this release

    [0.34.0] — 2026-08-23

    G4: sending on the Gmail tenant. send_message, reply_message and
    forward_message, behind MAIL_SEND_ENABLED, which is off by default and
    registers nothing when it is. Live pass in docs/operations.md — 37 checks, and
    it found a defect in a G2 tool rather than in its own.

    Added

    • The three send tools, and the shortest send path in this project. Probe 9
      found Gmail writes [Gmail]/Sent Mail itself on submission, so this tenant
      appends nothing — no Sent folder to resolve, no APPENDUID to parse, no
      second verdict that can fail on its own. The deletions are the feature: the
      obvious port from icloudmail would leave a second copy in Sent every time
      this server mails anybody
      , in the folder the user opens to find out what was
      sent for them. The live pass counts the copies, and one is the passing answer.

    • One verdict, and a sentence saying whose copy it is.
      SendOutcome.saved_to_sent is None on this tenant and to_dict emits
      sent_copy instead of the boolean. Both values would be lies — false says
      the user's record is missing when it is not, true claims an operation this
      server never performed — and a JSON null is worse than either, since
      anything testing it for truth reads it as false.

    • Sending is NOT gated on MAIL_WRITABLE_FOLDERS here, because no folder is
      written. A deployment with an empty write allowlist can still mail a stranger,
      and that is stated in the instructions rather than left to be discovered. The
      one thing it costs is that a reply cannot flag its original \Answered, so
      marked_answered: false is the ORDINARY outcome on that deployment — the tool
      description says not to read it as a failure, and the live pass asserts both
      halves.

    • tests/test_mail_smtp.py, which nothing had ever driven. Every send test
      on either tenant replaces submit with a fake, so the lines deciding which
      account authenticates and whether the credential crosses before STARTTLS were
      reachable only from production. Found by the break-the-guard pass: swapping
      mail_address back to icloud_mail_address broke Gmail's send entirely and
      every test still passed.

    Fixed

    • A STORE that changes nothing is no longer reported as a vanished message —
      on BOTH tenants.
      add_flags of a flag the message already carries answers
      {}, with no untagged FETCH, and that was read as "the message is gone". So
      update_message(read=true) on an already-read message told the user their
      mail had probably been deleted from another device: a failure reported
      about a success, on the most ordinary call the tool has. Shipped in 0.25.0 on
      icloudmail and 0.31.0 on gmail.

      A vanished UID answers {} too, so the two states are indistinguishable from
      the STORE alone. The fix asks: on an empty answer the client FETCHes the flags
      back, and only a FETCH that also returns nothing means gone. Not a
      provider table
      — the two servers disagree even here, since removing a flag
      that was never set answers {} on Gmail and answers with the flag list on
      iCloud.

      Neither suite could fail this way, because a fake that answers a STORE answers
      it every time. The Gmail fake now carries silent_store and vanished as
      separate switches, and the two tests using them are a pair: a no-op must
      succeed, a vanished message must still refuse.

    Changed

    • icloudmail/smtp.py is mail/smtp.py, shared by both mail tenants and
      authenticating with Config.mail_address. Same move mail/errors.py made in
      0.29.0 and for the same reason: the alternative was gmail/ importing
      icloudmail/. Submission came out identical on both accounts — what is
      genuinely provider-specific is what happens to the copy afterwards, and that
      stays in each tenant's client.

    • The Gmail instructions no longer say a server that can send is READ-ONLY, and
      no longer tell the model it cannot send once the gate is on. Built from the
      settings the tools are registered on, the same way the unsubscribe and
      label-admin clauses already were — this is the third gate to reach that
      branch, which is why the refusal list is now assembled rather than written out.

    • Preflight gains a Gmail send line that verifies nothing, deliberately.
      The mail tenant's checks whether Sent and Drafts exist and are writable; this
      tenant writes no folder to send, and the credential was already proved by the
      IMAP login — the same app password serves both hosts. It is a grant line, and
      it does not dial SMTP.

    Downloads