Skip to content

feat: email support using lettre #250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1d2849e
Initial commit of email.rs using lettre
trkelly23 Mar 15, 2025
252cebe
Initial commit of Add email support using lettre crate
trkelly23 Mar 15, 2025
fc1fb9f
Added the message printout if debug mode is enabled
trkelly23 Mar 16, 2025
d64980f
Added a test and deferred the extra headers and attachment features.
trkelly23 Mar 18, 2025
4edd6d0
Added tests for config and send_email(ignore).
trkelly23 Mar 19, 2025
dbbf50f
Fixed the email example and removed minor version from cargo.
trkelly23 Mar 19, 2025
0587340
chore(pre-commit.ci): auto fixes from pre-commit hooks
pre-commit-ci[bot] Mar 20, 2025
a1a25da
Fixed the email example and removed minor version from cargo.
trkelly23 Mar 15, 2025
84eb659
Merge branch 'feat-add-email-support' of https://github.com/trkelly23…
trkelly23 Mar 20, 2025
c964e45
chore(pre-commit.ci): auto fixes from pre-commit hooks
pre-commit-ci[bot] Mar 20, 2025
a13552e
Implemented a trait impl for the EmailBackend
trkelly23 Mar 20, 2025
6ac6e92
chore(pre-commit.ci): auto fixes from pre-commit hooks
pre-commit-ci[bot] Mar 20, 2025
d7ac14a
Refactor to insure multiple email backends could be added. Mocking a…
trkelly23 Mar 28, 2025
4ee5c6a
Pushing lock since there seems to be a conflict
trkelly23 Mar 28, 2025
49775cd
Merge branch 'master' into feat-add-email-support
trkelly23 Mar 28, 2025
553c2e9
chore(pre-commit.ci): auto fixes from pre-commit hooks
pre-commit-ci[bot] Mar 28, 2025
cb8093a
Adding the reworked smtp implementation and NOT working example for h…
trkelly23 May 8, 2025
749ef05
chore: cargo fmt
seqre May 15, 2025
3a042af
fix bootstrapper
seqre May 15, 2025
84c111d
chore: merge master
seqre May 15, 2025
ea8e2e0
chore(pre-commit.ci): auto fixes from pre-commit hooks
pre-commit-ci[bot] May 15, 2025
fa4f337
Merge branch 'master' into feat-add-email-support
m4tx May 15, 2025
6cc9cb6
Merge branch 'master' into feat-add-email-support
seqre May 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
389 changes: 227 additions & 162 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ members = [
"examples/json",
"examples/custom-task",
"examples/custom-error-pages",
"examples/send-email",
]
resolver = "2"

Expand Down Expand Up @@ -91,6 +92,7 @@ humantime = "2"
indexmap = "2"
insta = { version = "1", features = ["filters"] }
insta-cmd = "0.6"
lettre = { version = "0.11", features = ["smtp-transport", "builder", "native-tls"] }
mime_guess = { version = "2", default-features = false }
mockall = "0.13"
password-auth = { version = "1", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions cot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ http-body.workspace = true
http.workspace = true
humantime.workspace = true
indexmap.workspace = true
lettre.workspace = true
mime_guess.workspace = true
password-auth = { workspace = true, features = ["std", "argon2"] }
pin-project-lite.workspace = true
Expand Down
121 changes: 121 additions & 0 deletions cot/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ use derive_more::with_trait::{Debug, From};
use serde::{Deserialize, Serialize};
use subtle::ConstantTimeEq;

use crate::email;

/// The configuration for a project.
///
/// This is all the project-specific configuration data that can (and makes
Expand Down Expand Up @@ -211,6 +213,24 @@ pub struct ProjectConfig {
/// # Ok::<(), cot::Error>(())
/// ```
pub middlewares: MiddlewareConfig,
/// Configuration related to the email backend.
///
/// # Examples
///
/// ```
/// use cot::config::{EmailBackendConfig, ProjectConfig};
///
/// let config = ProjectConfig::from_toml(
/// r#"
/// [email_backend]
/// type = "none"
/// "#,
/// )?;
///
/// assert_eq!(config.email_backend, EmailBackendConfig::default());
/// # Ok::<(), cot::Error>(())
/// ```
pub email_backend: EmailBackendConfig,
}

const fn default_debug() -> bool {
Expand Down Expand Up @@ -313,6 +333,7 @@ impl ProjectConfigBuilder {
database: self.database.clone().unwrap_or_default(),
static_files: self.static_files.clone().unwrap_or_default(),
middlewares: self.middlewares.clone().unwrap_or_default(),
email_backend: self.email_backend.clone().unwrap_or_default(),
}
}
}
Expand Down Expand Up @@ -809,7 +830,104 @@ impl SessionMiddlewareConfigBuilder {
}
}
}
/// The type of email backend to use.
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum EmailBackendType {
/// No email backend.
#[default]
None,
/// SMTP email backend.
Smtp,
}
/// The configuration for the SMTP backend.
///
/// This is used as part of the [`EmailBackendConfig`] enum.
///
/// # Examples
///
/// ```
/// use cot::config::EmailBackendConfig;
///
/// let config = EmailBackendConfig::builder().build();
/// ```
#[derive(Debug, Default, Clone, PartialEq, Eq, Builder, Serialize, Deserialize)]
#[builder(build_fn(skip, error = std::convert::Infallible))]
#[serde(default)]
pub struct EmailBackendConfig {
/// The type of email backend to use.
/// Defaults to `None`.
#[builder(setter(into, strip_option), default)]
pub backend_type: EmailBackendType,
/// The SMTP server host address.
/// Defaults to "localhost".
#[builder(setter(into, strip_option), default)]
pub smtp_mode: email::SmtpTransportMode,
/// The SMTP server port.
/// Overwrites the default standard port when specified.
#[builder(setter(into, strip_option), default)]
pub port: Option<u16>,
/// The username for SMTP authentication.
#[builder(setter(into, strip_option), default)]
pub username: Option<String>,
/// The password for SMTP authentication.
#[builder(setter(into, strip_option), default)]
pub password: Option<String>,
/// The timeout duration for the SMTP connection.
#[builder(setter(into, strip_option), default)]
pub timeout: Option<Duration>,
}

impl EmailBackendConfig {
/// Create a new [`EmailBackendConfigBuilder`] to build a
/// [`EmailBackendConfig`].
///
/// # Examples
///
/// ```
/// use cot::config::EmailBackendConfig;
///
/// let config = EmailBackendConfig::builder().build();
/// ```
#[must_use]
pub fn builder() -> EmailBackendConfigBuilder {
EmailBackendConfigBuilder::default()
}
}
impl EmailBackendConfigBuilder {
/// Builds the email configuration.
///
/// # Examples
///
/// ```
/// use cot::config::EmailBackendConfig;
///
/// let config = EmailBackendConfig::builder().build();
/// ```
#[must_use]
pub fn build(&self) -> EmailBackendConfig {
match self.backend_type.clone().unwrap_or(EmailBackendType::None) {
EmailBackendType::Smtp => EmailBackendConfig {
backend_type: EmailBackendType::Smtp,
smtp_mode: self
.smtp_mode
.clone()
.unwrap_or(email::SmtpTransportMode::Localhost),
port: self.port.unwrap_or_default(),
username: self.username.clone().unwrap_or_default(),
password: self.password.clone().unwrap_or_default(),
timeout: self.timeout.unwrap_or_default(),
},
EmailBackendType::None => EmailBackendConfig {
backend_type: EmailBackendType::None,
smtp_mode: email::SmtpTransportMode::Localhost,
port: None,
username: None,
password: None,
timeout: None,
},
}
}
}
/// A secret key.
///
/// This is a wrapper over a byte array, which is used to store a cryptographic
Expand Down Expand Up @@ -1024,6 +1142,8 @@ mod tests {
live_reload.enabled = true
[middlewares.session]
secure = false
[email_backend]
type = "none"
"#;

let config = ProjectConfig::from_toml(toml_content).unwrap();
Expand All @@ -1046,6 +1166,7 @@ mod tests {
);
assert!(config.middlewares.live_reload.enabled);
assert!(!config.middlewares.session.secure);
assert_eq!(config.email_backend.backend_type, EmailBackendType::None);
}

#[test]
Expand Down
Loading
Loading