1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

Actually send emails

This commit is contained in:
Quentin Gliech
2022-01-19 18:00:45 +01:00
parent 2e12f43c0e
commit 93cbad34f5
13 changed files with 301 additions and 27 deletions

View File

@ -6,13 +6,15 @@ edition = "2021"
license = "Apache-2.0"
[dependencies]
anyhow = "1.0.52"
async-trait = "0.1.52"
tokio = { version = "1.15.0", features = ["macros"] }
mas-templates = { path = "../templates" }
anyhow = "1.0.52"
async-trait = "0.1.52"
mas-config = { path = "../config" }
tracing = "0.1.29"
[dependencies.lettre]
version = "0.10.0-rc.4"
default-features = false
features = ["tokio1-rustls-tls", "hostname", "builder", "tracing", "pool"]
features = ["tokio1-rustls-tls", "hostname", "builder", "tracing", "pool", "smtp-transport"]

View File

@ -12,24 +12,130 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::Arc;
use async_trait::async_trait;
use lettre::{
address::Envelope,
message::{Mailbox, MessageBuilder, MultiPart},
AsyncTransport, Message,
transport::smtp::{authentication::Credentials, AsyncSmtpTransport},
AsyncTransport, Message, Tokio1Executor,
};
use mas_config::{EmailSmtpMode, EmailTransportConfig};
use mas_templates::{EmailVerificationContext, Templates};
#[derive(Default, Clone)]
pub struct MailTransport {
inner: Arc<MailTransportInner>,
}
enum MailTransportInner {
Blackhole,
Smtp(AsyncSmtpTransport<Tokio1Executor>),
}
impl TryFrom<&EmailTransportConfig> for MailTransport {
type Error = anyhow::Error;
fn try_from(config: &EmailTransportConfig) -> Result<Self, Self::Error> {
let inner = match config {
EmailTransportConfig::Blackhole => MailTransportInner::Blackhole,
EmailTransportConfig::Smtp {
mode,
hostname,
credentials,
port,
} => {
let mut t = match mode {
EmailSmtpMode::Plain => {
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(hostname)
}
EmailSmtpMode::StartTls => {
AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(hostname)?
}
EmailSmtpMode::Tls => AsyncSmtpTransport::<Tokio1Executor>::relay(hostname)?,
};
if let Some(credentials) = credentials {
t = t.credentials(Credentials::new(
credentials.username.clone(),
credentials.password.clone(),
));
}
if let Some(port) = port {
t = t.port(*port);
}
MailTransportInner::Smtp(t.build())
}
};
let inner = Arc::new(inner);
Ok(Self { inner })
}
}
impl MailTransport {
pub async fn test_connection(&self) -> anyhow::Result<()> {
match self.inner.as_ref() {
MailTransportInner::Blackhole => {}
MailTransportInner::Smtp(t) => {
t.test_connection().await?;
}
}
Ok(())
}
}
impl Default for MailTransportInner {
fn default() -> Self {
Self::Blackhole
}
}
#[async_trait]
impl AsyncTransport for MailTransport {
type Ok = ();
type Error = anyhow::Error;
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
match self.inner.as_ref() {
MailTransportInner::Blackhole => {
tracing::warn!(
?envelope,
"An email was supposed to be sent but no email backend is configured"
);
}
MailTransportInner::Smtp(t) => {
t.send_raw(envelope, email).await?;
}
};
Ok(())
}
}
#[derive(Clone)]
pub struct Mailer {
templates: Templates,
transport: MailTransport,
from: Mailbox,
reply_to: Mailbox,
}
impl Mailer {
pub fn new<T>(templates: &Templates, from: Mailbox, reply_to: Mailbox) -> Self {
pub fn new(
templates: &Templates,
transport: &MailTransport,
from: &Mailbox,
reply_to: &Mailbox,
) -> Self {
Self {
templates: templates.clone(),
from,
reply_to,
transport: transport.clone(),
from: from.clone(),
reply_to: reply_to.clone(),
}
}
@ -56,23 +162,23 @@ impl Mailer {
let multipart = MultiPart::alternative_plain_html(plain, html);
let message = self.base_message().to(to).multipart(multipart)?;
let message = self
.base_message()
// TODO: template/localize this
.subject("Verify your email address")
.to(to)
.multipart(multipart)?;
Ok(message)
}
pub async fn send_verification_email<T>(
pub async fn send_verification_email(
&self,
transport: &T,
to: Mailbox,
context: &EmailVerificationContext,
) -> anyhow::Result<()>
where
T: AsyncTransport + Send + Sync,
T::Error: std::error::Error + Send + Sync + 'static,
{
) -> anyhow::Result<()> {
let message = self.prepare_verification_email(to, context).await?;
transport.send(message).await?;
self.transport.send(message).await?;
Ok(())
}
}