Async vs Blocking Execution
Relevant source files
The following files were used as context for generating this wiki page:
The Resend Rust SDK provides two modes of execution: asynchronous (default) and synchronous (blocking). These modes are mutually exclusive and are controlled via Cargo features. The SDK uses the maybe-async crate to manage a single codebase that can compile into either execution style.
Sources: README.md:14-16, Cargo.toml:25-27
Feature Configuration
The execution mode is determined at compile time using the blocking feature. By default, the crate operates in asynchronous mode using tokio as the runtime.
| Feature | Execution Mode | Dependency Impact | Description |
|---|---|---|---|
| (Default) | Asynchronous | reqwest | Uses non-blocking I/O and requires an async runtime. |
blocking | Synchronous | reqwest/blocking | Blocks the current thread until the request completes. |
Sources: Cargo.toml:25-27, README.md:21-23
Technical Implementation
The SDK implements both modes by applying the #[maybe_async::maybe_async] attribute to service methods. This attribute transforms async methods into synchronous ones when the blocking feature is enabled.
The diagram shows how the maybe_async attribute branches the compilation output based on the feature flag.
Sources: src/templates.rs:24, src/emails.rs:32, src/contacts.rs:43
Service Structure and Data Flow
The Resend client holds an Arc<Config>. Services like EmailsSvc or TemplateSvc wrap this configuration to perform requests.
The sequence diagram illustrates the internal flow where the execution mode determines if the send call yields a future or blocks.
Sources: src/emails.rs:25-46, src/templates.rs:18-32
Limitations and Behavior Differences
Execution modes differ significantly in how they handle rate limiting and resource management.
Rate Limiting
The SDK includes built-in rate limiting that targets 9 requests per 1.1 seconds to stay under the Resend API limit of 10 requests per second. However, this functionality is restricted to the asynchronous version. The blocking client does not implement automatic rate limiting.
Sources: src/lib.rs:5-18, README.md:31-33
Thread Safety and Testing
When using the blocking feature in tests, manual delays are often required because the automated rate limiter is inactive. The SDK uses a shared client in tests to maintain consistent state across execution units.
Sources: CONTRIBUTING.md:7-14, src/lib.rs:271-275
Code Snippet: Async usage (Default)
#[tokio::main]
async fn main() -> Result<()> {
let resend = Resend::default();
let email = CreateEmailBaseOptions::new(from, to, subject);
// .await is required here
let id = resend.emails.send(email).await?.id;
Ok(())
}
Sources: src/lib.rs:43-58
Code Snippet: Blocking usage
// Requires feature = "blocking"
fn main() -> Result<()> {
let resend = Resend::default();
let email = CreateEmailBaseOptions::new(from, to, subject);
// No .await; method blocks until completion
let id = resend.emails.send(email)?.id;
Ok(())
}
Sources: src/emails.rs:32-46, src/lib.rs:205-217
Conclusion
The choice between Async and Blocking execution depends on the application environment. Asynchronous execution is the default and recommended mode as it supports built-in rate limiting and higher concurrency. The blocking mode is available for simpler scripts or environments where an async runtime like tokio is not preferred, though users must manage rate limits manually.
Sources: README.md:31-33, src/lib.rs:5-10