Skip to content

[Sprint 6] Verify Service + Polish#8

Merged
uzyn merged 1 commit into
mainfrom
sprint-6-verify-polish
Apr 9, 2026
Merged

[Sprint 6] Verify Service + Polish#8
uzyn merged 1 commit into
mainfrom
sprint-6-verify-polish

Conversation

@uzyn

@uzyn uzyn commented Apr 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • S6.1 Verify Service: Port Probe -- Standalone Rust HTTP service (services/verify/) using axum. Accepts GET/POST /probe requests with target IP, connects back on port 25, returns JSON {"reachable": true/false}. Self-hostable with deployment docs.
  • S6.2 Verify Service: Email Echo -- MDA-compatible echo handler in the same binary (aimx-verify echo). Parses incoming email, extracts Authentication-Results for DKIM/SPF status, sends auto-reply via sendmail with verification results.
  • S6.3 CLI Polish -- Implemented aimx status (domain, mailbox counts with total/unread, DKIM key presence, OpenSMTPD status), aimx verify (sends test email to verify@aimx.email, polls for reply), enhanced --help text for all commands.
  • S6.4 Documentation -- Comprehensive README with quick start, installation, full CLI usage, config.yaml reference, channel manager examples, trust policy docs, MCP setup, DNS records, VPS provider table. MIT LICENSE file added.

Test counts

  • Main crate: 220 unit tests + 35 integration tests (all pass)
  • Verify service: 17 unit tests (all pass)
  • Total: 272 tests

New files

  • src/status.rs -- status command (12 unit tests)
  • src/verify.rs -- verify command (7 unit tests)
  • services/verify/src/main.rs -- probe HTTP service (7 unit tests)
  • services/verify/src/echo.rs -- email echo handler (10 unit tests)
  • services/verify/README.md -- deployment instructions
  • LICENSE -- MIT license

Test plan

  • cargo test passes (220 unit + 35 integration)
  • cargo clippy -- -D warnings clean
  • cargo fmt -- --check clean
  • Verify service: cargo test passes (17 tests)
  • Verify service: cargo clippy -- -D warnings clean
  • aimx status --help shows usage
  • aimx verify --help shows usage
  • Integration test: aimx status shows domain and mailboxes
  • VPS validation: full aimx setup + aimx status + aimx verify flow

- Add verify service (services/verify/) with port probe HTTP endpoint
  and email echo MDA handler, with 17 unit tests
- Implement `aimx status` showing domain, mailboxes, message counts,
  DKIM key presence, OpenSMTPD status (12 unit tests)
- Implement `aimx verify` for end-to-end email verification
- Enhance CLI with --help descriptions and version flag
- Add comprehensive README with quick start, config reference,
  channel manager docs, trust policy docs, MCP setup
- Add MIT LICENSE file
- Update sprint plan: mark Sprint 6 done

@uzyn uzyn left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sprint 6 Review: Verify Service + Polish

Sprint Goal Assessment

Sprint 6 aimed to complete the product with the hosted verification service, remaining CLI commands, and documentation for open source release. All four stories (S6.1--S6.4) have been implemented. The code is clean, tests pass, clippy is clean, and formatting is correct.

Acceptance Criteria Checklist

S6.1 -- Verify Service: Port Probe

  • Met -- check.aimx.email accepts probe requests with target IP (GET /probe?ip= and POST /probe)
  • Met -- Service connects to target IP on port 25 and reports open/closed (check_port25 with 10s timeout)
  • Met -- Response is JSON {"reachable": true/false, "ip": "..."} (matches spec, extra ip field is additive)
  • Met -- Service source code is in services/verify/
  • Met -- Self-hostable with deployment instructions (README has systemd unit, self-hosting guide, env var config)
  • Met -- 7 unit tests for the probe service

S6.2 -- Verify Service: Email Echo

  • Met -- verify@aimx.email receives email and sends auto-reply via aimx-verify echo subcommand
  • Met -- Reply includes DKIM/SPF verification results from Authentication-Results header
  • Met -- Concurrent handling: parse functions are stateless; the echo is an MDA invocation (one process per email, inherently concurrent)
  • Met -- Source code alongside probe service in services/verify/src/echo.rs

S6.3 -- CLI Polish: status, preflight, verify

  • Met -- aimx status shows domain, mailbox counts (total/unread), DKIM key presence, OpenSMTPD status
  • Met -- aimx preflight runs port 25 + DNS checks (delegates to setup::run_preflight_command)
  • Met -- aimx verify sends test email to verify@aimx.email, polls for reply, reports pass/fail
  • Met -- All commands have clear, formatted output
  • Met -- All commands have --help with descriptive usage text
  • Met -- 12 unit tests for status (formatting, frontmatter parsing, message counting) + 7 unit tests for verify

S6.4 -- Documentation

  • Met -- README.md with project description, quick start, requirements, installation, full usage examples
  • Met -- Compatible VPS providers listed (expanded to include DigitalOcean and Linode)
  • Met -- MCP configuration example for Claude Code
  • Met -- Channel manager configuration examples with template variables table
  • Met -- Trust policy documentation
  • Met -- config.yaml reference with all fields documented
  • Met -- MIT LICENSE file added

Test Coverage

  • Main crate: 220 unit tests + 35 integration tests (all pass)
  • Verify service: 17 unit tests (all pass)
  • Total: 272 tests
  • cargo clippy -- -D warnings: clean (both crates)
  • cargo fmt -- --check: clean (both crates)

Coverage Assessment

Test coverage for the new Sprint 6 code is solid. The status module has 12 unit tests covering formatting, frontmatter extraction, message counting, and edge cases. The verify module has 7 unit tests for file listing and helper functions. Integration tests cover aimx status end-to-end (ingest an email, then run status and check output) and --help for both status and verify.

Potential Issues (Non-blockers)

1. Wrong GitHub URL in README (minor)

README.md line 80 references https://github.com/nicholasgasior/aimx.git but the actual repo is uzyn/aimx. Same issue in services/verify/README.md line 3. This should be corrected before public release.

2. Probe service: No IP validation (suggestion)

The /probe endpoint accepts any string as ip and passes it directly to TcpStream::connect. While the service only connects on port 25 (reducing SSRF risk), a malicious caller could probe internal/private IPs (127.0.0.1, 10.x, 192.168.x, etc.) or hostnames. Consider validating that the target is a public IPv4/IPv6 address before connecting. This is a hardening suggestion, not a blocker for merge, but worth noting for production deployment.

3. Echo service: Multiline Authentication-Results headers (suggestion)

extract_auth_result iterates line by line. In real-world email, Authentication-Results headers are frequently folded across multiple lines (RFC 2822 header folding). If the DKIM/SPF result is on a continuation line, it would be missed. This is acceptable for the initial implementation since the echo service runs on aimx's own infrastructure where the MTA typically produces single-line headers, but it is worth being aware of.

4. Echo reply missing Message-ID and Date headers (suggestion)

The compose_reply function generates the reply email without a Message-ID or Date header. Per RFC 5322, both are required ("SHOULD" for Date, "SHOULD" for Message-ID). In practice, sendmail will likely add them, but it is better to be explicit.

5. Verify command sends from catchall@{domain} (minor)

If the domain has no "catchall" mailbox configured, or if the catchall address format differs, the from address might not match any configured mailbox. This is an edge case that would be caught by the user during aimx verify (the send would fail with a clear error from the send pipeline).

Security Assessment

No significant security issues found. The probe service only connects on port 25, limiting SSRF impact. The echo service reads from stdin and sends via sendmail -- no network listeners. The from field from incoming email is used as the reply To: header, which is standard MDA behavior and not a vulnerability.

Alignment with PRD

All Sprint 6 functional requirements are addressed:

  • FR-38: Hosted probe service at check.aimx.email -- implemented
  • FR-39: Hosted email endpoint at verify@aimx.email -- implemented
  • FR-40: Verify service is open source and self-hostable -- implemented with deployment docs
  • FR-42: aimx preflight -- connected to setup's preflight logic
  • FR-47: aimx status -- fully implemented
  • FR-48: aimx verify -- fully implemented

The README documentation covers all items listed in the PRD's Section 9 (In Scope) from a user-facing perspective. The config.yaml reference is comprehensive. The LICENSE is MIT, satisfying NFR-3.

Summary

This is a clean final sprint. All four stories are implemented with good test coverage (272 tests total, all passing). The code is well-structured, idiomatic Rust, and follows the patterns established in previous sprints. The documentation is thorough and covers all the required areas for an open source release.

The non-blocker suggestions above (wrong GitHub URL, IP validation, multiline headers, missing RFC headers) are all improvement items that can be addressed in a follow-up. None of them prevent the code from functioning correctly.

Verdict: No blockers. Clean implementation.

@uzyn uzyn left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVED (posted as comment due to GitHub self-review restriction)

All Sprint 6 acceptance criteria met. 272 tests passing, clippy clean, fmt clean. See the detailed review comment above for non-blocker suggestions.

Recommended merge commit message:

Add verify service, status/verify CLI commands, and documentation

Implement Sprint 6 (Verify Service + Polish):
- Port probe HTTP service (services/verify/) using axum for inbound port 25 checks
- Email echo MDA handler for DKIM/SPF verification replies
- `aimx status` command showing domain, mailboxes, message counts, DKIM/OpenSMTPD status
- `aimx verify` command for end-to-end email verification via verify@aimx.email
- Enhanced --help text for all CLI commands
- Comprehensive README with quick start, usage, config reference, DNS records
- MIT LICENSE file
- 272 tests (220 unit + 35 integration + 17 verify service)

@uzyn uzyn merged commit f7d452c into main Apr 9, 2026
2 checks passed
@uzyn uzyn deleted the sprint-6-verify-polish branch April 10, 2026 00:32
uzyn added a commit that referenced this pull request Apr 14, 2026
* Make IPv6 outbound opt-in via enable_ipv6 config flag

aimx send now defaults to IPv4-only outbound delivery. Set
enable_ipv6 = true in config.toml to opt into dual-stack (OS
chooses). This matches the SPF record aimx setup generates by
default (ip4: only) and avoids SPF failures at Gmail when the
server has a global IPv6 but DNS isn't set up for it.

- Config: new enable_ipv6: bool field, defaults to false
- send.rs: LettreTransport carries the flag; re-introduces
  resolve_ipv4() helper and gates the connect target in
  try_deliver()
- mcp.rs: pass config.enable_ipv6 when constructing transport
- book/configuration.md: new "IPv6 delivery (advanced)" section
  documenting the required AAAA + ip6: SPF additions
- book/setup.md: note under DNS table that AAAA / ip6: are only
  needed when enable_ipv6 is on
- aimx setup is unchanged — this is a hidden config flag

Verified live: default config connects over IPv4 (AF_INET);
adding enable_ipv6 = true makes it connect over IPv6 (AF_INET6).

* Update PRD for enable_ipv6 opt-in (FR-7, FR-19, resolved decision #8)

* Gate aimx setup IPv6 detection behind enable_ipv6 flag

aimx setup now reads `enable_ipv6` from the existing config (if any)
and only calls `get_server_ipv6()` when the flag is true. With the
flag unset or false, AAAA is not advertised, `ip6:` is not added to
SPF, and neither is verified — matching the IPv4-only default of
`aimx send`. Any existing AAAA record in DNS is left alone.

Re-entrant workflow:
- Fresh install -> no config yet -> IPv4-only setup.
- User edits config.toml and adds `enable_ipv6 = true`.
- User re-runs `sudo aimx setup <domain>` -> setup detects the flag,
  shows AAAA + ip6: SPF in the DNS table, and verifies them.

Verified live on a dual-stack VPS: default config produces an
IPv4-only DNS table; adding `enable_ipv6 = true` produces AAAA +
ip6: SPF in the same output.

- src/setup.rs: read enable_ipv6 when loading existing config,
  gate get_server_ipv6() call in run_setup
- book/configuration.md: updated IPv6-delivery section to describe
  the re-run-setup workflow and the ignore-when-disabled behavior
- book/setup.md: note that AAAA / ip6: are only shown when the flag
  is on
- docs/prd.md: FR-7 updated to reflect the new gate

* Address PR #50 review feedback on enable_ipv6 flag

- Extract connect-target selection into pure `select_connect_target()` with
  `ConnectTarget` enum; add unit tests covering all four IPv4/IPv6 combinations.
- Skip MX hosts with no A record when `enable_ipv6 = false` instead of silently
  falling through to the hostname (which could still resolve to IPv6 and
  violate the flag). Emits a clear "no A record; skipping" error so
  `try_deliver` moves on to the next MX.
- Replace blocking `ToSocketAddrs` in `resolve_ipv4` with a new
  `mx::resolve_a()` helper built on `hickory-resolver`, for consistency with
  MX resolution and to avoid blocking getaddrinfo in tokio workers.
- Add a doc comment on `resolve_ipv4` explaining why it exists (flag
  semantics, third add/remove in three sprints) to prevent future deletion.
- Add a doc comment on TLS SNI vs. connect-target mismatch while
  `dangerous_accept_invalid_certs` is set.
- Extract `detect_server_ipv6(enable_ipv6, net)` gate in setup.rs and add
  tests using `get_server_ipv6_calls` counter on `MockNetworkOps` to verify
  the network call is made iff the flag is true.
- Correct book/configuration.md: `aimx verify` only probes port 25, not IPv6;
  the flag only affects `aimx setup`.
- Add post-merge addendum under Sprint 26 in docs/sprint.md noting that this
  follow-up flipped the default to opt-in IPv6.
uzyn added a commit that referenced this pull request Apr 17, 2026
…lans

Code-review-backed fix plans for each of the 10 findings, with file:line
refs, effort estimates, and a priority order. No code changes yet —
this consolidates the investigation so fixes can be sequenced.

- #10 DKIM mismatch: not a code bug; DNS republish + optional startup check
- #9 shell injection: pass trigger vars via env, not string substitution
- #8 MCP writes: route state mutations through daemon UDS
- #7 claude-code hint: print `claude mcp add` command post-install
- #4 send config read: move mailbox resolution to daemon side
- #2 SPF: plumb envelope MAIL FROM from smtp session through ingest
- #5 wildcard send: remove wildcard branch from resolve_from_mailbox
- #1 mailbox create: add restart hint (or route via daemon)
- #3 plan wording: clarify "compose new" in docs/manual-test.md
uzyn added a commit that referenced this pull request Apr 17, 2026
* docs: add manual test results with findings

Full execution of docs/manual-test.md against agent.zeroshot.lol.
10 findings recorded with severity and fix direction, notably:

- P0 DKIM key on disk does not match DNS TXT (root cause of
  outbound dkim=fail at Gmail)
- P0 Shell injection in on_receive cmd template expansion
- P1 MCP write ops (email_mark_read, etc.) fail when MCP runs
  as non-root due to root:root 0644 mailbox files

* docs: add Recommended fixes section with per-finding implementation plans

Code-review-backed fix plans for each of the 10 findings, with file:line
refs, effort estimates, and a priority order. No code changes yet —
this consolidates the investigation so fixes can be sequenced.

- #10 DKIM mismatch: not a code bug; DNS republish + optional startup check
- #9 shell injection: pass trigger vars via env, not string substitution
- #8 MCP writes: route state mutations through daemon UDS
- #7 claude-code hint: print `claude mcp add` command post-install
- #4 send config read: move mailbox resolution to daemon side
- #2 SPF: plumb envelope MAIL FROM from smtp session through ingest
- #5 wildcard send: remove wildcard branch from resolve_from_mailbox
- #1 mailbox create: add restart hint (or route via daemon)
- #3 plan wording: clarify "compose new" in docs/manual-test.md
uzyn added a commit that referenced this pull request Apr 17, 2026
Sprint 44 (post-launch security + quick fixes) addresses findings #9,
#10, #7, #1-tier-1, #3. Sprint 45 (strict outbound + MCP writes via
daemon addresses #4, #5, #8 and introduces UDS MARK-READ/MARK-UNREAD
verbs. Sprint 46 (mailbox CRUD via UDS) addresses #1-tier-2 with
MAILBOX-CREATE/MAILBOX-DELETE verbs so daemon picks up changes live.

PRD FR-18d tightened: outbound send now requires a concrete non-wildcard
mailbox under config.domain; catchall is inbound-only. PRD FR-18e added
to cover the new state-mutation verbs on the UDS socket.

Finding #2 (SPF envelope MAIL FROM) excluded — already shipped in
cd22428.
EOF
)
uzyn added a commit that referenced this pull request Apr 18, 2026
- `aimx send` no longer reads /etc/aimx/config.toml; daemon parses From:
  from the submitted body and resolves the sender mailbox against its
  in-memory Config.
- Outbound rejects both foreign-domain From (ERR DOMAIN) and any From
  whose local part does not match an explicitly configured non-wildcard
  mailbox (ERR MAILBOX). Catchall is inbound-only per FR-18d (tightened).
- New AIMX/1 MARK-READ / MARK-UNREAD UDS verbs with typed codec, per-
  mailbox tokio RwLock in a StateContext, and client wiring in `aimx mcp`
  so email_mark_read / email_mark_unread work without root write access
  to mailbox files.
- New error codes PROTOCOL / NOTFOUND / IO added; legacy From-Mailbox:
  header silently ignored for forward-compatibility.

Closes findings #4, #5, #8 from the 2026-04-17 manual test run.
uzyn added a commit that referenced this pull request Apr 21, 2026
Add verify service, status/verify CLI commands, and documentation

Implement Sprint 6 (Verify Service + Polish):
- Port probe HTTP service (services/verify/) using axum for inbound port 25 checks
- Email echo MDA handler for DKIM/SPF verification replies
- `aimx status` command showing domain, mailboxes, message counts, DKIM/OpenSMTPD status
- `aimx verify` command for end-to-end email verification via verify@aimx.email
- Enhanced --help text for all CLI commands
- Comprehensive README with quick start, usage, config reference, DNS records
- MIT LICENSE file
- 272 tests (220 unit + 35 integration + 17 verify service)
uzyn added a commit that referenced this pull request Apr 21, 2026
* Make IPv6 outbound opt-in via enable_ipv6 config flag

aimx send now defaults to IPv4-only outbound delivery. Set
enable_ipv6 = true in config.toml to opt into dual-stack (OS
chooses). This matches the SPF record aimx setup generates by
default (ip4: only) and avoids SPF failures at Gmail when the
server has a global IPv6 but DNS isn't set up for it.

- Config: new enable_ipv6: bool field, defaults to false
- send.rs: LettreTransport carries the flag; re-introduces
  resolve_ipv4() helper and gates the connect target in
  try_deliver()
- mcp.rs: pass config.enable_ipv6 when constructing transport
- book/configuration.md: new "IPv6 delivery (advanced)" section
  documenting the required AAAA + ip6: SPF additions
- book/setup.md: note under DNS table that AAAA / ip6: are only
  needed when enable_ipv6 is on
- aimx setup is unchanged — this is a hidden config flag

Verified live: default config connects over IPv4 (AF_INET);
adding enable_ipv6 = true makes it connect over IPv6 (AF_INET6).

* Update PRD for enable_ipv6 opt-in (FR-7, FR-19, resolved decision #8)

* Gate aimx setup IPv6 detection behind enable_ipv6 flag

aimx setup now reads `enable_ipv6` from the existing config (if any)
and only calls `get_server_ipv6()` when the flag is true. With the
flag unset or false, AAAA is not advertised, `ip6:` is not added to
SPF, and neither is verified — matching the IPv4-only default of
`aimx send`. Any existing AAAA record in DNS is left alone.

Re-entrant workflow:
- Fresh install -> no config yet -> IPv4-only setup.
- User edits config.toml and adds `enable_ipv6 = true`.
- User re-runs `sudo aimx setup <domain>` -> setup detects the flag,
  shows AAAA + ip6: SPF in the DNS table, and verifies them.

Verified live on a dual-stack VPS: default config produces an
IPv4-only DNS table; adding `enable_ipv6 = true` produces AAAA +
ip6: SPF in the same output.

- src/setup.rs: read enable_ipv6 when loading existing config,
  gate get_server_ipv6() call in run_setup
- book/configuration.md: updated IPv6-delivery section to describe
  the re-run-setup workflow and the ignore-when-disabled behavior
- book/setup.md: note that AAAA / ip6: are only shown when the flag
  is on
- docs/prd.md: FR-7 updated to reflect the new gate

* Address PR #50 review feedback on enable_ipv6 flag

- Extract connect-target selection into pure `select_connect_target()` with
  `ConnectTarget` enum; add unit tests covering all four IPv4/IPv6 combinations.
- Skip MX hosts with no A record when `enable_ipv6 = false` instead of silently
  falling through to the hostname (which could still resolve to IPv6 and
  violate the flag). Emits a clear "no A record; skipping" error so
  `try_deliver` moves on to the next MX.
- Replace blocking `ToSocketAddrs` in `resolve_ipv4` with a new
  `mx::resolve_a()` helper built on `hickory-resolver`, for consistency with
  MX resolution and to avoid blocking getaddrinfo in tokio workers.
- Add a doc comment on `resolve_ipv4` explaining why it exists (flag
  semantics, third add/remove in three sprints) to prevent future deletion.
- Add a doc comment on TLS SNI vs. connect-target mismatch while
  `dangerous_accept_invalid_certs` is set.
- Extract `detect_server_ipv6(enable_ipv6, net)` gate in setup.rs and add
  tests using `get_server_ipv6_calls` counter on `MockNetworkOps` to verify
  the network call is made iff the flag is true.
- Correct book/configuration.md: `aimx verify` only probes port 25, not IPv6;
  the flag only affects `aimx setup`.
- Add post-merge addendum under Sprint 26 in docs/sprint.md noting that this
  follow-up flipped the default to opt-in IPv6.
uzyn added a commit that referenced this pull request Apr 21, 2026
…lans

Code-review-backed fix plans for each of the 10 findings, with file:line
refs, effort estimates, and a priority order. No code changes yet —
this consolidates the investigation so fixes can be sequenced.

- #10 DKIM mismatch: not a code bug; DNS republish + optional startup check
- #9 shell injection: pass trigger vars via env, not string substitution
- #8 MCP writes: route state mutations through daemon UDS
- #7 claude-code hint: print `claude mcp add` command post-install
- #4 send config read: move mailbox resolution to daemon side
- #2 SPF: plumb envelope MAIL FROM from smtp session through ingest
- #5 wildcard send: remove wildcard branch from resolve_from_mailbox
- #1 mailbox create: add restart hint (or route via daemon)
- #3 plan wording: clarify "compose new" in docs/manual-test.md
uzyn added a commit that referenced this pull request Apr 21, 2026
* docs: add manual test results with findings

Full execution of docs/manual-test.md against agent.zeroshot.lol.
10 findings recorded with severity and fix direction, notably:

- P0 DKIM key on disk does not match DNS TXT (root cause of
  outbound dkim=fail at Gmail)
- P0 Shell injection in on_receive cmd template expansion
- P1 MCP write ops (email_mark_read, etc.) fail when MCP runs
  as non-root due to root:root 0644 mailbox files

* docs: add Recommended fixes section with per-finding implementation plans

Code-review-backed fix plans for each of the 10 findings, with file:line
refs, effort estimates, and a priority order. No code changes yet —
this consolidates the investigation so fixes can be sequenced.

- #10 DKIM mismatch: not a code bug; DNS republish + optional startup check
- #9 shell injection: pass trigger vars via env, not string substitution
- #8 MCP writes: route state mutations through daemon UDS
- #7 claude-code hint: print `claude mcp add` command post-install
- #4 send config read: move mailbox resolution to daemon side
- #2 SPF: plumb envelope MAIL FROM from smtp session through ingest
- #5 wildcard send: remove wildcard branch from resolve_from_mailbox
- #1 mailbox create: add restart hint (or route via daemon)
- #3 plan wording: clarify "compose new" in docs/manual-test.md
uzyn added a commit that referenced this pull request Apr 21, 2026
Sprint 44 (post-launch security + quick fixes) addresses findings #9,
#10, #7, #1-tier-1, #3. Sprint 45 (strict outbound + MCP writes via
daemon addresses #4, #5, #8 and introduces UDS MARK-READ/MARK-UNREAD
verbs. Sprint 46 (mailbox CRUD via UDS) addresses #1-tier-2 with
MAILBOX-CREATE/MAILBOX-DELETE verbs so daemon picks up changes live.

PRD FR-18d tightened: outbound send now requires a concrete non-wildcard
mailbox under config.domain; catchall is inbound-only. PRD FR-18e added
to cover the new state-mutation verbs on the UDS socket.

Finding #2 (SPF envelope MAIL FROM) excluded — already shipped in
f5cebd2.
EOF
)
uzyn added a commit that referenced this pull request Apr 21, 2026
- `aimx send` no longer reads /etc/aimx/config.toml; daemon parses From:
  from the submitted body and resolves the sender mailbox against its
  in-memory Config.
- Outbound rejects both foreign-domain From (ERR DOMAIN) and any From
  whose local part does not match an explicitly configured non-wildcard
  mailbox (ERR MAILBOX). Catchall is inbound-only per FR-18d (tightened).
- New AIMX/1 MARK-READ / MARK-UNREAD UDS verbs with typed codec, per-
  mailbox tokio RwLock in a StateContext, and client wiring in `aimx mcp`
  so email_mark_read / email_mark_unread work without root write access
  to mailbox files.
- New error codes PROTOCOL / NOTFOUND / IO added; legacy From-Mailbox:
  header silently ignored for forward-compatibility.

Closes findings #4, #5, #8 from the 2026-04-17 manual test run.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant