Skip to content

feat(cli): credential verification mode for preflight #259

@randomparity

Description

@randomparity

Summary

Add a way to test that the stored credentials actually authenticate against the configured IMAP and SMTP servers, without standing up the MCP transport. Either an opt-in flag on --dry-run (--with-auth) or a dedicated subcommand (verify-credentials). Same code paths as the production server up through LOGIN / SMTP AUTH; reports per-protocol success/failure to stdout and exits.

Motivation

--dry-run today validates: config parse, audit/credential-store wiring, TCP reach, TLS handshake (with pin), and CAPABILITY preflight. It deliberately stops before LOGIN — see crates/rimap-imap/src/preflight.rs:

//! PreflightInfo.tls_fingerprint). Does NOT perform LOGIN and does NOT

SMTP is not touched at all. That's a reasonable boundary for preflight, but it leaves a real onboarding hole on both protocols: the first time the credential is exercised is when the MCP client launches the server (IMAP) or when the agent first asks for send_email (SMTP). If the password is wrong (typo at login prompt, wrong username form, server requires a SASL mechanism the client isn't offering, separate keychain entry needed for SMTP on Gmail-style split-host providers), the failure surfaces as ERR_AUTH inside the MCP client's stderr — the worst place to debug it from.

Users currently work around this with raw IMAP via openssl s_client + manual LOGIN, and SMTP via swaks --quit-after AUTH (both documented in the quickstarts). That works but it:

  • Bypasses the keyring (re-enters the password by hand), so it doesn't actually validate the same credential the server will use.
  • Doesn't exercise SASL negotiation, OAuth flows, or any auth path beyond plain LOGIN / AUTH LOGIN.
  • Requires the user to construct the right tool invocation per server (implicit TLS vs STARTTLS, separate recipes for IMAP vs SMTP).
  • For Gmail-style split-host setups, doesn't help confirm that the SMTP-specific keychain entry (separate --host from the IMAP one) is actually present and correct.

A built-in verification step would close the gap and exercise exactly the production code path through to LOGIN (IMAP) and AUTH (SMTP) success/failure, using the keychain-resolved credential the production server will use.

Sketch

rusty-imap-mcp --dry-run --with-auth
# or
rusty-imap-mcp verify-credentials

Same preflight output as today, plus a per-protocol trailing block:

Authentication:
  IMAP:
    [ok ] keyring lookup matched (service=rusty-imap-mcp, account=default/drc@linux.ibm.com@imap.linux.ibm.com)
    [ok ] LOGIN succeeded as drc@linux.ibm.com (mechanism=LOGIN)
  SMTP:
    [ok ] keyring lookup matched (service=rusty-imap-mcp, account=default/drc@linux.ibm.com@smtp.example.com)
    [ok ] AUTH succeeded as drc@linux.ibm.com (mechanism=LOGIN, STARTTLS)

Or on failure (typical Gmail split-host SMTP case where the user only stored the IMAP keyring entry):

Authentication:
  IMAP:
    [ok ] LOGIN succeeded as you@gmail.com (mechanism=LOGIN)
  SMTP:
    [err] keyring lookup failed: no entry for host smtp.gmail.com
    Suggested fixes:
      - Run `rusty-imap-mcp login --host smtp.gmail.com --username you@gmail.com`
      - Reuse the same App Password — Gmail accepts it for both IMAP and SMTP

SMTP is skipped (not an error) if [smtp] is not configured.

In scope

  • IMAP LOGIN (PLAIN, LOGIN, future SASL mechanisms as they're added).
  • SMTP AUTH using the existing lettre client path. Reads the SMTP-specific keychain key (<account>/<smtp_username>@<smtp_host>), which on split-host providers (Gmail) is distinct from the IMAP entry.
  • Both transport modes per protocol: implicit TLS and STARTTLS.
  • TLS fingerprint pinning honored on both: a pinned IMAP cert that mismatches still fails; SMTP cert validation uses the configured trust path.
  • Audit emission for the verification attempts, marked so audit consumers can distinguish them from real server-mode auth records.

Open design questions

  • Flag vs subcommand. --dry-run --with-auth keeps everything under one umbrella but adds combinatorial flag complexity. A separate verify-credentials subcommand is more discoverable.
  • Multi-account behavior. Iterate every [[accounts]] and report per-account, or require --account <id>? Iterating risks rate-limit hits against shared providers (Gmail throttles repeated AUTH attempts).
  • Audit source kind. Should the emitted Auth records carry a source = "verify" tag (or equivalent) so audit consumers can filter, or should they be indistinguishable from real server auth? Indistinguishable preserves audit fidelity; tagged keeps verification noise out of usage analytics.
  • Failure-mode coverage in error suggestions. Where to draw the line between "diagnostic suggestion" and "support article"? E.g., for SMTP 535 on Gmail, suggest checking App Password / 2FA / "Less secure apps"?

Out of scope

  • Token-exchange flows (OAuth refresh, etc.) — those go elsewhere.
  • Folder-listing or message-fetch validation — separate concern. Could be a later verify-permissions mode.
  • Actually sending a test email — the --quit-after AUTH equivalent is what's wanted; transacting a message has different blast radius.

Related docs

The quickstart workarounds using openssl s_client (IMAP) and swaks (SMTP) landed alongside this issue:

  • docs/quickstart-gmail.md → "Optional: verify the credential authenticates" (IMAP) and the SMTP verify step inside "Optional: enable sending"
  • docs/quickstart-proton-bridge.md → "Optional: verify the credential authenticates" (IMAP) and the SMTP verify subsection inside "Optional: enable sending"
  • docs/troubleshooting.md → credential verification section, cross-linking both quickstart subsections

Once this feature ships, all four quickstart subsections (and the troubleshooting cross-link) should be retitled to point at the new flag/subcommand, with the raw-IMAP and swaks recipes demoted to "manual alternative" footnotes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions