Skip to content

feat(email): split email-send from the full email provider - #17

Merged
senamakel merged 9 commits into
mainfrom
email-send-split
Aug 30, 2026
Merged

feat(email): split email-send from the full email provider#17
senamakel merged 9 commits into
mainfrom
email-send-split

Conversation

@senamakel

@senamakel senamakel commented Aug 30, 2026

Copy link
Copy Markdown
Member

Splits the email feature into a send-only half and the full provider, so a host that only ever delivers mail stops linking the IMAP receive stack.

email-send = ["dep:lettre"]
email      = ["email-send", "dep:async-imap", "dep:mail-parser"]

Why

This is a prerequisite for moving OpenHuman's channels onto the TinyChannels bus module, and without it that move sheds almost nothing.

OpenHuman's voice feature requires tinychannels/email for one reason: voice/audio_toolkit/ops.rs emails a generated podcast as an attachment. It uses exactly EmailChannel::new(cfg) and send_message(msg) — inherent methods, never the Channel trait, never a mailbox. But email was one feature, so a voice-enabled build linked the whole IMAP receive stack it could never call.

Measured on OpenHuman's product profile (dep-sim.py, cross-checked against an independent reachability walk over cargo tree --prefix depth — both agree):

Cut delta
async-imap + mail-parser (receive) −15 pkg / −13 names
prost (lark) −4 pkg / −4 names
tinychannels root edge entirely −20 pkg / −18 names

So ~17 of the 18 names attributable to this crate are the two provider features, not the crate itself. Without this split, a voice-enabled host keeps async-imap + mail-parser no matter where the channel providers run, and the bus move sheds only the 4 lark names.

At this crate's own level the split is 174 → 156 packages (−18) for emailemail-send: async-channel, async-imap, concurrent-queue, crossbeam-utils, equivalent, event-listener, event-listener-strategy, hashify, imap-proto, indexmap, mail-parser, minimal-lexical, parking, pin-project, pin-project-internal, pin-utils, self_cell, stop-token.

What is gated

Everything lives in src/providers/email_channel.rs and the two halves barely touch:

  • Send (email-send, lettre)create_smtp_transport, send_message, build_plain_message, build_message_with_attachment.
  • Receive (email, async-imap + mail-parser)connect_imap, fetch_unseen, wait_for_changes, listen_with_idle, run_idle_session, process_unseen, extract_sender, extract_text, ParsedEmail, IdleWaitResult, and the seen_messages dedupe field.

impl Channel for EmailChannel is gated on the full email feature. A send-only build cannot listen, so advertising the trait would promise a half-working channel. Nothing is lost: voice uses the inherent methods.

The module crate is unaffected — tinychannels-module already requests features = ["email", "lark", "whatsapp-web"] and still gets the complete provider.

A hole in the existing CI matrix, found while doing this

The --features email / --features lark lanes run at the workspace root, where tinychannels-module depends on this crate with features = ["email", "lark", "whatsapp-web"]. Cargo unifies those in, so those lanes have really been all-features builds and could never observe a gate being off. My first cargo check --features email-send "passed" for exactly that reason, and cargo tree still showed async-imap.

The three lanes added here use -p tinychannels --no-default-features, which is what makes them test the surface they name. I have deliberately not retrofitted the existing lanes in this PR — that is a separate change with its own risk of surfacing unrelated breakage, and worth doing on its own.

Tests

send_only_tests in email_channel.rs, gated #[cfg(all(test, feature = "email-send", not(feature = "email")))], pins the three items OpenHuman's voice path reaches for. This is the half a compile check cannot state on its own: the crate builds either way, so nothing would otherwise notice the send path growing a dependency on the receive half.

Verified locally, exit codes checked individually rather than chained:

fmt-check              PASS
clippy all-features    PASS
clippy email           PASS
clippy no-features     PASS
clippy email-send      PASS
test all-features      PASS
test email-send        PASS   (incl. both new tests, confirmed by name)
test email             PASS   (878 + 111 + 3)

The scoped clippy run caught four real items a plain cargo check did not — unused anyhow!, async_trait, Duration, and a seen_messages field never read in a send-only build. All are now gated.

Follow-up

Consumer side in OpenHuman: voice moves to tinychannels/email-send, and channels drops tinychannels/email once the providers run in the module.

Commits are the auto-commit hook's granular checkpoints, kept as-is by preference; the reasoning is here rather than squashed into one message.

Summary by CodeRabbit

  • New Features

    • Added an email send-only configuration for SMTP delivery without requiring IMAP receiving capabilities.
    • The full email configuration continues to support both SMTP sending and IMAP receiving.
    • Email messages with plain text and attachments can now be built and formatted in send-only setups.
  • Bug Fixes

    • Improved feature separation so send-only builds avoid unnecessary email-receiving components.
    • Added validation coverage for the new send-only email configuration.

senamakel and others added 7 commits August 30, 2026 16:45
Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T14:10:17.844237Z e820aca New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1eddd8ad-38b5-4a9e-b4d4-8ccdde311848

📥 Commits

Reviewing files that changed from the base of the PR and between 855e29c and fac79ca.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • Cargo.toml
  • src/providers/email_channel.rs
  • src/providers/mod.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Important

Approval pending

CodeRabbit has no unresolved comments, but it has not reviewed the latest commit.

Use the checkbox below to review the latest commit. CodeRabbit will approve the changes if it finds no blocking issues.

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The email feature is split into SMTP-only and full IMAP receive plus SMTP send modes. Receive code and state compile only with email. New tests and CI steps validate the SMTP-only mode.

Changes

Email feature split

Layer / File(s) Summary
Feature contract and module exposure
Cargo.toml, src/providers/mod.rs
email-send enables SMTP sending through lettre. email extends it with IMAP dependencies. The email module and EmailChannel re-export are available with email-send.
Conditional receive implementation
src/providers/email_channel.rs
IMAP imports, state, helpers, IDLE processing, parsed email types, and receive test support require the email feature.
SMTP-only validation
src/providers/email_channel.rs, .github/workflows/ci.yml
Send-only tests build plain and attachment messages. CI runs Clippy, build, and test commands with only email-send enabled.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to fac79

The PR separates send-only email support from receive functionality and adds focused validation for the supported feature combinations; no actionable merge-blocking risk remains beyond normal checks and review.

Poem

A rabbit sends mail through a bright SMTP stream
IMAP stays tucked from the send-only dream
Plain notes and attachments hop into flight
CI checks each lane in the morning light
Two feature paths now compile just right

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.75% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 16 functions across 2 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: splitting the full email provider from the SMTP-only email-send feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 68.75% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 16 functions across 2 files. (2 skipped: 2 unsupported.)

✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requesting changes: 1 lane(s) blocking, worst finding is high.

Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.

             $0.0148 · 122,972 in / 4,352 out · 30,306 cached (25%) · openrouter/openai/text-embedding-3-small, deepseek/deepseek-v4-flash, z-ai/glm-5.2 · 342 embedded
critique:    $0.0066 · 53,691 in  / 2,917 out · 9,057 cached (17%)  · deepseek/deepseek-v4-flash, z-ai/glm-5.2
security:    $0.0055 · 46,505 in  / 857 out   · 15,331 cached (33%) · deepseek/deepseek-v4-flash, z-ai/glm-5.2
tests:       $0.0012 · 15,053 in  / 124 out   · 0 cached (0%)       · deepseek/deepseek-v4-flash
description: $0.0014 · 7,723 in   / 454 out   · 5,918 cached (77%)  · z-ai/glm-5.2

Comment thread src/providers/email_channel.rs
Comment thread src/providers/mod.rs
Comment thread src/providers/mod.rs
Comment thread Cargo.toml
@tinysweeper

tinysweeper Bot commented Aug 30, 2026

Copy link
Copy Markdown

How this change flows

2 changed behaviours across 8 relationships. 6 surrounding behaviours are shown (60 graph nodes walked). 40 further behaviours left out to keep the diagram readable.

flowchart LR
  n0["ParsedEmail<br/>changed<br/>1 finding"]:::blocking
  n1["yuanbao<br/>changed<br/>2 findings"]:::blocking
  n2["Result"]:::impacted
  n3["full_dm_dispatch"]:::impacted
  n4["new"]:::impacted
  n5["fetch_unseen"]:::impacted
  n6["process_unseen"]:::impacted
  n7["run_idle_session"]:::impacted
  n3 -->|uses| n1
  n5 -->|uses| n0
  n5 -->|uses| n2
  n5 -->|calls| n4
  n6 -->|uses| n2
  n6 -->|calls| n5
  n7 -->|uses| n2
  n7 -->|calls| n6
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading

Green: changed behaviour. Grey: surrounding behaviour. Arrows name the call, use, implementation, or test relationship. Orange: has findings. Red: has a finding that blocks the merge.

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot added the priority: p1 Next. Wrong behaviour a user will hit, or a security weakness behind a condition. label Aug 30, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fac79caaa0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/providers/mod.rs
Comment thread Cargo.toml
senamakel and others added 2 commits August 30, 2026 17:04
Split the existing `email` feature into a lighter `email-send` variant that only requires SMTP dependencies, allowing consumers that only need to send email to avoid pulling in the heavier IMAP and mail-parser crates. The `EmailChannel` type is now re-exported under the `email-send` feature gate, and the compile-time smoke test has been updated to verify the export works with the new feature name.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e820acac1f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Cargo.toml
@senamakel
senamakel merged commit 465761a into main Aug 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p1 Next. Wrong behaviour a user will hit, or a security weakness behind a condition.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant