You've already forked AstralRinth
forked from didirus/AstralRinth
Custom Emails (#4526)
* Dynamic email template * Set lower cache expiry for templates * Custom email route * Fix subject line on custom emails * chore: query cache, clippy, fmt * Bugfixes * Key-based caching on custom emails * Sequentially process emails prone to causing cache stampede * Fill variables in dynamic body + subject line * Update apps/labrinth/src/queue/email/templates.rs Co-authored-by: aecsocket <aecsocket@tutanota.com> Signed-off-by: François-Xavier Talbot <108630700+fetchfern@users.noreply.github.com> * Update apps/labrinth/src/queue/email/templates.rs Co-authored-by: aecsocket <aecsocket@tutanota.com> Signed-off-by: François-Xavier Talbot <108630700+fetchfern@users.noreply.github.com> --------- Signed-off-by: François-Xavier Talbot <108630700+fetchfern@users.noreply.github.com> Co-authored-by: aecsocket <aecsocket@tutanota.com>
This commit is contained in:
committed by
GitHub
parent
aec49cff7c
commit
0c66fa3f12
@@ -4,7 +4,7 @@ use crate::database::models::notifications_deliveries_item::DBNotificationDelive
|
||||
use crate::database::models::notifications_template_item::NotificationTemplate;
|
||||
use crate::database::models::user_item::DBUser;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::notifications::NotificationBody;
|
||||
use crate::models::notifications::{NotificationBody, NotificationType};
|
||||
use crate::models::v3::notifications::{
|
||||
NotificationChannel, NotificationDeliveryStatus,
|
||||
};
|
||||
@@ -20,6 +20,7 @@ use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::Mutex as TokioMutex;
|
||||
use tokio::sync::Semaphore;
|
||||
use tracing::{error, info, instrument, warn};
|
||||
|
||||
const EMAIL_RETRY_DELAY_SECONDS: i64 = 10;
|
||||
@@ -187,13 +188,19 @@ impl EmailQueue {
|
||||
|
||||
// For all notifications we collected, fill out the template
|
||||
// and send it via SMTP in parallel.
|
||||
|
||||
let mut futures = FuturesUnordered::new();
|
||||
|
||||
// Some email notifications should still be processed sequentially. This is to avoid cache stampede in the
|
||||
// case that processing the email can be heavy. For example, custom emails always make a POST request to modrinth.com,
|
||||
// which, while not necessarily slow, is subject to heavy rate limiting.
|
||||
let sequential_processing = Arc::new(Semaphore::new(1));
|
||||
|
||||
for notification in notifications {
|
||||
let this = self.clone();
|
||||
let transport = Arc::clone(&transport);
|
||||
|
||||
let seq = Arc::clone(&sequential_processing);
|
||||
|
||||
futures.push(async move {
|
||||
let mut txn = this.pg.begin().await?;
|
||||
|
||||
@@ -214,15 +221,35 @@ impl EmailQueue {
|
||||
));
|
||||
};
|
||||
|
||||
this.send_one_with_transport(
|
||||
&mut txn,
|
||||
transport,
|
||||
notification.body,
|
||||
notification.user_id,
|
||||
mailbox,
|
||||
)
|
||||
.await
|
||||
.map(|status| (notification.id, status))
|
||||
// For the cache stampede reasons mentioned above, we process custom emails exclusively sequentially.
|
||||
// This could cause unnecessary slowness if we're sending a lot of custom emails with the same key in one go,
|
||||
// and the cache is already populated (thus the sequential processing would not be needed).
|
||||
let maybe_permit = if notification.body.notification_type()
|
||||
== NotificationType::Custom
|
||||
{
|
||||
Some(
|
||||
seq.acquire()
|
||||
.await
|
||||
.expect("Semaphore should never be closed"),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let result = this
|
||||
.send_one_with_transport(
|
||||
&mut txn,
|
||||
transport,
|
||||
notification.body,
|
||||
notification.user_id,
|
||||
mailbox,
|
||||
)
|
||||
.await
|
||||
.map(|status| (notification.id, status));
|
||||
|
||||
drop(maybe_permit);
|
||||
|
||||
result
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user