Rust
Use the official Lettermint Rust SDK to send email from a Rust application. The SDK also supports the Team API and webhook signature verification. See SDKs and integrations for other packages.
This guide covers SDK 2.0. If your project uses 1.x, follow the Rust SDK 2.0 upgrade guide on GitHub.
Requirements
- Rust 1.98 or newer (edition 2024)
- A Tokio runtime. The SDK uses
reqwestwith rustls. - A Project API token
Install the SDK
Add the SDK and Tokio to Cargo.toml:
Code
Run cargo build to install the dependencies. The package is also available on crates.io, and its API reference is on docs.rs.
Send your first email
Store your Project API token in LETTERMINT_PROJECT_TOKEN. Then, create the client with the token as the sending token and send a message:
Code
The client stores nothing about a message. It is cheap to clone, safe to share across tasks, and meant to be created once. You can also pass the token as a single value, as in Lettermint::new(token)?.
API acceptance does not confirm delivery. Use webhooks to receive delivery and bounce events.
The Team API uses a separate Team API token. Pass it with team_token, alone or next to sending_token. Each part of the client uses its own token and never falls back to the other. See Use the Team API.
Add email options
The examples in this section use lettermint.emails().compose(), which returns an owned EmailBuilder. Each call to compose() starts a new message, so options from one message never reach the next. Setters take the builder by value and return it, and send() borrows it, so a builder can be sent again. Clone a builder to use it as a template:
Code
To build an email over several statements, rebind the builder:
Code
Setters never fail. send() and build() validate the message before any request.
You can also send a plain types::SendMailRequest with lettermint.emails().send(&message, options). It uses the API's field names. Optional nullable fields such as html, text, and tag are Option<Option<String>>: None leaves the field out, and Some(None) sends null.
Code
Recipients and reply-to addresses
to, cc, bcc, and reply_to replace the list. Each takes one address or a list, such as an array, a Vec, or a slice:
Code
Metadata and headers
Use metadata to add application data to webhook payloads. Metadata does not add headers to the email. metadata and headers take a list of pairs and replace what was set before:
Code
Tags
Use name and value tags to organize messages for filtering and analytics:
Code
You can add up to 20 tags. The tag method remains available for one legacy tag, and you can add up to 19 tags when you also use it. The SDK checks tag names and values before it sends the request and returns Error::InvalidInput with the field that failed, such as tags, or messages[2].tags in a batch. See Tags for the value rules.
Route and tracking settings
Select a route and control open and click tracking for the message:
Code
See open tracking, click tracking, and TLS delivery for the related behavior.
Attachments and inline images
Build an attachment with Attachment::from_bytes, which encodes the content to Base64 for you, or with Attachment::from_base64 if you already have encoded content:
Code
For an inline image, set the content ID and use the same value in the HTML cid: URL:
Code
See Attachments and inline images for size and file type limits. lettermint.blocked_file_types() lists the extensions and MIME types that the API rejects. It needs the team token.
Idempotency
Pass an idempotency key so a retry never sends the same email twice. If a request with the same key is repeated within the idempotency window, Lettermint returns the original result instead of sending again. The key is an option of the call that sends the email:
Code
emails().send(&message, options) and emails().send_batch(&messages, options) take the same SendOptions. The key applies only to that call, and the SDK never retries on its own. SendOptions::timeout(duration) overrides the client's timeout for one call.
Derive the key from something stable in your domain, such as an order or invoice ID, so the same business event always maps to the same key. See Idempotency for key rules and the retention period.
Schedule an email
Use an ISO 8601 timestamp to schedule an email:
Code
See Email scheduling for scheduling limits and cancellation behavior.
Batch sending
Send up to 500 emails in one request. send_batch takes a slice of SendMailRequest values. Call build() on a builder to add it:
Code
The SDK validates every message before the request. A failure names the message, for example messages[1].tags.
Handle errors
Every method returns lettermint::Result<T>, which uses the lettermint::Error enum. API errors carry an ApiError with status(), code(), message(), details(), and body(). Match the variants that you want to handle:
Code
The enum is #[non_exhaustive], so keep a wildcard arm. The other variants are Authentication, Permission, NotFound, Conflict, Server, and Api for any other 4xx response, plus Connection, UnexpectedResponse, Redirect, Config for a missing token or invalid option, InvalidInput for a request that the SDK rejected before sending it, and WebhookVerification. Error::status() and Error::code() work on every variant.
The SDK never retries on its own. Reuse the same idempotency key when you retry so the customer gets one email. Errors never contain request headers or tokens.
Use the Team API
With a Team API token, the same client manages domains, messages, projects, routes, statistics, suppressions, the team, and webhooks. Store the token in LETTERMINT_TEAM_TOKEN:
Code
To use both parts, set both tokens on one builder with sending_token and team_token. A missing token returns Error::Config before any request.
Query parameters are typed structs. Every list also has an iterate method that returns a Paginator. It follows next_cursor until the last page, and you read it with next() or as a futures::Stream:
Code
The email client sends the Project API token in X-Lettermint-Token. The Team API uses bearer authentication. Do not use a Project API token for Team API operations.
Verify webhook signatures
Verify each delivery with the webhook's signing secret (whsec_...) and the raw request body. Parsing and re-serializing the JSON breaks the signature:
Code
headers can be an http::HeaderMap (from the http crate, which axum and hyper use), a HashMap or BTreeMap of strings, or a list of (name, value) pairs. verify requires the X-Lettermint-Signature and X-Lettermint-Delivery headers and returns a WebhookVerificationError with a reason() when a delivery is not genuine. The default timestamp tolerance is five minutes. See Webhook signatures for a complete handler.
Next steps
Crate API reference
Review all public Rust types and methods.
Team API quickstart
Manage domains, projects, routes, and team settings.
Email activity
Review message status and delivery events.
GitHub repository
View the source, report issues, or contribute.