Skip to content

Commit 1cf728f

Browse files
author
jetemail
committed
init
1 parent d728211 commit 1cf728f

17 files changed

Lines changed: 1121 additions & 2 deletions

.github/workflows/ci.yml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
12+
jobs:
13+
check:
14+
name: Check
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: dtolnay/rust-toolchain@stable
19+
- uses: Swatinem/rust-cache@v2
20+
- run: cargo check
21+
22+
check-blocking:
23+
name: Check (blocking)
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
- uses: dtolnay/rust-toolchain@stable
28+
- uses: Swatinem/rust-cache@v2
29+
- run: cargo check --features blocking --no-default-features --features native-tls
30+
31+
test:
32+
name: Tests
33+
runs-on: ubuntu-latest
34+
steps:
35+
- uses: actions/checkout@v4
36+
- uses: dtolnay/rust-toolchain@stable
37+
- uses: Swatinem/rust-cache@v2
38+
- run: cargo test
39+
40+
fmt:
41+
name: Format
42+
runs-on: ubuntu-latest
43+
steps:
44+
- uses: actions/checkout@v4
45+
- uses: dtolnay/rust-toolchain@stable
46+
with:
47+
components: rustfmt
48+
- run: cargo fmt --all --check
49+
50+
clippy:
51+
name: Clippy
52+
runs-on: ubuntu-latest
53+
steps:
54+
- uses: actions/checkout@v4
55+
- uses: dtolnay/rust-toolchain@stable
56+
with:
57+
components: clippy
58+
- uses: Swatinem/rust-cache@v2
59+
- run: cargo clippy --all-targets -- -D warnings

.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Build artifacts
2+
/target/
3+
4+
# Cargo lock (library crate)
5+
Cargo.lock
6+
7+
# Editor/IDE
8+
.idea/
9+
.vscode/
10+
*.swp
11+
*.swo
12+
*~
13+
14+
# OS files
15+
.DS_Store
16+
Thumbs.db
17+
18+
# Environment
19+
.env

Cargo.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "jetemail"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "Rust SDK for the JetEmail API"
6+
license = "MIT"
7+
repository = "https://github.com/jetemail/jetemail-rust"
8+
keywords = ["email", "jetemail", "api"]
9+
categories = ["api-bindings", "email"]
10+
11+
[dependencies]
12+
reqwest = { version = "0.13", default-features = false, features = ["json"] }
13+
serde = { version = "1", features = ["derive"] }
14+
serde_json = "1"
15+
thiserror = "2"
16+
maybe-async = "0.2"
17+
base64 = "0.22"
18+
19+
[dev-dependencies]
20+
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
21+
22+
[features]
23+
default = ["native-tls"]
24+
blocking = ["reqwest/blocking", "maybe-async/is_sync"]
25+
native-tls = ["reqwest/native-tls"]
26+
rustls = ["reqwest/rustls"]

README.md

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,141 @@
1-
# jetemail-rust
2-
JetEmail Rust SDK
1+
# JetEmail Rust SDK
2+
3+
The official Rust SDK for the [JetEmail](https://jetemail.com) email API.
4+
5+
## Installation
6+
7+
Add to your `Cargo.toml`:
8+
9+
```toml
10+
[dependencies]
11+
jetemail = "0.1"
12+
```
13+
14+
## Usage
15+
16+
### Send an email
17+
18+
```rust
19+
use jetemail::{CreateEmailOptions, JetEmail};
20+
21+
#[tokio::main]
22+
async fn main() -> jetemail::Result<()> {
23+
let client = JetEmail::new("je_your_api_key");
24+
25+
let email = CreateEmailOptions::new(
26+
"you@example.com",
27+
"recipient@example.com",
28+
"Hello from JetEmail!",
29+
)
30+
.with_html("<h1>Hello World!</h1>");
31+
32+
let response = client.emails.send(email).await?;
33+
println!("Email sent! ID: {}", response.id);
34+
35+
Ok(())
36+
}
37+
```
38+
39+
### Send to multiple recipients
40+
41+
```rust
42+
let email = CreateEmailOptions::new(
43+
"you@example.com",
44+
vec!["alice@example.com".into(), "bob@example.com".into()],
45+
"Hello everyone!",
46+
)
47+
.with_html("<p>Hi there!</p>")
48+
.with_cc("cc@example.com")
49+
.with_bcc("bcc@example.com")
50+
.with_reply_to("reply@example.com");
51+
```
52+
53+
### Send with attachments
54+
55+
```rust
56+
use jetemail::Attachment;
57+
58+
let email = CreateEmailOptions::new(
59+
"you@example.com",
60+
"recipient@example.com",
61+
"Email with attachment",
62+
)
63+
.with_html("<p>See attached.</p>")
64+
.with_attachment(Attachment::from_content(b"file contents", "file.txt"))
65+
.with_attachment(Attachment::from_path("./report.pdf")?);
66+
```
67+
68+
### Send batch emails
69+
70+
Send up to 100 emails in a single API call:
71+
72+
```rust
73+
let emails = vec![
74+
CreateEmailOptions::new("you@example.com", "alice@example.com", "Hello Alice!")
75+
.with_html("<p>Hi Alice!</p>"),
76+
CreateEmailOptions::new("you@example.com", "bob@example.com", "Hello Bob!")
77+
.with_html("<p>Hi Bob!</p>"),
78+
];
79+
80+
let response = client.batch.send(emails).await?;
81+
```
82+
83+
### Custom headers
84+
85+
```rust
86+
let email = CreateEmailOptions::new(
87+
"you@example.com",
88+
"recipient@example.com",
89+
"With custom headers",
90+
)
91+
.with_html("<p>Hello!</p>")
92+
.with_header("X-Custom-Header", "custom-value");
93+
```
94+
95+
## Configuration
96+
97+
### Environment variable
98+
99+
```rust
100+
// Uses JETEMAIL_API_KEY environment variable
101+
let client = JetEmail::default();
102+
```
103+
104+
### Advanced configuration
105+
106+
Use `ConfigBuilder` for full control over the client. This lets you override the API base URL (useful for testing against a local or staging server), set a custom user agent, or provide your own `reqwest` client with custom timeouts or proxy settings.
107+
108+
```rust
109+
use jetemail::ConfigBuilder;
110+
111+
let config = ConfigBuilder::new("je_your_api_key")
112+
.base_url("https://staging-api.jetemail.com") // default: https://api.jetemail.com
113+
.user_agent("my-app/1.0")
114+
.client(
115+
reqwest::Client::builder()
116+
.timeout(std::time::Duration::from_secs(30))
117+
.build()
118+
.unwrap(),
119+
)
120+
.build();
121+
122+
let client = JetEmail::with_config(config);
123+
```
124+
125+
## Blocking Usage
126+
127+
Enable the `blocking` feature for synchronous usage:
128+
129+
```toml
130+
[dependencies]
131+
jetemail = { version = "0.1", features = ["blocking"] }
132+
```
133+
134+
```rust
135+
let client = JetEmail::new("je_your_api_key");
136+
let response = client.emails.send(email)?;
137+
```
138+
139+
## License
140+
141+
MIT

examples/send_batch.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use jetemail::{CreateEmailOptions, JetEmail};
2+
3+
#[tokio::main]
4+
async fn main() -> jetemail::Result<()> {
5+
let client = JetEmail::new("je_your_api_key");
6+
7+
let emails = vec![
8+
CreateEmailOptions::new("you@example.com", "alice@example.com", "Hello Alice!")
9+
.with_html("<p>Hi Alice!</p>"),
10+
CreateEmailOptions::new("you@example.com", "bob@example.com", "Hello Bob!")
11+
.with_html("<p>Hi Bob!</p>"),
12+
];
13+
14+
let response = client.batch.send(emails).await?;
15+
for email in &response.data {
16+
println!("Sent email ID: {}", email.id);
17+
}
18+
19+
Ok(())
20+
}

examples/send_email.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use jetemail::{CreateEmailOptions, JetEmail};
2+
3+
#[tokio::main]
4+
async fn main() -> jetemail::Result<()> {
5+
let client = JetEmail::new("je_your_api_key");
6+
7+
let email = CreateEmailOptions::new(
8+
"you@example.com",
9+
"recipient@example.com",
10+
"Hello from JetEmail!",
11+
)
12+
.with_html("<h1>Hello World!</h1><p>Sent with JetEmail Rust SDK.</p>");
13+
14+
let response = client.emails.send(email).await?;
15+
println!("Email sent! ID: {}", response.id);
16+
17+
Ok(())
18+
}

examples/with_attachments.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use jetemail::{Attachment, CreateEmailOptions, JetEmail};
2+
3+
#[tokio::main]
4+
async fn main() -> jetemail::Result<()> {
5+
let client = JetEmail::new("je_your_api_key");
6+
7+
let email = CreateEmailOptions::new(
8+
"you@example.com",
9+
"recipient@example.com",
10+
"Email with attachment",
11+
)
12+
.with_html("<p>Please see the attached file.</p>")
13+
.with_attachment(Attachment::from_content(b"Hello, World!", "hello.txt"))
14+
.with_cc("cc@example.com")
15+
.with_reply_to("reply@example.com");
16+
17+
let response = client.emails.send(email).await?;
18+
println!("Email sent! ID: {}", response.id);
19+
20+
Ok(())
21+
}

src/batch.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use std::sync::Arc;
2+
3+
use serde::Deserialize;
4+
5+
use crate::config::Config;
6+
use crate::emails::{CreateEmailOptions, CreateEmailResponse};
7+
use crate::error::Result;
8+
9+
/// Response from sending a batch of emails.
10+
#[derive(Clone, Debug, Deserialize)]
11+
pub struct BatchEmailResponse {
12+
/// The responses for each email in the batch.
13+
pub data: Vec<CreateEmailResponse>,
14+
}
15+
16+
/// Service for sending batch emails via the JetEmail API.
17+
#[derive(Clone, Debug)]
18+
pub struct BatchSvc(pub(crate) Arc<Config>);
19+
20+
impl BatchSvc {
21+
/// Send a batch of emails (up to 100).
22+
#[maybe_async::maybe_async]
23+
pub async fn send(&self, emails: Vec<CreateEmailOptions>) -> Result<BatchEmailResponse> {
24+
if emails.is_empty() {
25+
return Err(crate::error::Error::JetEmail {
26+
message: "Batch must contain at least one email".into(),
27+
status_code: 0,
28+
response: None,
29+
});
30+
}
31+
if emails.len() > 100 {
32+
return Err(crate::error::Error::JetEmail {
33+
message: "Batch cannot contain more than 100 emails".into(),
34+
status_code: 0,
35+
response: None,
36+
});
37+
}
38+
39+
let body = serde_json::json!({ "emails": emails });
40+
let response = self
41+
.0
42+
.send(reqwest::Method::POST, "/email-batch", Some(body))
43+
.await?;
44+
serde_json::from_value(response).map_err(|e| crate::error::Error::Parse(e.to_string()))
45+
}
46+
}

0 commit comments

Comments
 (0)