feat(mass-email): Added the ability to configure the Mass Email, we can now send BCC and we are less likely to be rate limited when not using bcc #4377

pull/4384/head
tidusjar 3 years ago
parent 69e8b5a7e2
commit ca655ae570

@ -38,9 +38,9 @@ namespace Ombi.Core.Tests.Senders
{
Body = "Test",
Subject = "Subject",
Users = new List<Store.Entities.OmbiUser>
Users = new List<OmbiUser>
{
new Store.Entities.OmbiUser
new OmbiUser
{
Id = "a"
}
@ -143,5 +143,86 @@ namespace Ombi.Core.Tests.Senders
_mocker.Verify<IEmailProvider>(x => x.SendAdHoc(It.IsAny<NotificationMessage>(), It.IsAny<EmailNotificationSettings>()), Times.Never);
}
[Test]
public async Task SendMassEmail_Bcc()
{
var model = new MassEmailModel
{
Body = "Test",
Subject = "Subject",
Bcc = true,
Users = new List<OmbiUser>
{
new OmbiUser
{
Id = "a"
},
new OmbiUser
{
Id = "b"
}
}
};
_mocker.Setup<OmbiUserManager, IQueryable<OmbiUser>>(x => x.Users).Returns(new List<OmbiUser>
{
new OmbiUser
{
Id = "a",
Email = "Test@test.com"
},
new OmbiUser
{
Id = "b",
Email = "b@test.com"
}
}.AsQueryable().BuildMock().Object);
var result = await _subject.SendMassEmail(model);
_mocker.Verify<IEmailProvider>(x => x.SendAdHoc(It.Is<NotificationMessage>(m => m.Subject == model.Subject
&& m.Message == model.Body
&& m.Other["bcc"] == "Test@test.com,b@test.com"), It.IsAny<EmailNotificationSettings>()), Times.Once);
}
[Test]
public async Task SendMassEmail_Bcc_NoEmails()
{
var model = new MassEmailModel
{
Body = "Test",
Subject = "Subject",
Bcc = true,
Users = new List<OmbiUser>
{
new OmbiUser
{
Id = "a"
},
new OmbiUser
{
Id = "b"
}
}
};
_mocker.Setup<OmbiUserManager, IQueryable<OmbiUser>>(x => x.Users).Returns(new List<OmbiUser>
{
new OmbiUser
{
Id = "a",
},
new OmbiUser
{
Id = "b",
}
}.AsQueryable().BuildMock().Object);
var result = await _subject.SendMassEmail(model);
_mocker.Verify<IEmailProvider>(x => x.SendAdHoc(It.IsAny<NotificationMessage>(), It.IsAny<EmailNotificationSettings>()), Times.Never);
}
}
}

@ -35,6 +35,8 @@ namespace Ombi.Core.Models
public string Subject { get; set; }
public string Body { get; set; }
public bool Bcc { get; set; }
public List<OmbiUser> Users { get; set; }
}
}

@ -25,7 +25,9 @@
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@ -64,6 +66,26 @@ namespace Ombi.Core.Senders
var customization = await _customizationService.GetSettingsAsync();
var email = await _emailService.GetSettingsAsync();
var messagesSent = new List<Task>();
if (model.Bcc)
{
await SendBccMails(model, customization, email, messagesSent);
}
else
{
await SendIndividualEmails(model, customization, email, messagesSent);
}
await Task.WhenAll(messagesSent);
return true;
}
private async Task SendBccMails(MassEmailModel model, CustomizationSettings customization, EmailNotificationSettings email, List<Task> messagesSent)
{
var resolver = new NotificationMessageResolver();
var curlys = new NotificationMessageCurlys();
var validUsers = new List<OmbiUser>();
foreach (var user in model.Users)
{
var fullUser = await _userManager.Users.FirstOrDefaultAsync(x => x.Id == user.Id);
@ -72,8 +94,43 @@ namespace Ombi.Core.Senders
_log.LogInformation("User {0} has no email, cannot send mass email to this user", fullUser.UserName);
continue;
}
validUsers.Add(fullUser);
}
if (!validUsers.Any())
{
return;
}
var firstUser = validUsers.FirstOrDefault();
var bccAddress = string.Join(',', validUsers.Select(x => x.Email));
curlys.Setup(firstUser, customization);
var template = new NotificationTemplates() { Message = model.Body, Subject = model.Subject };
var content = resolver.ParseMessage(template, curlys);
var msg = new NotificationMessage
{
Message = content.Message,
Subject = content.Subject,
Other = new Dictionary<string, string> { { "bcc", bccAddress } }
};
messagesSent.Add(_email.SendAdHoc(msg, email));
}
private async Task SendIndividualEmails(MassEmailModel model, CustomizationSettings customization, EmailNotificationSettings email, List<Task> messagesSent)
{
var resolver = new NotificationMessageResolver();
var curlys = new NotificationMessageCurlys();
foreach (var user in model.Users)
{
var fullUser = await _userManager.Users.FirstOrDefaultAsync(x => x.Id == user.Id);
if (!fullUser.Email.HasValue())
{
_log.LogInformation("User {0} has no email, cannot send mass email to this user", fullUser.UserName);
continue;
}
curlys.Setup(fullUser, customization);
var template = new NotificationTemplates() { Message = model.Body, Subject = model.Subject };
var content = resolver.ParseMessage(template, curlys);
@ -83,13 +140,19 @@ namespace Ombi.Core.Senders
To = fullUser.Email,
Subject = content.Subject
};
messagesSent.Add(_email.SendAdHoc(msg, email));
messagesSent.Add(DelayEmail(msg, email));
_log.LogInformation("Sent mass email to user {0} @ {1}", fullUser.UserName, fullUser.Email);
}
}
await Task.WhenAll(messagesSent);
return true;
/// <summary>
/// This will add a 2 second delay, this is to help with concurrent connection limits
/// <see href="https://github.com/Ombi-app/Ombi/issues/4377"/>
/// </summary>
private async Task DelayEmail(NotificationMessage msg, EmailNotificationSettings email)
{
await Task.Delay(2000);
await _email.SendAdHoc(msg, email);
}
}
}

@ -64,7 +64,20 @@ namespace Ombi.Notifications
MessageId = messageId
};
message.From.Add(new MailboxAddress(string.IsNullOrEmpty(settings.SenderName) ? settings.SenderAddress : settings.SenderName, settings.SenderAddress));
if (model.To.HasValue())
{
message.To.Add(new MailboxAddress(model.To, model.To));
}
// Check for BCC
if (model.Other.TryGetValue("bcc", out var bcc))
{
var bccList = bcc.Split(',', StringSplitOptions.RemoveEmptyEntries);
foreach (var item in bccList)
{
message.Bcc.Add(new MailboxAddress(item, item));
}
}
using (var client = new SmtpClient())
{

@ -10,13 +10,13 @@
"cSpell.words": [
"usermanagement"
],
"discord.enabled": true,
"conventionalCommits.scopes": [
"discover",
"request-limits",
"notifications",
"settings",
"user-management",
"newsletter"
"newsletter",
"mass-email"
]
}

@ -121,6 +121,7 @@ export interface IMassEmailModel {
subject: string;
body: string;
users: IUser[];
bcc: boolean;
}
export interface INotificationPreferences {

@ -3,7 +3,7 @@
<wiki></wiki>
<fieldset>
<legend>Mass Email</legend>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<input type="text" class="form-control form-control-custom " id="subject" name="subject" placeholder="Subject" [(ngModel)]="subject" [ngClass]="{'form-error': missingSubject}">
@ -11,7 +11,7 @@
</div>
<div class="form-group" >
<textarea rows="10" type="text" class="form-control-custom form-control " id="themeContent" name="themeContent" [(ngModel)]="message"></textarea>
<textarea rows="10" type="text" class="form-control-custom form-control" id="themeContent" name="themeContent" [(ngModel)]="message" placeholder="This supports HTML"></textarea>
</div>
<div class="form-group">
@ -20,7 +20,14 @@
<small>May appear differently on email clients</small>
<hr/>
<div [innerHTML]="message"></div>
<hr/>
</div>
<small>This will send out the Mass email BCC'ing all of the selected users rather than sending individual messages</small>
<div class="md-form-field">
<mat-slide-toggle [(ngModel)]="bcc">BCC</mat-slide-toggle>
</div>
<br>
<br>
<div class="form-group">
<div>
<button type="submit" id="save" (click)="send()" class="mat-focus-indicator btn-spacing mat-raised-button mat-button-base mat-accent">Send</button>
@ -29,22 +36,20 @@
</div>
<div class="col-md-6">
<!--Users Section-->
<label class="control-label">Recipients</label>
<label class="control-label">Recipients with Email Addresses</label>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="all" (click)="selectAllUsers()">
<label for="all">Select All</label>
<div class="md-form-field">
<mat-slide-toggle (change)="selectAllUsers($event)">Select All</mat-slide-toggle>
</div>
</div>
<div class="form-group" *ngFor="let u of users">
<div class="checkbox">
<input type="checkbox" id="user{{u.user.id}}" [(ngModel)]="u.selected">
<label for="user{{u.user.id}}">{{u.user.userName}}</label>
<div class="md-form-field">
<mat-slide-toggle id="user{{u.user.id}}" [(ngModel)]="u.selected">{{u.user.userName}} ({{u.user.emailAddress}})</mat-slide-toggle>
</div>
</div>
</div>
</div>
</fieldset>

@ -12,6 +12,7 @@ export class MassEmailComponent implements OnInit {
public users: IMassEmailUserModel[] = [];
public message: string;
public subject: string;
public bcc: boolean;
public missingSubject = false;
@ -26,17 +27,19 @@ export class MassEmailComponent implements OnInit {
public ngOnInit(): void {
this.identityService.getUsers().subscribe(x => {
x.forEach(u => {
if (u.emailAddress) {
this.users.push({
user: u,
selected: false,
});
}
});
});
this.settingsService.getEmailSettingsEnabled().subscribe(x => this.emailEnabled = x);
}
public selectAllUsers() {
this.users.forEach(u => u.selected = !u.selected);
public selectAllUsers(event: any) {
this.users.forEach(u => u.selected = event.checked);
}
public send() {
@ -44,10 +47,10 @@ export class MassEmailComponent implements OnInit {
this.missingSubject = true;
return;
}
if(!this.emailEnabled) {
this.notification.error("You have not yet setup your email notifications, do that first!");
return;
}
// if(!this.emailEnabled) {
// this.notification.error("You have not yet setup your email notifications, do that first!");
// return;
// }
this.missingSubject = false;
// Where(x => x.selected).Select(x => x.user)
const selectedUsers = this.users.filter(u => {
@ -63,6 +66,7 @@ export class MassEmailComponent implements OnInit {
users: selectedUsers,
subject: this.subject,
body: this.message,
bcc: this.bcc,
};
this.notification.info("Sending","Sending mass email... Please wait");
this.notificationMessageService.sendMassEmail(model).subscribe(x => {

Loading…
Cancel
Save