Skip to content

Commit 4539ccd

Browse files
UrsMetzleider
andauthored
Send mails in chunks (#1509)
* Send mails to groups in chunks * Send mails to all members in chunks * Inline variable * Use const * Deduplicate recipients in chunked delivery * Remove unnecessary test setup * Use async/await * Ensure correct handling of duplicated recipients * Allow to take sender from Message * fix after merge * solution for the member question * final adaptions --------- Co-authored-by: leider <derleider@web.de>
1 parent d3f2db2 commit 4539ccd

File tree

7 files changed

+411
-42
lines changed

7 files changed

+411
-42
lines changed

config-examples/mailsender-config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
"sender-name": "Softwerkskammer Benachrichtigungen",
1111
"sender-address": "info@my-domain.org",
1212
"include-footer": false,
13-
"doNotSendMails": "insert email address here for local development as replacement for receivers"
13+
"doNotSendMails": "insert email address here for local development as replacement for receivers",
14+
"maxMailSendingChunkSize": 3
1415
}

softwerkskammer/lib/commons/statusmessage.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,8 @@ module.exports = {
3838
successMessage: function successMessage(title, text, additionalArguments) {
3939
return statusMessage("alert-success", title, text, additionalArguments);
4040
},
41+
42+
isErrorMessage: function isErrorMessage(statusmessage) {
43+
return statusmessage.contents().type === "alert-danger";
44+
},
4145
};

softwerkskammer/lib/mailsender/mailsenderService.js

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use strict";
22
const { DateTime } = require("luxon");
33
const conf = require("simple-configure");
4+
const R = require("ramda");
45
const logger = require("winston").loggers.get("application");
56

67
const groupsService = require("../groups/groupsService");
@@ -16,6 +17,7 @@ const Group = require("../groups/group");
1617
const misc = require("../commons/misc");
1718

1819
const mailtransport = require("./mailtransport");
20+
const statusmessage = require("../commons/statusmessage");
1921

2022
async function sendMail(message, type) {
2123
return mailtransport.sendMail(message, type, conf.get("sender-address"), conf.get("include-footer"));
@@ -37,10 +39,40 @@ function activityMarkdown(activity, language) {
3739
return markdown;
3840
}
3941

42+
function createChunkedSendingReportMessage(statusmessages, subject, message) {
43+
const isError = R.any(statusmessage.isErrorMessage, statusmessages);
44+
const markdown = isError
45+
? `Fehler: ${statusmessages.map((s) => s.contents().additionalArguments.err).join(" ")}`
46+
: "E-Mails erfolgreich versendet";
47+
48+
return message.cloneWithBody({
49+
subject: `${isError ? "ERROR" : "SUCCESS"} ${subject}`,
50+
markdown,
51+
sendCopyToSelf: true,
52+
});
53+
}
54+
55+
async function sendMailInChunks(maxMailSendingChunkSize, allMembers, message, type) {
56+
const membersInChunks = R.splitEvery(maxMailSendingChunkSize, allMembers);
57+
58+
try {
59+
const statusmessages = await Promise.all(
60+
membersInChunks.map((bccs) => {
61+
message.setBccToMemberAddresses(bccs);
62+
return sendMail(message, type);
63+
}),
64+
);
65+
const resultMessage = createChunkedSendingReportMessage(statusmessages, `Report "${message.subject}"`, message);
66+
await sendMail(resultMessage, type);
67+
} catch (err) {
68+
logger.error(err);
69+
}
70+
}
71+
4072
module.exports = {
4173
activityMarkdown,
4274

43-
dataForShowingMessageForActivity: async function (activityURL, language) {
75+
dataForShowingMessageForActivity: async function dataForShowingMessageForActivity(activityURL, language) {
4476
const [activity, groups] = [
4577
activitiesService.getActivityWithGroupAndParticipants(activityURL),
4678
groupstore.allGroups(),
@@ -105,7 +137,11 @@ module.exports = {
105137
}
106138
try {
107139
groups.forEach(groupsAndMembersService.addMembersToGroup);
108-
message.setBccToGroupMemberAddresses(groups);
140+
const allMembers = R.uniqBy(
141+
(member) => member.email(),
142+
groups.flatMap((group) => group.members),
143+
);
144+
109145
let activity;
110146
try {
111147
activity = activitystore.getActivity(activityURL);
@@ -115,7 +151,17 @@ module.exports = {
115151
if (activity) {
116152
message.setIcal(icalService.activityAsICal(activity).toString());
117153
}
118-
return sendMail(message, type);
154+
155+
const maxMailSendingChunkSize = conf.get("maxMailSendingChunkSize");
156+
if (allMembers.length > maxMailSendingChunkSize) {
157+
// noinspection ES6MissingAwait - we explicitly don't want to wait because that could cause timeouts
158+
sendMailInChunks(maxMailSendingChunkSize, allMembers, message, type);
159+
160+
return mailtransport.statusmessageForSuccess(type);
161+
} else {
162+
message.setBccToMemberAddresses(allMembers);
163+
return sendMail(message, type);
164+
}
119165
} catch (err1) {
120166
return mailtransport.statusmessageForError(type, err1);
121167
}
@@ -142,8 +188,11 @@ module.exports = {
142188
sendMailToAllMembers: async function sendMailToAllMembers(message) {
143189
const type = "$t(mailsender.notification)";
144190
const members = memberstore.allMembers();
145-
message.setBccToMemberAddresses(members);
146-
return sendMail(message, type);
191+
192+
// noinspection ES6MissingAwait - we explicitly don't want to wait because that could cause timeouts
193+
sendMailInChunks(conf.get("maxMailSendingChunkSize"), members, message, type);
194+
195+
return mailtransport.statusmessageForSuccess(type);
147196
},
148197

149198
sendMagicLinkToMember: async function sendMagicLinkToMember(member, token) {

softwerkskammer/lib/mailsender/message.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ class Message {
2323
return this;
2424
}
2525

26+
cloneWithBody(body) {
27+
const fakeMember = {
28+
displayName: () => this.senderName,
29+
email: () => this.senderAddress,
30+
};
31+
return new Message(body, fakeMember);
32+
}
33+
2634
setTo(toAddresses) {
2735
this.to = toAddresses;
2836
}

0 commit comments

Comments
 (0)