attempt at fixing confirmation emails by setting email headers, link for manual confirmation email and cut next release #29

Merged
ben merged 8 commits from ben/Jobboerse:main into main 2022-06-10 15:36:51 +02:00
4 changed files with 94 additions and 55 deletions
Showing only changes of commit 8e84eee376 - Show all commits

set UserAgent and Auto-Submitted header in E-Mails

Bennet Bleßmann 2022-06-10 14:25:11 +02:00 committed by Bennet Bleßmann
Signed by: ben
GPG key ID: 3BE1A1A3CBC3CF99

View file

@ -5,7 +5,7 @@ banner = "Hinweis: Die Jobbörse wird aktuall noch evaluiert und befindet sich n
type = 'Development'
[email]
from = "jobs@localhost"
from = "Test-Jobbörse <jobs@localhost>"
subject = "Test"
[[footer_links]]

85
src/email.rs Normal file
View file

@ -0,0 +1,85 @@
use crate::job_offers::error::EmailError;
use crate::server_config::EmailConfig;
use crate::template;
use crate::util::SerializableUrl;
use handlebars::Handlebars;
use lettre::message::header::{Header, HeaderName, HeaderValue};
use lettre::message::{Mailbox, SinglePart};
use lettre::{Address, AsyncTransport};
use log::warn;
#[derive(serde::Serialize)]
pub(crate) struct EmailData {
pub(crate) confirmation_link: SerializableUrl,
}
pub(crate) async fn send_confirmation_email(
hb: &Handlebars<'_>,
contact_address: Address,
email_config: &EmailConfig,
email_data: &EmailData,
) -> actix_web::Result<(), EmailError> {
let to_mailbox = Mailbox::new(None, contact_address);
let email_body = hb.render(template::EMAIL_PLAIN, &email_data)?;
#[derive(Clone, Debug)]
struct AutoGeneratedHeader(String);
impl Default for AutoGeneratedHeader {
fn default() -> Self {
Self("auto-generated".into())
}
}
impl Header for AutoGeneratedHeader {
fn name() -> HeaderName {
HeaderName::new_from_ascii_str("Auto-Submitted")
}
fn parse(s: &str) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
Ok(Self(s.into()))
}
fn display(&self) -> HeaderValue {
HeaderValue::new(Self::name(), self.0.clone())
}
}
let ua_header =
lettre::message::header::UserAgent::from(String::from("fs-infmath-jobboerse via sendmail"));
let message = lettre::Message::builder()
.from(email_config.from.to_owned())
.to(to_mailbox)
.subject(&email_config.subject)
.header(ua_header.clone())
.header(AutoGeneratedHeader::default())
.singlepart(SinglePart::plain(email_body))?;
lettre::AsyncSendmailTransport::new().send(message).await?;
let message = lettre::Message::builder()
.from(email_config.from.to_owned())
.to(email_config.from.to_owned())
.subject(&email_config.subject)
.header(ua_header)
.header(AutoGeneratedHeader::default())
.singlepart(SinglePart::plain(
"Automatischer Hinweis: Eine neue Stellenausschreibung wurde zur Jobbörse eingereicht!"
.to_owned(),
));
match message {
Ok(msg) => {
dbg!(&msg);
if let Err(err) = lettre::AsyncSendmailTransport::new().send(msg).await {
warn!("Failed to send remainder {}", err);
}
}
Err(err) => {
warn!("Failed to construct reminder {}", err)
}
}
Ok(())
}

View file

@ -19,6 +19,7 @@ use job_offers::lease::SubmissionLimiter;
use route::error_handler;
mod auth;
mod email;
mod error;
mod job_offers;
mod route;

View file

@ -7,18 +7,17 @@ use actix_session::Session;
use actix_web::{get, post, web, HttpRequest, HttpResponse, Result};
use futures_util::StreamExt;
use handlebars::Handlebars;
use lettre::message::{Mailbox, SinglePart};
use lettre::{Address, AsyncTransport};
use lettre::Address;
use log::{debug, error, warn};
use rand::distributions::DistString;
use serde::Serialize;
use serde_json::json;
use tempfile::NamedTempFile;
use url::Url;
use crate::auth::User;
use crate::email::EmailData;
use crate::error::{LoginRequired, PresentationError};
use crate::job_offers::error::{EmailError, SaveResponseError};
use crate::job_offers::error::SaveResponseError;
use crate::job_offers::lease::SubmissionLimiter;
use crate::job_offers::{
Attachment, ConfirmationStatus, JobOffer, JobOfferStatus, JobOffers, Link, MutBorrowedJobOffer,
@ -28,9 +27,9 @@ use crate::route::form_constants::{self, UploadLimits};
use crate::route::job_offer::confirmation::JOBOFFER_CONFIRM_ROUTE;
use crate::route::job_offer::error::{FormProcessingError, SubmissionResponseError};
use crate::route::HTML_CONTENT;
use crate::server_config::{EmailConfig, ServerConfig};
use crate::template;
use crate::server_config::ServerConfig;
use crate::util::{parse_date, parse_datetime, process_links, process_new_attachments};
use crate::{email, template};
use multipart_helper::{multi_field, multi_file, once_field};
pub(crate) const JOBOFFER_CREATION_ROUTE: &str = "create_offer";
@ -251,12 +250,12 @@ pub(crate) async fn create_job_offer<'data, 'config>(
if let Some(email_config) = &config.config.email {
let confirm_url = req.url_for(JOBOFFER_CONFIRM_ROUTE, &[created_offer.id(), &token])?;
send_confirmation_email(
email::send_confirmation_email(
hb,
job_offer_form.contact,
email_config,
&EmailData {
confirmation_link: confirm_url,
confirmation_link: confirm_url.into(),
},
)
.await?;
@ -266,52 +265,6 @@ pub(crate) async fn create_job_offer<'data, 'config>(
Ok(created_offer)
}
#[derive(serde::Serialize)]
struct EmailData {
#[serde(serialize_with = "crate::util::url_as_string")]
confirmation_link: Url,
}
async fn send_confirmation_email(
hb: &Handlebars<'_>,
contact_address: Address,
email_config: &EmailConfig,
email_data: &EmailData,
) -> Result<(), EmailError> {
let to_mailbox = Mailbox::new(None, contact_address);
let email_body = hb.render(template::EMAIL_PLAIN, &email_data)?;
let message = lettre::Message::builder()
.from(email_config.from.to_owned())
.to(to_mailbox)
.subject(&email_config.subject)
.singlepart(SinglePart::plain(email_body))?;
lettre::AsyncSendmailTransport::new().send(message).await?;
let message = lettre::Message::builder()
.from(email_config.from.to_owned())
.to(email_config.from.to_owned())
.subject(&email_config.subject)
.singlepart(SinglePart::plain(
"Automatischer Hinweis: Eine neue Stellenausschreibung wurde zur Jobbörse eingereicht!"
.to_owned(),
));
match message {
Ok(msg) => {
if let Err(err) = lettre::AsyncSendmailTransport::new().send(msg).await {
warn!("Failed to send remainder {}", err);
}
}
Err(err) => {
warn!("Failed to construct reminder {}", err)
}
}
Ok(())
}
impl JobOfferSubmitForm {
/// Convert the Multipart struct representing multipart form-data
/// into structured form data