build: bump io-email

This commit is contained in:
Clément DOUIN
2026-05-20 23:47:49 +02:00
parent 791a54ef15
commit c2228e504f
2 changed files with 64 additions and 34 deletions
Generated
+1 -1
View File
@@ -1061,7 +1061,7 @@ dependencies = [
[[package]]
name = "io-email"
version = "0.0.1"
source = "git+https://github.com/pimalaya/io-email#8bcdf92f8872f8436d8482814965e5750d87c4cc"
source = "git+https://github.com/pimalaya/io-email#15aeedd215b5edf463e8b3cae8bbd4ae223e5cff"
dependencies = [
"chrono",
"chumsky",
+63 -33
View File
@@ -24,16 +24,17 @@
//! [`Deref`]/[`DerefMut`] onto the inner client so callers can call
//! its methods directly.
//!
//! Construction is backend-asymmetric (IMAP needs TLS + SASL, JMAP
//! needs an HTTP credential, Maildir just needs a root path). The
//! single [`EmailClient::new`] entry-point loads the configuration,
//! picks the merged account, then walks the configured backends in
//! `jmap → imap → maildir` order and opens the first one allowed by
//! the `BackendFlag`.
//! Construction picks the first storage backend (`jmap → imap →
//! maildir`) allowed by the `BackendFlag` that is configured on the
//! account. When the account also has SMTP configured, an SMTP slot
//! is registered on the same client so `send_message` works for
//! IMAP/Maildir accounts; JMAP accounts already send via JMAP
//! submission. SMTP connection failures are logged and skipped — the
//! client still opens for reading.
use std::ops::{Deref, DerefMut};
use anyhow::{bail, Result};
use anyhow::{Result, bail};
use crate::{
account::context::Account,
@@ -47,25 +48,24 @@ pub struct EmailClient {
}
impl EmailClient {
/// Loads the configuration, picks the active account, builds the
/// merged [`Account`], then opens the first backend allowed by
/// `backend` that is configured on the account. Selection order
/// is `jmap → imap → maildir`. Bails when no backend matches.
pub fn new(
config: Config,
mut account_config: AccountConfig,
backend: Backend,
) -> Result<Self> {
use io_email::client::EmailClientStd;
let mut inner = EmailClientStd::new();
let mut configured = false;
#[cfg(feature = "jmap")]
if backend.allows_jmap() {
if !configured && backend.allows_jmap() {
if let Some(jmap_config) = account_config.jmap.take() {
use io_email::client::EmailClientStd;
use io_jmap::client::JmapClientStd;
use pimalaya_stream::tls::Tls;
use crate::jmap::client::{jmap_http_auth, parse_server_url};
let account = Account::from(config).merge(Account::from(account_config));
let mut tls: Tls = jmap_config.tls.clone().into();
tls.rustls.alpn = vec!["http/1.1".into()];
let http_auth = jmap_http_auth(jmap_config.auth.clone())?;
@@ -73,21 +73,17 @@ impl EmailClient {
let mut client = JmapClientStd::connect(&url, &tls, http_auth)?;
client.session_get(&url)?;
return Ok(Self {
inner: EmailClientStd::Jmap(client),
account,
});
inner = inner.with_jmap(client);
configured = true;
}
}
#[cfg(feature = "imap")]
if backend.allows_imap() {
if !configured && backend.allows_imap() {
if let Some(imap_config) = account_config.imap.take() {
use io_email::client::EmailClientStd;
use io_imap::client::ImapClientStd;
use pimalaya_stream::{sasl::Sasl, std::stream::StreamStd, tls::Tls};
let account = Account::from(config).merge(Account::from(account_config));
let mut tls: Tls = imap_config.tls.into();
tls.rustls.alpn = vec!["imap".into()];
let sasl: Option<Sasl> = imap_config.sasl.map(Sasl::try_from).transpose()?;
@@ -95,30 +91,64 @@ impl EmailClient {
let client =
ImapClientStd::<StreamStd>::connect(&server, &tls, imap_config.starttls, sasl)?;
return Ok(Self {
inner: EmailClientStd::Imap(client),
account,
});
inner = inner.with_imap(client);
configured = true;
}
}
#[cfg(feature = "maildir")]
if backend.allows_maildir() {
if !configured && backend.allows_maildir() {
if let Some(maildir_config) = account_config.maildir.take() {
use io_email::client::EmailClientStd;
use io_maildir::client::MaildirClient;
let account = Account::from(config).merge(Account::from(account_config));
let client = MaildirClient::new(maildir_config.root);
return Ok(Self {
inner: EmailClientStd::Maildir(client),
account,
});
inner = inner.with_maildir(client);
configured = true;
}
}
bail!("no backend matching `{backend}` is configured for this account")
if !configured {
bail!("no backend matching `{backend}` is configured for this account");
}
// Register SMTP alongside the storage backend so shared
// `send_message` works for IMAP/Maildir accounts. JMAP already
// sends via submission, but if both are present, SMTP wins
// because storage is registered first.
#[cfg(feature = "smtp")]
if let Some(smtp_config) = account_config.smtp.take() {
use std::net::Ipv4Addr;
use io_smtp::{client::SmtpClientStd, rfc5321::types::ehlo_domain::EhloDomain};
use pimalaya_stream::{sasl::Sasl, std::stream::StreamStd, tls::Tls};
let smtp = (|| -> Result<SmtpClientStd<StreamStd>> {
let mut tls: Tls = smtp_config.tls.into();
tls.rustls.alpn = vec!["smtp".into()];
let sasl: Option<Sasl> = smtp_config.sasl.map(Sasl::try_from).transpose()?;
let domain: EhloDomain<'static> = Ipv4Addr::new(127, 0, 0, 1).into();
let server = crate::smtp::client::parse_smtp_server(&smtp_config.server)?;
Ok(SmtpClientStd::<StreamStd>::connect(
&server,
&tls,
smtp_config.starttls,
domain,
sasl,
)?)
})();
match smtp {
Ok(client) => inner = inner.with_smtp(client),
Err(err) => {
log::warn!("SMTP backend disabled: {err}. Sending will be unavailable.")
}
}
}
let account = Account::from(config).merge(Account::from(account_config));
Ok(Self { inner, account })
}
}