refactor: use new mailer struct
This commit is contained in:
parent
760e19522b
commit
8b470f2d4f
@ -1,11 +1,11 @@
|
||||
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
|
||||
use lettre::{SmtpTransport, Transport};
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
mail::Mailer,
|
||||
models::{Function, Registration, Role, User},
|
||||
utils::{email, ApplicationError},
|
||||
utils::ApplicationError,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -22,7 +22,7 @@ pub async fn post_new(
|
||||
user: web::ReqData<User>,
|
||||
pool: web::Data<PgPool>,
|
||||
form: web::Form<NewUserForm>,
|
||||
mailer: web::Data<SmtpTransport>,
|
||||
mailer: web::Data<Mailer>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
if user.role != Role::AreaManager && user.role != Role::Admin {
|
||||
return Err(ApplicationError::Unauthorized);
|
||||
@ -49,9 +49,7 @@ pub async fn post_new(
|
||||
|
||||
let registration = Registration::insert_new_for_user(pool.get_ref(), id).await?;
|
||||
let new_user = User::read_by_id(pool.get_ref(), id).await?.unwrap();
|
||||
let message = email::build_registration_message(&new_user, ®istration.token)?;
|
||||
|
||||
mailer.send(&message)?;
|
||||
mailer.send_registration_mail(&new_user, ®istration.token)?;
|
||||
|
||||
Ok(HttpResponse::Found()
|
||||
.insert_header((LOCATION, "/users"))
|
||||
|
@ -1,12 +1,12 @@
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use lettre::{SmtpTransport, Transport};
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
endpoints::user::handle_password_change_request,
|
||||
mail::Mailer,
|
||||
models::{PasswordReset, User},
|
||||
utils::{email, ApplicationError},
|
||||
utils::ApplicationError,
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@ -22,7 +22,7 @@ struct ResetPasswordForm {
|
||||
async fn post(
|
||||
form: web::Form<ResetPasswordForm>,
|
||||
pool: web::Data<PgPool>,
|
||||
mailer: web::Data<SmtpTransport>,
|
||||
mailer: web::Data<Mailer>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
if form.email.is_some()
|
||||
&& form.token.is_none()
|
||||
@ -31,10 +31,7 @@ async fn post(
|
||||
{
|
||||
if let Ok(user) = User::read_for_login(pool.get_ref(), form.email.as_ref().unwrap()).await {
|
||||
let reset = PasswordReset::insert_new_for_user(pool.get_ref(), user.id).await?;
|
||||
|
||||
let message = email::build_forgot_password_message(&user, &reset.token);
|
||||
|
||||
mailer.send(&message)?;
|
||||
mailer.send_forgot_password_mail(&user, &reset.token)?;
|
||||
}
|
||||
|
||||
return Ok(HttpResponse::Ok().body("E-Mail versandt!"));
|
||||
|
@ -0,0 +1,96 @@
|
||||
use lettre::{
|
||||
message::{Mailbox, MultiPart, SinglePart},
|
||||
Message, Transport,
|
||||
};
|
||||
use rinja::Template;
|
||||
|
||||
use crate::{models::User, utils::ApplicationError};
|
||||
|
||||
use super::Mailer;
|
||||
|
||||
impl Mailer {
|
||||
pub fn send_forgot_password_mail(
|
||||
&self,
|
||||
user: &User,
|
||||
token: &str,
|
||||
) -> Result<(), ApplicationError> {
|
||||
let message = build(&self.hostname, &user.name, &user.email, token)?;
|
||||
self.transport.send(&message)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "emails/forgot_password.txt")]
|
||||
struct ForgotPasswordMailTemplatePlain<'a> {
|
||||
name: &'a str,
|
||||
reset_url: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "emails/forgot_password.html")]
|
||||
struct ForgotPasswordMailTemplateHtml<'a> {
|
||||
name: &'a str,
|
||||
reset_url: &'a str,
|
||||
}
|
||||
|
||||
fn build(
|
||||
hostname: &str,
|
||||
name: &str,
|
||||
email: &str,
|
||||
token: &str,
|
||||
) -> Result<Message, ApplicationError> {
|
||||
let reset_url = format!("https://{hostname}/reset-password?token={token}");
|
||||
|
||||
let plain = ForgotPasswordMailTemplatePlain {
|
||||
name,
|
||||
reset_url: &reset_url,
|
||||
}
|
||||
.to_string();
|
||||
|
||||
let html = ForgotPasswordMailTemplateHtml {
|
||||
name,
|
||||
reset_url: &reset_url,
|
||||
}
|
||||
.to_string();
|
||||
|
||||
let message = Message::builder()
|
||||
.from("noreply <noreply@brasiwa-leipzig.de>".parse()?)
|
||||
.reply_to("noreply <noreply@brasiwa-leipzig.de>".parse()?)
|
||||
.to(Mailbox::new(Some(name.to_string()), email.parse()?))
|
||||
.subject("Brass: Zurücksetzen des Passworts angefordert")
|
||||
.multipart(
|
||||
MultiPart::alternative()
|
||||
.singlepart(SinglePart::plain(plain))
|
||||
.singlepart(SinglePart::html(html)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::utils::test_helper::assert_mail_snapshot;
|
||||
use lettre::transport::stub::StubTransport;
|
||||
|
||||
#[test]
|
||||
fn build_mail_snapshot() {
|
||||
let message = build(
|
||||
"brasiwa-leipzig.de",
|
||||
"Max Mustermann",
|
||||
"max@example.com",
|
||||
"123456789",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sender = StubTransport::new_ok();
|
||||
sender.send(&message).unwrap();
|
||||
let messages = sender.messages();
|
||||
let (_, sent_message) = messages.first().unwrap();
|
||||
|
||||
assert_mail_snapshot!(sent_message);
|
||||
}
|
||||
}
|
@ -11,14 +11,18 @@ use crate::utils::ApplicationError;
|
||||
|
||||
mod forgot_password;
|
||||
mod registration;
|
||||
mod testmail;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Mailer {
|
||||
transport: Transports,
|
||||
hostname: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Transports {
|
||||
SmtpTransport(SmtpTransport),
|
||||
#[allow(unused)]
|
||||
StubTransport(StubTransport),
|
||||
}
|
||||
|
||||
@ -75,3 +79,14 @@ impl Mailer {
|
||||
Ok(mailer)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
impl Mailer {
|
||||
pub fn new_stub() -> Self {
|
||||
Mailer {
|
||||
transport: Transports::StubTransport(StubTransport::new_ok()),
|
||||
hostname: String::from("testhostname")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use lettre::{
|
||||
message::{header::ContentType, Mailbox, MultiPart, SinglePart},
|
||||
message::{Mailbox, MultiPart, SinglePart},
|
||||
Message, Transport,
|
||||
};
|
||||
use rinja::Template;
|
||||
@ -62,23 +62,21 @@ fn build(
|
||||
.subject("Brass: Registrierung deines Accounts")
|
||||
.multipart(
|
||||
MultiPart::alternative()
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(ContentType::TEXT_PLAIN)
|
||||
.body(plain),
|
||||
)
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(ContentType::TEXT_HTML)
|
||||
.body(html),
|
||||
),
|
||||
.singlepart(SinglePart::plain(plain))
|
||||
.singlepart(SinglePart::html(html)),
|
||||
)?;
|
||||
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_mail_snapshot() {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::utils::test_helper::assert_mail_snapshot;
|
||||
use lettre::transport::stub::StubTransport;
|
||||
|
||||
#[test]
|
||||
fn build_mail_snapshot() {
|
||||
let message = build(
|
||||
"brasiwa-leipzig.de",
|
||||
"Max Mustermann",
|
||||
@ -87,10 +85,11 @@ fn build_mail_snapshot() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sender = lettre::transport::stub::StubTransport::new_ok();
|
||||
let sender = StubTransport::new_ok();
|
||||
sender.send(&message).unwrap();
|
||||
let messages = sender.messages();
|
||||
let (_, sent_message) = messages.first().unwrap();
|
||||
|
||||
crate::utils::test_helper::assert_mail_snapshot!(sent_message);
|
||||
assert_mail_snapshot!(sent_message);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
---
|
||||
source: web/src/mail/forgot_password.rs
|
||||
expression: sent_message
|
||||
snapshot_kind: text
|
||||
---
|
||||
From: noreply <noreply@brasiwa-leipzig.de>
|
||||
Reply-To: noreply <noreply@brasiwa-leipzig.de>
|
||||
To: "Max Mustermann" <max@example.com>
|
||||
Subject: Brass: =?utf-8?b?WnVyw7xja3NldHplbg==?= des Passworts angefordert
|
||||
MIME-Version: 1.0
|
||||
Date: Date
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="boundary"
|
||||
|
||||
--boundary
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
Hallo Max Mustermann,
|
||||
|
||||
du hast angefordert, dein Passwort zur=C3=BCckzusetzen. Kopiere daf=C3=BCr =
|
||||
folgenden Link in deinen Browser:
|
||||
|
||||
https://brasiwa-leipzig.de/reset-password?token=3D123456789
|
||||
|
||||
Bitte beachte, dass der Link nur 24 Stunden g=C3=BCltig ist. Solltest du ni=
|
||||
chts angefordert haben, dann musst du nichts weiter tun.
|
||||
|
||||
Viele Gr=C3=BC=C3=9Fe
|
||||
--boundary
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<p>Hallo Max Mustermann,</p>
|
||||
|
||||
<p>du hast angefordert, dein Passwort zur=C3=BCckzusetzen. Klicke daf=C3=BC=
|
||||
r <a href=3D"https://brasiwa-leipzig.de/reset-password?token=3D123456789" t=
|
||||
arget=3D"_blank">hier</a> oder kopiere folgenden Link in deinen Browser:</p>
|
||||
|
||||
<p>https://brasiwa-leipzig.de/reset-password?token=3D123456789</p>
|
||||
|
||||
<p>Bitte beachte, dass der Link <b>nur 24 Stunden g=C3=BCltig</b> ist. Soll=
|
||||
test du nichts angefordert haben, dann musst du nichts weiter tun.</p>
|
||||
|
||||
<p>Viele Gr=C3=BC=C3=9Fe</p>
|
||||
--boundary--
|
21
web/src/mail/testmail.rs
Normal file
21
web/src/mail/testmail.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use lettre::{message::SinglePart, Message, Transport};
|
||||
|
||||
use crate::utils::ApplicationError;
|
||||
|
||||
use super::Mailer;
|
||||
|
||||
impl Mailer {
|
||||
pub fn send_test_mail(&self, to: &str) -> Result<(), ApplicationError> {
|
||||
let message = Message::builder()
|
||||
.from("noreply <noreply@brasiwa-leipzig.de>".parse()?)
|
||||
.reply_to("noreply <noreply@brasiwa-leipzig.de>".parse()?)
|
||||
.to(to.parse()?)
|
||||
.subject("Brass: Test E-Mail")
|
||||
.singlepart(SinglePart::plain(
|
||||
"Testmail von Brass. E-Mail Versand funktioniert!".to_string(),
|
||||
))?;
|
||||
|
||||
self.transport.send(&message)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ use actix_web::dev::{ServiceFactory, ServiceRequest, ServiceResponse};
|
||||
use actix_web::{web, App, HttpServer};
|
||||
use actix_web_static_files::ResourceFiles;
|
||||
use brass_config::{get_env, load_config, Config};
|
||||
use lettre::Transport;
|
||||
use mail::Mailer;
|
||||
use sqlx::postgres::PgPool;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
@ -36,7 +36,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
let args = parse_args()?;
|
||||
|
||||
let pool = PgPool::connect(&config.database_url).await?;
|
||||
let mailer = utils::email::get_mailer(&config)?;
|
||||
let mailer = Mailer::new(&config)?;
|
||||
|
||||
handle_command(args.command, &pool, &mailer).await?;
|
||||
|
||||
@ -52,10 +52,10 @@ async fn main() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_app<T>(
|
||||
pub fn create_app(
|
||||
config: Config,
|
||||
pool: Pool<Postgres>,
|
||||
mailer: T,
|
||||
mailer: Mailer,
|
||||
) -> App<
|
||||
impl ServiceFactory<
|
||||
ServiceRequest,
|
||||
@ -64,7 +64,7 @@ pub fn create_app<T>(
|
||||
InitError = (),
|
||||
Error = actix_web::error::Error,
|
||||
>,
|
||||
> where T: Transport + 'static {
|
||||
> {
|
||||
let generated = generate();
|
||||
let secret_key = Key::from(config.secret_key.as_bytes());
|
||||
let store = SqlxPostgresqlSessionStore::from_pool(pool.clone().into());
|
||||
|
@ -1,114 +0,0 @@
|
||||
use std::env;
|
||||
|
||||
use brass_config::{Config, SmtpTlsType};
|
||||
use lettre::{
|
||||
message::{header::ContentType, Mailbox, MultiPart, SinglePart},
|
||||
transport::smtp::{authentication::Credentials, extension::ClientId},
|
||||
Message, SmtpTransport,
|
||||
};
|
||||
|
||||
use crate::models::User;
|
||||
|
||||
use super::ApplicationError;
|
||||
|
||||
pub fn get_mailer(config: &Config) -> anyhow::Result<SmtpTransport> {
|
||||
let mut builder = match config.smtp_tlstype {
|
||||
SmtpTlsType::StartTLS => SmtpTransport::starttls_relay(&config.smtp_server)?.port(config.smtp_port),
|
||||
SmtpTlsType::TLS => SmtpTransport::relay(&config.smtp_server)?.port(config.smtp_port),
|
||||
SmtpTlsType::NoTLS => SmtpTransport::builder_dangerous(&config.smtp_server).port(config.smtp_port),
|
||||
};
|
||||
|
||||
if let (Some(login), Some(password)) = (config.smtp_login.as_ref(), config.smtp_password.as_ref()) {
|
||||
builder = builder.credentials(Credentials::new(login.to_string(), password.to_string()));
|
||||
}
|
||||
|
||||
let mailer = builder
|
||||
.hello_name(ClientId::Domain(config.hostname.clone()))
|
||||
.build();
|
||||
|
||||
Ok(mailer)
|
||||
}
|
||||
|
||||
pub fn build_forgot_password_message(user: &User, token: &str) -> Message {
|
||||
let hostname = env::var("HOSTNAME").unwrap();
|
||||
let reset_url = format!("https://{hostname}/reset-password?token={token}");
|
||||
|
||||
let message = Message::builder()
|
||||
.from("noreply <noreply@brasiwa-leipzig.de>".parse().unwrap())
|
||||
.reply_to("noreply <noreply@brasiwa-leipzig.de>".parse().unwrap())
|
||||
.to(format!("{} <{}>", user.name, user.email).parse().unwrap())
|
||||
.subject("Brass: Zurücksetzen des Passworts angefordert")
|
||||
.multipart(
|
||||
MultiPart::alternative()
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(ContentType::TEXT_PLAIN)
|
||||
.body(format!(r##"Hallo {},
|
||||
|
||||
du hast angefordert, dein Passwort zurückzusetzen. Kopiere dafür folgenden Link in deinen Browser:
|
||||
|
||||
{reset_url}
|
||||
|
||||
Bitte beachte, dass der Link nur 24 Stunden gültig ist. Solltest du nichts angefordert haben, dann musst du nichts weiter tun.
|
||||
|
||||
Viele Grüße"##, user.name))
|
||||
)
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(ContentType::TEXT_HTML)
|
||||
.body(format!(r##"<p>Hallo {},</p>
|
||||
|
||||
<p>du hast angefordert, dein Passwort zurückzusetzen. Klicke dafür <a href="{reset_url}" target="_blank">hier</a> oder kopiere folgenden Link in deinen Browser:</p>
|
||||
|
||||
<p>{reset_url}</p>
|
||||
|
||||
<p>Bitte beachte, dass der Link <b>nur 24 Stunden gültig</b> ist. Solltest du nichts angefordert haben, dann musst du nichts weiter tun.</p>
|
||||
|
||||
<p>Viele Grüße</p>"##, user.name))
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
pub fn build_registration_message(user: &User, token: &str) -> Result<Message, ApplicationError> {
|
||||
let hostname = env::var("HOSTNAME")?;
|
||||
let register_url = format!("https://{hostname}/register?token={token}");
|
||||
|
||||
let message = Message::builder()
|
||||
.from("noreply <noreply@brasiwa-leipzig.de>".parse()?)
|
||||
.reply_to("noreply <noreply@brasiwa-leipzig.de>".parse()?)
|
||||
.to(Mailbox::new(Some(user.name.clone()), user.email.parse()?))
|
||||
.subject("Brass: Registrierung deines Accounts")
|
||||
.multipart(
|
||||
MultiPart::alternative()
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(ContentType::TEXT_PLAIN)
|
||||
.body(format!(r##"Hallo {},
|
||||
|
||||
dein Account für https:://{hostname} wurde erstellt. Du musst nur noch ein Passwort festlegen. Kopiere dafür folgenden Link in deinen Browser:
|
||||
|
||||
{register_url}
|
||||
|
||||
Bitte beachte, dass der Link nur 24 Stunden gültig ist.
|
||||
|
||||
Viele Grüße"##, user.name))
|
||||
)
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(ContentType::TEXT_HTML)
|
||||
.body(format!(r##"<p>Hallo {},</p>
|
||||
|
||||
<p>dein Account für <a href="https:://{hostname}" target="_blank">https://{hostname}</a> wurde erstellt. Du musst nur noch ein Passwort festlegen. Klicke dafür <a href="{register_url}" target="_blank">hier</a> oder kopiere folgenden Link in deinen Browser:</p>
|
||||
|
||||
<p>{register_url}</p>
|
||||
|
||||
<p>Bitte beachte, dass der Link <b>nur 24 Stunden gültig</b> ist.</p>
|
||||
|
||||
<p>Viele Grüße</p>"##, user.name))
|
||||
))
|
||||
?;
|
||||
|
||||
Ok(message)
|
||||
}
|
@ -4,13 +4,10 @@ use std::{
|
||||
process::exit,
|
||||
};
|
||||
|
||||
use lettre::{
|
||||
message::{header::ContentType, SinglePart},
|
||||
Message, SmtpTransport, Transport,
|
||||
};
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use crate::{
|
||||
mail::Mailer,
|
||||
models::{Function, Role, User},
|
||||
utils::auth::generate_salt_and_hash_plain_password,
|
||||
};
|
||||
@ -65,7 +62,7 @@ fn prompt(prompt: &str) -> anyhow::Result<String> {
|
||||
pub async fn handle_command(
|
||||
command: Option<Command>,
|
||||
pool: &Pool<Postgres>,
|
||||
mailer: &SmtpTransport,
|
||||
mailer: &Mailer,
|
||||
) -> anyhow::Result<()> {
|
||||
match command {
|
||||
Some(Command::Migrate) => {
|
||||
@ -101,19 +98,7 @@ pub async fn handle_command(
|
||||
exit(0);
|
||||
}
|
||||
Some(Command::TestMail(to)) => {
|
||||
let message = Message::builder()
|
||||
.from("noreply <noreply@brasiwa-leipzig.de>".parse().unwrap())
|
||||
.reply_to("noreply <noreply@brasiwa-leipzig.de>".parse().unwrap())
|
||||
.to(to.parse().unwrap())
|
||||
.subject("Brass: Test E-Mail")
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(ContentType::TEXT_HTML)
|
||||
.body("Testmail von Brass. E-Mail Versand funktioniert!".to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
match mailer.send(&message) {
|
||||
match mailer.send_test_mail(&to) {
|
||||
Ok(_) => println!("Successfully sent mail to {to}."),
|
||||
Err(e) => {
|
||||
if let Some(source) = e.source() {
|
||||
@ -123,7 +108,6 @@ pub async fn handle_command(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
None => (),
|
||||
|
@ -1,6 +1,5 @@
|
||||
mod application_error;
|
||||
pub mod auth;
|
||||
pub mod email;
|
||||
pub mod event_planning_template;
|
||||
pub mod manage_commands;
|
||||
pub mod password_help;
|
||||
|
@ -6,10 +6,9 @@ use actix_web::{
|
||||
dev::{Service, ServiceResponse},
|
||||
test::init_service,
|
||||
};
|
||||
use lettre::transport::stub::StubTransport;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
|
||||
use crate::create_app;
|
||||
use crate::{create_app, mail::Mailer};
|
||||
use brass_config::{load_config, Config, Environment};
|
||||
use regex::{Captures, Regex};
|
||||
use sqlx::{
|
||||
@ -33,7 +32,7 @@ impl DbTestContext {
|
||||
init_service(create_app(
|
||||
self.config.clone(),
|
||||
self.db_pool.clone(),
|
||||
StubTransport::new_ok(),
|
||||
Mailer::new_stub(),
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
9
web/templates/emails/forgot_password.html
Normal file
9
web/templates/emails/forgot_password.html
Normal file
@ -0,0 +1,9 @@
|
||||
<p>Hallo {{ name }},</p>
|
||||
|
||||
<p>du hast angefordert, dein Passwort zurückzusetzen. Klicke dafür <a href="{{ reset_url }}" target="_blank">hier</a> oder kopiere folgenden Link in deinen Browser:</p>
|
||||
|
||||
<p>{{ reset_url }}</p>
|
||||
|
||||
<p>Bitte beachte, dass der Link <b>nur 24 Stunden gültig</b> ist. Solltest du nichts angefordert haben, dann musst du nichts weiter tun.</p>
|
||||
|
||||
<p>Viele Grüße</p>
|
9
web/templates/emails/forgot_password.txt
Normal file
9
web/templates/emails/forgot_password.txt
Normal file
@ -0,0 +1,9 @@
|
||||
Hallo {{ name }},
|
||||
|
||||
du hast angefordert, dein Passwort zurückzusetzen. Kopiere dafür folgenden Link in deinen Browser:
|
||||
|
||||
{{ reset_url }}
|
||||
|
||||
Bitte beachte, dass der Link nur 24 Stunden gültig ist. Solltest du nichts angefordert haben, dann musst du nichts weiter tun.
|
||||
|
||||
Viele Grüße
|
Loading…
x
Reference in New Issue
Block a user