missive 0.7.0

Compose, deliver, preview, and test emails in Rust - pluggable providers with zero configuration code
Documentation
# Decision 0002: Mailer Async Dispatch Strategy

Status: accepted

Issue: `missive-wdl.14`

Date: 2026-05-28

## Context

`Mailer` currently uses `#[async_trait]`:

```rust
#[async_trait]
pub trait Mailer: Send + Sync {
    async fn deliver_prepared(&self, email: &PreparedEmail) -> Result<DeliveryResult, MailError>;
}
```

The macro boxes returned futures so the trait remains object-safe. That object
safety is used by environment-based provider selection, the compatibility global
facade, interceptors, and `EmailClient<Arc<dyn Mailer>>`.

Native async functions in traits remove the macro for static dispatch, but they
are not object-safe. A trait with native `async fn` cannot be used directly as
`dyn Mailer`, so runtime provider selection would need a separate boxed adapter
trait or wrapper.

## Decision

For v0.7, keep `Mailer` as the object-safe provider trait implemented with
`async_trait`.

`EmailClient<M>` remains the primary application API:

```rust
let client = EmailClient::new(ResendMailer::new(config)?);
client.deliver(email).await?;
```

Runtime provider selection remains explicit through a trait object:

```rust
let client: EmailClient<Arc<dyn Mailer>> = EmailClient::from_env()?;
```

Do not split the public provider trait into native-async and boxed-object
variants during the v0.7 cleanup. The library can revisit that split later if
benchmarks show the boxed future allocation matters for a real workload.

## Tradeoffs

### Object Safety

Keeping `async_trait` preserves a single provider trait that works for both
concrete providers and runtime-selected providers. This keeps custom provider
implementations simple and avoids forcing downstream applications to learn two
traits.

Native async traits would improve the static-dispatch shape, but they would
break `Arc<dyn Mailer>` and require a second object-safe adapter. That adapter
would still box futures, so runtime dispatch would not become allocation-free.

### Dispatch Modes

Concrete clients such as `EmailClient<ResendMailer>` avoid trait-object dynamic
dispatch, but provider method futures are still boxed by `async_trait`.

Object clients such as `EmailClient<Arc<dyn Mailer>>` pay both dynamic dispatch
and boxed future costs. This is acceptable for v0.7 because delivery providers
are I/O-bound and network latency dominates a small allocation.

### Migration Cost

Moving to native async traits now would require touching every provider,
interceptor, compatibility facade, config loader, and custom-provider example.
It would also introduce a new boxed adapter type and migration guidance before
the rest of the v0.7 API changes have settled.

The current cleanup has higher-value work: typed configuration, explicit
clients, validated prepared emails, attachment correctness, and safer provider
serialization.

## Future Option

A later release can introduce a split design if there is measured demand:

- `StaticMailer` with native async trait methods for concrete provider use.
- `BoxMailer` or `DynMailer` as an object-safe adapter for runtime provider
  selection.
- `EmailClient<M>` generic over either concrete static providers or the boxed
  adapter.

That future change should be benchmark-driven and should include migration
examples for custom providers.

## Consequences

`async_trait` remains a core dependency for v0.7.

The public docs should describe `EmailClient<M>` as the primary API and
`Arc<dyn Mailer>` as the explicit runtime-dispatch path, not as a hidden default.

This decision supersedes the zero-boxing aspiration in Decision 0001 for the
v0.7 cleanup window. The target remains idiomatic and explicit ownership first;
removing boxed futures is deferred until it has evidence and a lower-migration
design.