Polymail
Unified email sending interface for Rust. Write your email once, send it through any supported provider — swap providers by changing one line.
Currently supported: Lettermint, Postmark, SendGrid.
Usage
[]
= { = "0.1", = ["lettermint"] }
= { = "1", = ["rt", "macros"] }
Lettermint is the default feature, so features = ["lettermint"] can be omitted.
Send an email
use ;
use LettermintMailer;
async
HTML + text with all options
use ;
use LettermintMailer;
async
Batch sending
Providers with native batch support send all emails in a single API call. Others fall back to sequential sends.
use ;
use LettermintMailer;
async
Switching providers
use ;
use PostmarkMailer;
let mailer = new;
// Same Email, same .send() call — just a different mailer.
Fallback across providers
FallbackMailer tries providers in order. On transient failures (network issues, rate limits, service outages), it moves to the next provider. On permanent failures (invalid address, hard bounce), it returns immediately — retrying won't help.
use ;
use LettermintMailer;
use PostmarkMailer;
let mailer = new;
// Tries Lettermint first; if it's down, sends through Postmark.
let result = mailer.send.await?;
FallbackMailer implements Mailer, so it works anywhere a single provider does — including Box<dyn Mailer>.
Errors that trigger fallback:
| Error | Fallback? | Reason |
|---|---|---|
Provider |
yes | Transport failure (network, TLS, timeout) |
RateLimitExceeded |
yes | Provider-specific quota, next provider may accept |
ServiceUnavailable |
yes | Provider is down |
Authentication |
yes | Bad key for this provider, next may work |
InvalidAddress |
no | Bad email, will fail everywhere |
InactiveRecipient |
no | Recipient-level suppression |
SpamComplaint |
no | Recipient-level suppression |
HardBounce |
no | Recipient-level suppression |
Serialization |
no | Client-side bug |
Using as a trait object
use Mailer;
use LettermintMailer;
Features
| Feature | Default | Description |
|---|---|---|
lettermint |
yes | Lettermint provider |
postmark |
no | Postmark provider |
sendgrid |
no | SendGrid provider |
Enable multiple providers at once:
= { = "0.1", = ["lettermint", "postmark"] }
Provider capabilities
| Capability | Lettermint | Postmark | SendGrid |
|---|---|---|---|
| Single send | yes | yes | yes |
| Batch send (native) | yes (up to 500) | yes (up to 500) | no (sequential fallback) |
| Attachments | yes | yes | yes |
| Inline attachments | yes | yes | yes |
| Custom headers | yes | yes | yes (per-personalization) |
| Multiple reply-to | yes | first only | first only |
| Tags | first tag | first tag | multiple (categories) |
| Metadata | yes | yes | yes (as custom args) |
Error handling
Provider-specific errors are mapped to shared SendError variants so you can handle common failure modes without matching on providers:
use ;
match mailer.send.await
Error mapping by provider
SendError |
Postmark | Lettermint | SendGrid |
|---|---|---|---|
Authentication |
— | HTTP 401/403 | HTTP 401/403 |
InvalidAddress |
error code 300 | HTTP 422 (validation) | HTTP 400 |
InactiveRecipient |
error code 406 | batch status | — |
SpamComplaint |
error code 409 | batch status | — |
HardBounce |
error code 422 | batch status | — |
RateLimitExceeded |
error code 429 | HTTP 429 | HTTP 429 |
ServiceUnavailable |
error codes 500–504 | HTTP 5xx | HTTP 500–504 |
Provider |
transport errors | transport/parse errors | transport/parse errors |
Api |
other error codes | other HTTP errors | other HTTP errors |
Testing
License
Dual-licensed under MIT or Apache 2.0.