@stamhoofd/models 2.91.0 → 2.93.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/factories/RegistrationPeriodFactory.d.ts +2 -1
- package/dist/src/factories/RegistrationPeriodFactory.d.ts.map +1 -1
- package/dist/src/factories/RegistrationPeriodFactory.js +2 -1
- package/dist/src/factories/RegistrationPeriodFactory.js.map +1 -1
- package/dist/src/helpers/EmailBuilder.d.ts +1 -2
- package/dist/src/helpers/EmailBuilder.d.ts.map +1 -1
- package/dist/src/helpers/EmailBuilder.js +7 -21
- package/dist/src/helpers/EmailBuilder.js.map +1 -1
- package/dist/src/helpers/MemberMerger.d.ts.map +1 -1
- package/dist/src/helpers/MemberMerger.js +6 -0
- package/dist/src/helpers/MemberMerger.js.map +1 -1
- package/dist/src/migrations/1755529026-email-sender-id.sql +2 -0
- package/dist/src/migrations/1755789797-email-counts-errors.sql +7 -0
- package/dist/src/migrations/1755789798-email-recipient-errors.sql +2 -0
- package/dist/src/migrations/1756115313-email-recipient-ids-and-errors.sql +10 -0
- package/dist/src/migrations/1756115314-email-recipient-email-optional.sql +2 -0
- package/dist/src/migrations/1756115315-email-recipients-count.sql +3 -0
- package/dist/src/migrations/1756115316-email-cached-counts.sql +4 -0
- package/dist/src/models/Email.d.ts +63 -2
- package/dist/src/models/Email.d.ts.map +1 -1
- package/dist/src/models/Email.js +559 -194
- package/dist/src/models/Email.js.map +1 -1
- package/dist/src/models/Email.test.js +151 -43
- package/dist/src/models/Email.test.js.map +1 -1
- package/dist/src/models/EmailRecipient.d.ts +31 -1
- package/dist/src/models/EmailRecipient.d.ts.map +1 -1
- package/dist/src/models/EmailRecipient.js +53 -2
- package/dist/src/models/EmailRecipient.js.map +1 -1
- package/dist/src/models/WebshopUitpasNumber.d.ts +2 -1
- package/dist/src/models/WebshopUitpasNumber.d.ts.map +1 -1
- package/dist/src/models/WebshopUitpasNumber.js +20 -3
- package/dist/src/models/WebshopUitpasNumber.js.map +1 -1
- package/package.json +2 -2
- package/src/factories/RegistrationPeriodFactory.ts +3 -2
- package/src/helpers/EmailBuilder.ts +9 -24
- package/src/helpers/MemberMerger.ts +7 -0
- package/src/migrations/1755529026-email-sender-id.sql +2 -0
- package/src/migrations/1755789797-email-counts-errors.sql +7 -0
- package/src/migrations/1755789798-email-recipient-errors.sql +2 -0
- package/src/migrations/1756115313-email-recipient-ids-and-errors.sql +10 -0
- package/src/migrations/1756115314-email-recipient-email-optional.sql +2 -0
- package/src/migrations/1756115315-email-recipients-count.sql +3 -0
- package/src/migrations/1756115316-email-cached-counts.sql +4 -0
- package/src/models/Email.test.ts +165 -44
- package/src/models/Email.ts +639 -207
- package/src/models/EmailRecipient.ts +46 -2
- package/src/models/WebshopUitpasNumber.ts +23 -3
package/dist/src/models/Email.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Email = void 0;
|
|
4
|
+
exports.isSoftEmailRecipientError = isSoftEmailRecipientError;
|
|
4
5
|
const tslib_1 = require("tslib");
|
|
5
6
|
const simple_database_1 = require("@simonbackx/simple-database");
|
|
6
7
|
const structures_1 = require("@stamhoofd/structures");
|
|
@@ -15,10 +16,35 @@ const EmailBuilder_1 = require("../helpers/EmailBuilder");
|
|
|
15
16
|
const EmailRecipient_1 = require("./EmailRecipient");
|
|
16
17
|
const EmailTemplate_1 = require("./EmailTemplate");
|
|
17
18
|
const Organization_1 = require("./Organization");
|
|
19
|
+
function errorToSimpleErrors(e) {
|
|
20
|
+
if ((0, simple_errors_1.isSimpleErrors)(e)) {
|
|
21
|
+
return e;
|
|
22
|
+
}
|
|
23
|
+
else if ((0, simple_errors_1.isSimpleError)(e)) {
|
|
24
|
+
return new simple_errors_1.SimpleErrors(e);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
return new simple_errors_1.SimpleErrors(new simple_errors_1.SimpleError({
|
|
28
|
+
code: 'unknown_error',
|
|
29
|
+
message: ((typeof e === 'object' && e !== null && 'message' in e && typeof e.message === 'string') ? e.message : 'Unknown error'),
|
|
30
|
+
human: $t(`Onbekende fout`),
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function isSoftEmailRecipientError(error) {
|
|
35
|
+
if (error.hasCode('email_skipped_unsubscribed')) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (error.hasCode('email_skipped_duplicate_recipient')) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
18
43
|
class Email extends sql_1.QueryableModel {
|
|
19
44
|
static table = 'emails';
|
|
20
45
|
id;
|
|
21
46
|
organizationId = null;
|
|
47
|
+
senderId = null;
|
|
22
48
|
userId = null;
|
|
23
49
|
recipientFilter = structures_1.EmailRecipientFilter.create({});
|
|
24
50
|
/**
|
|
@@ -33,9 +59,57 @@ class Email extends sql_1.QueryableModel {
|
|
|
33
59
|
text = null;
|
|
34
60
|
fromAddress = null;
|
|
35
61
|
fromName = null;
|
|
36
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Amount of recipients with an email address
|
|
64
|
+
*/
|
|
65
|
+
emailRecipientsCount = null;
|
|
66
|
+
/**
|
|
67
|
+
* Amount of recipients without an email address
|
|
68
|
+
*/
|
|
69
|
+
otherRecipientsCount = null;
|
|
70
|
+
/**
|
|
71
|
+
* Amount of recipients that have successfully received the email.
|
|
72
|
+
*/
|
|
73
|
+
succeededCount = 0;
|
|
74
|
+
/**
|
|
75
|
+
* Amount of recipients that somehow failed to receive the email,
|
|
76
|
+
* but with a soft error that doesn't require action.
|
|
77
|
+
* - Duplicate email in recipient list
|
|
78
|
+
* - Unsubscribed
|
|
79
|
+
*/
|
|
80
|
+
softFailedCount = 0;
|
|
81
|
+
/**
|
|
82
|
+
* Amount of recipients that somehow failed to receive the email:
|
|
83
|
+
* - Invalid email address
|
|
84
|
+
* - Full email inbox
|
|
85
|
+
*/
|
|
86
|
+
failedCount = 0;
|
|
87
|
+
/**
|
|
88
|
+
* Unique amount of members that are in the recipients list.
|
|
89
|
+
*/
|
|
90
|
+
membersCount = 0;
|
|
91
|
+
/**
|
|
92
|
+
* Does only include bounces AFTER sending the email
|
|
93
|
+
*/
|
|
94
|
+
hardBouncesCount = 0;
|
|
95
|
+
/**
|
|
96
|
+
* Does only include bounces AFTER sending the email
|
|
97
|
+
*/
|
|
98
|
+
softBouncesCount = 0;
|
|
99
|
+
/**
|
|
100
|
+
* Does only include bounces AFTER sending the email
|
|
101
|
+
*/
|
|
102
|
+
spamComplaintsCount = 0;
|
|
37
103
|
status = structures_1.EmailStatus.Draft;
|
|
38
104
|
recipientsStatus = structures_1.EmailRecipientsStatus.NotCreated;
|
|
105
|
+
/**
|
|
106
|
+
* Errors related to creating the recipients.
|
|
107
|
+
*/
|
|
108
|
+
recipientsErrors = null;
|
|
109
|
+
/**
|
|
110
|
+
* Errors related to sending the email.
|
|
111
|
+
*/
|
|
112
|
+
emailErrors = null;
|
|
39
113
|
/**
|
|
40
114
|
* todo: ignore automatically
|
|
41
115
|
*/
|
|
@@ -44,6 +118,7 @@ class Email extends sql_1.QueryableModel {
|
|
|
44
118
|
createdAt;
|
|
45
119
|
updatedAt;
|
|
46
120
|
static recipientLoaders = new Map();
|
|
121
|
+
static pendingNotificationCountUpdates = new Map();
|
|
47
122
|
throwIfNotReadyToSend() {
|
|
48
123
|
if (this.subject == null || this.subject.length == 0) {
|
|
49
124
|
throw new simple_errors_1.SimpleError({
|
|
@@ -73,6 +148,13 @@ class Email extends sql_1.QueryableModel {
|
|
|
73
148
|
human: $t(`e92cd077-b0f1-4b0a-82a0-8a8baa82e73a`),
|
|
74
149
|
});
|
|
75
150
|
}
|
|
151
|
+
if (this.recipientsErrors !== null && this.recipientsStatus !== structures_1.EmailRecipientsStatus.Created) {
|
|
152
|
+
throw new simple_errors_1.SimpleError({
|
|
153
|
+
code: 'invalid_recipients',
|
|
154
|
+
message: 'Failed to build recipients (count)',
|
|
155
|
+
human: $t(`Er ging iets mis bij het aanmaken van de ontvangers. Probeer je selectie aan te passen. Neem contact op als het probleem zich blijft voordoen.`) + ' ' + this.recipientsErrors.getHuman(),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
76
158
|
this.validateAttachments();
|
|
77
159
|
}
|
|
78
160
|
validateAttachments() {
|
|
@@ -177,12 +259,10 @@ class Email extends sql_1.QueryableModel {
|
|
|
177
259
|
this.json = defaultTemplate.json;
|
|
178
260
|
return true;
|
|
179
261
|
}
|
|
180
|
-
async
|
|
181
|
-
this.throwIfNotReadyToSend();
|
|
182
|
-
await this.save();
|
|
262
|
+
async lock(callback) {
|
|
183
263
|
const id = this.id;
|
|
184
|
-
return await queues_1.QueueHandler.schedule('
|
|
185
|
-
|
|
264
|
+
return await queues_1.QueueHandler.schedule('lock-email-' + id, async (options) => {
|
|
265
|
+
const upToDate = await Email.getByID(id);
|
|
186
266
|
if (!upToDate) {
|
|
187
267
|
throw new simple_errors_1.SimpleError({
|
|
188
268
|
code: 'not_found',
|
|
@@ -190,224 +270,444 @@ class Email extends sql_1.QueryableModel {
|
|
|
190
270
|
human: $t(`55899a7c-f3d4-43fe-a431-70a3a9e78e34`),
|
|
191
271
|
});
|
|
192
272
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
273
|
+
const c = await callback(upToDate, options);
|
|
274
|
+
this.copyFrom(upToDate);
|
|
275
|
+
return c;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
static async bumpNotificationCount(emailId, type) {
|
|
279
|
+
// Send an update query
|
|
280
|
+
const base = Email.update()
|
|
281
|
+
.where('id', emailId);
|
|
282
|
+
switch (type) {
|
|
283
|
+
case 'hard-bounce': {
|
|
284
|
+
base.set('hardBouncesCount', new sql_1.SQLCalculation(sql_1.SQL.column('hardBouncesCount'), new sql_1.SQLPlusSign(), (0, sql_1.readDynamicSQLExpression)(1)));
|
|
285
|
+
break;
|
|
197
286
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// Not eligible for retry
|
|
202
|
-
upToDate.status = structures_1.EmailStatus.Failed;
|
|
203
|
-
await upToDate.save();
|
|
204
|
-
return upToDate;
|
|
205
|
-
}
|
|
206
|
-
if (upToDate.createdAt < new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 2)) {
|
|
207
|
-
// Too long
|
|
208
|
-
console.error('Email has been sending for too long. Marking as failed...', upToDate.id);
|
|
209
|
-
upToDate.status = structures_1.EmailStatus.Failed;
|
|
210
|
-
await upToDate.save();
|
|
211
|
-
return upToDate;
|
|
212
|
-
}
|
|
287
|
+
case 'soft-bounce': {
|
|
288
|
+
base.set('softBouncesCount', new sql_1.SQLCalculation(sql_1.SQL.column('softBouncesCount'), new sql_1.SQLPlusSign(), (0, sql_1.readDynamicSQLExpression)(1)));
|
|
289
|
+
break;
|
|
213
290
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
let replyTo = upToDate.getFromAddress();
|
|
218
|
-
if (!from) {
|
|
219
|
-
throw new simple_errors_1.SimpleError({
|
|
220
|
-
code: 'invalid_field',
|
|
221
|
-
message: 'Missing from',
|
|
222
|
-
human: $t(`e92cd077-b0f1-4b0a-82a0-8a8baa82e73a`),
|
|
223
|
-
});
|
|
291
|
+
case 'complaint': {
|
|
292
|
+
base.set('spamComplaintsCount', new sql_1.SQLCalculation(sql_1.SQL.column('spamComplaintsCount'), new sql_1.SQLPlusSign(), (0, sql_1.readDynamicSQLExpression)(1)));
|
|
293
|
+
break;
|
|
224
294
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
295
|
+
}
|
|
296
|
+
await base.update();
|
|
297
|
+
await this.checkNeedsNotificationCountUpdate(emailId, true);
|
|
298
|
+
}
|
|
299
|
+
static async checkNeedsNotificationCountUpdate(emailId, didUpdate = false) {
|
|
300
|
+
const existing = this.pendingNotificationCountUpdates.get(emailId);
|
|
301
|
+
const object = existing ?? {
|
|
302
|
+
timer: null,
|
|
303
|
+
lastUpdate: didUpdate ? new Date() : null,
|
|
304
|
+
};
|
|
305
|
+
if (didUpdate) {
|
|
306
|
+
object.lastUpdate = new Date();
|
|
307
|
+
}
|
|
308
|
+
if (existing) {
|
|
309
|
+
this.pendingNotificationCountUpdates.set(emailId, object);
|
|
310
|
+
}
|
|
311
|
+
if (object.lastUpdate && object.lastUpdate < new Date(Date.now() - 5 * 60 * 1000)) {
|
|
312
|
+
// After 5 minutes without notifications, run an update.
|
|
313
|
+
await this.updateNotificationsCounts(emailId);
|
|
314
|
+
// Stop here
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
// Schedule a slow update of all counts
|
|
318
|
+
if (!object.timer) {
|
|
319
|
+
object.timer = setTimeout(() => {
|
|
320
|
+
object.timer = null;
|
|
321
|
+
this.checkNeedsNotificationCountUpdate(emailId).catch(console.error);
|
|
322
|
+
}, 1 * 60 * 1000);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
static async updateNotificationsCounts(emailId) {
|
|
326
|
+
queues_1.QueueHandler.cancel('updateNotificationsCounts-' + emailId);
|
|
327
|
+
return await queues_1.QueueHandler.schedule('updateNotificationsCounts-' + emailId, async () => {
|
|
328
|
+
const query = sql_1.SQL.select(new sql_1.SQLSelectAs(new sql_1.SQLCount(sql_1.SQL.column('hardBounceError')), new sql_1.SQLAlias('data__hardBounces')),
|
|
329
|
+
// If the current amount_due is negative, we can ignore that negative part if there is a future due item
|
|
330
|
+
new sql_1.SQLSelectAs(new sql_1.SQLCount(sql_1.SQL.column('softBounceError')), new sql_1.SQLAlias('data__softBounces')), new sql_1.SQLSelectAs(new sql_1.SQLCount(sql_1.SQL.column('spamComplaintError')), new sql_1.SQLAlias('data__complaints')))
|
|
331
|
+
.from(EmailRecipient_1.EmailRecipient.table)
|
|
332
|
+
.where('emailId', emailId);
|
|
333
|
+
const result = await query.fetch();
|
|
334
|
+
if (result.length !== 1) {
|
|
335
|
+
console.error('Unexpected result', result);
|
|
336
|
+
return;
|
|
229
337
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
// Create recipients if not yet created
|
|
235
|
-
await upToDate.buildRecipients();
|
|
236
|
-
abort.throwIfAborted();
|
|
237
|
-
// Refresh model
|
|
238
|
-
upToDate = await Email.getByID(id);
|
|
239
|
-
if (!upToDate) {
|
|
240
|
-
throw new simple_errors_1.SimpleError({
|
|
241
|
-
code: 'not_found',
|
|
242
|
-
message: 'Email not found',
|
|
243
|
-
human: $t(`55899a7c-f3d4-43fe-a431-70a3a9e78e34`),
|
|
244
|
-
});
|
|
338
|
+
const row = result[0]['data'];
|
|
339
|
+
if (!row) {
|
|
340
|
+
console.error('Unexpected result row', result);
|
|
341
|
+
return;
|
|
245
342
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
343
|
+
const hardBounces = row['hardBounces'];
|
|
344
|
+
const softBounces = row['softBounces'];
|
|
345
|
+
const complaints = row['complaints'];
|
|
346
|
+
if (typeof hardBounces !== 'number' || typeof softBounces !== 'number' || typeof complaints !== 'number') {
|
|
347
|
+
console.error('Unexpected result values', row);
|
|
348
|
+
return;
|
|
252
349
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
350
|
+
console.log('Updating email notification counts', emailId, hardBounces, softBounces, complaints);
|
|
351
|
+
// Send an update query
|
|
352
|
+
await Email.update()
|
|
353
|
+
.where('id', emailId)
|
|
354
|
+
.set('hardBouncesCount', hardBounces)
|
|
355
|
+
.set('softBouncesCount', softBounces)
|
|
356
|
+
.set('spamComplaintsCount', complaints)
|
|
357
|
+
.update();
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
async queueForSending(waitForSending = false) {
|
|
361
|
+
this.throwIfNotReadyToSend();
|
|
362
|
+
await this.lock(async (upToDate) => {
|
|
363
|
+
if (upToDate.status === structures_1.EmailStatus.Draft) {
|
|
364
|
+
upToDate.status = structures_1.EmailStatus.Queued;
|
|
365
|
+
}
|
|
366
|
+
await upToDate.save();
|
|
367
|
+
});
|
|
368
|
+
if (waitForSending) {
|
|
369
|
+
return await this.resumeSending();
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
this.resumeSending().catch(console.error);
|
|
373
|
+
}
|
|
374
|
+
return this;
|
|
375
|
+
}
|
|
376
|
+
async resumeSending() {
|
|
377
|
+
const id = this.id;
|
|
378
|
+
return await queues_1.QueueHandler.schedule('send-email', async ({ abort }) => {
|
|
379
|
+
return await this.lock(async function (upToDate) {
|
|
380
|
+
if (upToDate.status === structures_1.EmailStatus.Sent) {
|
|
381
|
+
// Already done
|
|
382
|
+
// In other cases -> queue has stopped and we can retry
|
|
383
|
+
return upToDate;
|
|
271
384
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
385
|
+
if (upToDate.status === structures_1.EmailStatus.Sending) {
|
|
386
|
+
// This is an automatic retry.
|
|
387
|
+
if (upToDate.emailType) {
|
|
388
|
+
// Not eligible for retry
|
|
389
|
+
upToDate.status = structures_1.EmailStatus.Failed;
|
|
390
|
+
await upToDate.save();
|
|
391
|
+
return upToDate;
|
|
392
|
+
}
|
|
393
|
+
if (upToDate.createdAt < new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 2)) {
|
|
394
|
+
// Too long
|
|
395
|
+
console.error('Email has been sending for too long. Marking as failed...', upToDate.id);
|
|
396
|
+
upToDate.status = structures_1.EmailStatus.Failed;
|
|
397
|
+
await upToDate.save();
|
|
398
|
+
return upToDate;
|
|
399
|
+
}
|
|
275
400
|
}
|
|
276
|
-
if (
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
content: attachment.content,
|
|
280
|
-
contentType: attachment.contentType ?? undefined,
|
|
281
|
-
encoding: 'base64',
|
|
282
|
-
});
|
|
401
|
+
else if (upToDate.status !== structures_1.EmailStatus.Queued) {
|
|
402
|
+
console.error('Email is not queued or sending, cannot send', upToDate.id, upToDate.status);
|
|
403
|
+
return upToDate;
|
|
283
404
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
405
|
+
const organization = upToDate.organizationId ? await Organization_1.Organization.getByID(upToDate.organizationId) : null;
|
|
406
|
+
let from = upToDate.getDefaultFromAddress(organization);
|
|
407
|
+
let replyTo = upToDate.getFromAddress();
|
|
408
|
+
const attachments = [];
|
|
409
|
+
let succeededCount = 0;
|
|
410
|
+
let softFailedCount = 0;
|
|
411
|
+
let failedCount = 0;
|
|
412
|
+
try {
|
|
413
|
+
upToDate.throwIfNotReadyToSend();
|
|
414
|
+
if (!from) {
|
|
288
415
|
throw new simple_errors_1.SimpleError({
|
|
289
|
-
code: '
|
|
290
|
-
message: '
|
|
291
|
-
human: $t(`
|
|
416
|
+
code: 'invalid_field',
|
|
417
|
+
message: 'Missing from',
|
|
418
|
+
human: $t(`e92cd077-b0f1-4b0a-82a0-8a8baa82e73a`),
|
|
292
419
|
});
|
|
293
420
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
fileBuffer = Buffer.from(await response.arrayBuffer());
|
|
421
|
+
// Can we send from this e-mail or reply-to?
|
|
422
|
+
if (upToDate.fromAddress && await (0, EmailBuilder_1.canSendFromEmail)(upToDate.fromAddress, organization ?? null)) {
|
|
423
|
+
from = upToDate.getFromAddress();
|
|
424
|
+
replyTo = null;
|
|
299
425
|
}
|
|
300
|
-
|
|
426
|
+
abort.throwIfAborted();
|
|
427
|
+
upToDate.status = structures_1.EmailStatus.Sending;
|
|
428
|
+
upToDate.sentAt = upToDate.sentAt ?? new Date();
|
|
429
|
+
await upToDate.save();
|
|
430
|
+
// Create recipients if not yet created
|
|
431
|
+
await upToDate.buildRecipients();
|
|
432
|
+
abort.throwIfAborted();
|
|
433
|
+
// Refresh model
|
|
434
|
+
const c = (await Email.getByID(id));
|
|
435
|
+
if (!c) {
|
|
301
436
|
throw new simple_errors_1.SimpleError({
|
|
302
|
-
code: '
|
|
303
|
-
message: '
|
|
304
|
-
human: $t(`
|
|
437
|
+
code: 'not_found',
|
|
438
|
+
message: 'Email not found',
|
|
439
|
+
human: $t(`55899a7c-f3d4-43fe-a431-70a3a9e78e34`),
|
|
305
440
|
});
|
|
306
441
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
while (true) {
|
|
315
|
-
abort.throwIfAborted();
|
|
316
|
-
const data = await sql_1.SQL.select()
|
|
317
|
-
.from('email_recipients')
|
|
318
|
-
.where('emailId', upToDate.id)
|
|
319
|
-
.where('sentAt', null)
|
|
320
|
-
.where('id', sql_1.SQLWhereSign.Greater, idPointer)
|
|
321
|
-
.orderBy(sql_1.SQL.column('id'), 'ASC')
|
|
322
|
-
.limit(batchSize)
|
|
323
|
-
.fetch();
|
|
324
|
-
const recipients = EmailRecipient_1.EmailRecipient.fromRows(data, 'email_recipients');
|
|
325
|
-
if (recipients.length === 0) {
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
const sendingPromises = [];
|
|
329
|
-
for (const recipient of recipients) {
|
|
330
|
-
if (recipientsSet.has(recipient.id)) {
|
|
331
|
-
console.error('Found duplicate recipient while sending email', recipient.id);
|
|
332
|
-
continue;
|
|
442
|
+
upToDate.copyFrom(c);
|
|
443
|
+
if (upToDate.recipientsStatus !== structures_1.EmailRecipientsStatus.Created) {
|
|
444
|
+
throw new simple_errors_1.SimpleError({
|
|
445
|
+
code: 'recipients_not_created',
|
|
446
|
+
message: 'Failed to create recipients',
|
|
447
|
+
human: $t(`f660b2eb-e382-4d21-86e4-673ca7bc2d4a`),
|
|
448
|
+
});
|
|
333
449
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
450
|
+
// Create a buffer of all attachments
|
|
451
|
+
for (const attachment of upToDate.attachments) {
|
|
452
|
+
if (!attachment.content && !attachment.file) {
|
|
453
|
+
console.warn('Attachment without content found, skipping', attachment);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
let filename = $t('b1291584-d2ad-4ebd-88ed-cbda4f3755b4');
|
|
457
|
+
if (attachment.contentType === 'application/pdf') {
|
|
458
|
+
// tmp solution for pdf only
|
|
459
|
+
filename += '.pdf';
|
|
460
|
+
}
|
|
461
|
+
if (attachment.file?.name) {
|
|
462
|
+
filename = attachment.file.name.toLowerCase().replace(/[^a-z0-9.]+/g, '-').replace(/^-+/, '').replace(/-+$/, '');
|
|
463
|
+
}
|
|
464
|
+
// Correct file name if needed
|
|
465
|
+
if (attachment.filename) {
|
|
466
|
+
filename = attachment.filename.toLowerCase().replace(/[^a-z0-9.]+/g, '-').replace(/^-+/, '').replace(/-+$/, '');
|
|
467
|
+
}
|
|
468
|
+
if (attachment.content) {
|
|
469
|
+
attachments.push({
|
|
470
|
+
filename: filename,
|
|
471
|
+
content: attachment.content,
|
|
472
|
+
contentType: attachment.contentType ?? undefined,
|
|
473
|
+
encoding: 'base64',
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
// Note: because we send lots of emails, we better download the file here so we can reuse it in every email instead of downloading it every time
|
|
478
|
+
const withSigned = await attachment.file.withSignedUrl();
|
|
479
|
+
if (!withSigned || !withSigned.signedUrl) {
|
|
480
|
+
throw new simple_errors_1.SimpleError({
|
|
481
|
+
code: 'attachment_not_found',
|
|
482
|
+
message: 'Attachment not found',
|
|
483
|
+
human: $t(`ce6ddaf0-8347-42c5-b4b7-fbe860c7b7f2`),
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
const filePath = withSigned.signedUrl;
|
|
487
|
+
let fileBuffer = null;
|
|
488
|
+
try {
|
|
489
|
+
const response = await fetch(filePath);
|
|
490
|
+
fileBuffer = Buffer.from(await response.arrayBuffer());
|
|
491
|
+
}
|
|
492
|
+
catch (e) {
|
|
493
|
+
throw new simple_errors_1.SimpleError({
|
|
494
|
+
code: 'attachment_not_found',
|
|
495
|
+
message: 'Attachment not found',
|
|
496
|
+
human: $t(`ce6ddaf0-8347-42c5-b4b7-fbe860c7b7f2`),
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
attachments.push({
|
|
500
|
+
filename: filename,
|
|
501
|
+
contentType: attachment.contentType ?? undefined,
|
|
502
|
+
content: fileBuffer,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Start actually sending in batches of recipients that are not yet sent
|
|
507
|
+
let idPointer = '';
|
|
508
|
+
const batchSize = 100;
|
|
509
|
+
const recipientsSet = new Set();
|
|
510
|
+
let isSavingStatus = false;
|
|
511
|
+
let lastStatusSave = new Date();
|
|
512
|
+
async function saveStatus() {
|
|
513
|
+
if (!upToDate) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (isSavingStatus) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if ((new Date().getTime() - lastStatusSave.getTime()) < 1000 * 5) {
|
|
520
|
+
// Save at most every 5 seconds
|
|
344
521
|
return;
|
|
345
522
|
}
|
|
346
|
-
|
|
523
|
+
if (succeededCount < upToDate.succeededCount || softFailedCount < upToDate.softFailedCount || failedCount < upToDate.failedCount) {
|
|
524
|
+
// Do not update on retries
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
lastStatusSave = new Date();
|
|
528
|
+
isSavingStatus = true;
|
|
529
|
+
upToDate.succeededCount = succeededCount;
|
|
530
|
+
upToDate.softFailedCount = softFailedCount;
|
|
531
|
+
upToDate.failedCount = failedCount;
|
|
347
532
|
try {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
533
|
+
await upToDate.save();
|
|
534
|
+
}
|
|
535
|
+
finally {
|
|
536
|
+
isSavingStatus = false;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
while (true) {
|
|
540
|
+
abort.throwIfAborted();
|
|
541
|
+
const data = await sql_1.SQL.select()
|
|
542
|
+
.from('email_recipients')
|
|
543
|
+
.where('emailId', upToDate.id)
|
|
544
|
+
.where('id', sql_1.SQLWhereSign.Greater, idPointer)
|
|
545
|
+
.orderBy(sql_1.SQL.column('id'), 'ASC')
|
|
546
|
+
.limit(batchSize)
|
|
547
|
+
.fetch();
|
|
548
|
+
const recipients = EmailRecipient_1.EmailRecipient.fromRows(data, 'email_recipients');
|
|
549
|
+
if (recipients.length === 0) {
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
const sendingPromises = [];
|
|
553
|
+
let skipped = 0;
|
|
554
|
+
for (const recipient of recipients) {
|
|
555
|
+
idPointer = recipient.id;
|
|
556
|
+
if (recipient.sentAt) {
|
|
557
|
+
// Already sent
|
|
558
|
+
if (recipient.email) {
|
|
559
|
+
recipientsSet.add(recipient.email);
|
|
560
|
+
}
|
|
561
|
+
succeededCount += 1;
|
|
562
|
+
await saveStatus();
|
|
563
|
+
skipped++;
|
|
564
|
+
continue;
|
|
354
565
|
}
|
|
355
|
-
|
|
566
|
+
if (!recipient.email) {
|
|
567
|
+
skipped++;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (recipientsSet.has(recipient.id)) {
|
|
571
|
+
console.error('Found duplicate recipient while sending email', recipient.id);
|
|
572
|
+
softFailedCount += 1;
|
|
356
573
|
recipient.failCount += 1;
|
|
357
|
-
recipient.failErrorMessage =
|
|
574
|
+
recipient.failErrorMessage = 'Duplicate recipient';
|
|
575
|
+
recipient.failError = new simple_errors_1.SimpleErrors(new simple_errors_1.SimpleError({
|
|
576
|
+
code: 'email_skipped_duplicate_recipient',
|
|
577
|
+
message: 'Duplicate recipient',
|
|
578
|
+
human: $t('Dit e-mailadres staat meerdere keren tussen de ontvangers en werd daarom overgeslagen'),
|
|
579
|
+
}));
|
|
358
580
|
recipient.firstFailedAt = recipient.firstFailedAt ?? new Date();
|
|
359
581
|
recipient.lastFailedAt = new Date();
|
|
360
582
|
await recipient.save();
|
|
583
|
+
await saveStatus();
|
|
584
|
+
skipped++;
|
|
585
|
+
continue;
|
|
361
586
|
}
|
|
587
|
+
recipientsSet.add(recipient.email);
|
|
588
|
+
let promiseResolve;
|
|
589
|
+
const promise = new Promise((resolve) => {
|
|
590
|
+
promiseResolve = resolve;
|
|
591
|
+
});
|
|
592
|
+
const virtualRecipient = recipient.getRecipient();
|
|
593
|
+
let resolved = false;
|
|
594
|
+
const callback = async (error) => {
|
|
595
|
+
if (resolved) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
resolved = true;
|
|
599
|
+
try {
|
|
600
|
+
if (error === null) {
|
|
601
|
+
// Mark saved
|
|
602
|
+
recipient.sentAt = new Date();
|
|
603
|
+
// Update repacements that have been generated
|
|
604
|
+
recipient.replacements = virtualRecipient.replacements;
|
|
605
|
+
succeededCount += 1;
|
|
606
|
+
await recipient.save();
|
|
607
|
+
await saveStatus();
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
recipient.failCount += 1;
|
|
611
|
+
recipient.failErrorMessage = error.message;
|
|
612
|
+
recipient.failError = errorToSimpleErrors(error);
|
|
613
|
+
recipient.firstFailedAt = recipient.firstFailedAt ?? new Date();
|
|
614
|
+
recipient.lastFailedAt = new Date();
|
|
615
|
+
if (isSoftEmailRecipientError(recipient.failError)) {
|
|
616
|
+
softFailedCount += 1;
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
failedCount += 1;
|
|
620
|
+
}
|
|
621
|
+
await recipient.save();
|
|
622
|
+
await saveStatus();
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
catch (e) {
|
|
626
|
+
console.error(e);
|
|
627
|
+
}
|
|
628
|
+
promiseResolve();
|
|
629
|
+
};
|
|
630
|
+
// Do send the email
|
|
631
|
+
// Create e-mail builder
|
|
632
|
+
const builder = await (0, EmailBuilder_1.getEmailBuilder)(organization ?? null, {
|
|
633
|
+
recipients: [
|
|
634
|
+
virtualRecipient,
|
|
635
|
+
],
|
|
636
|
+
from,
|
|
637
|
+
replyTo,
|
|
638
|
+
subject: upToDate.subject,
|
|
639
|
+
html: upToDate.html,
|
|
640
|
+
type: upToDate.emailType ? 'transactional' : 'broadcast',
|
|
641
|
+
attachments,
|
|
642
|
+
callback(error) {
|
|
643
|
+
callback(error).catch(console.error);
|
|
644
|
+
},
|
|
645
|
+
headers: {
|
|
646
|
+
'X-Email-Id': upToDate.id,
|
|
647
|
+
'X-Email-Recipient-Id': recipient.id,
|
|
648
|
+
},
|
|
649
|
+
});
|
|
650
|
+
abort.throwIfAborted(); // do not schedule if aborted
|
|
651
|
+
email_1.Email.schedule(builder);
|
|
652
|
+
sendingPromises.push(promise);
|
|
362
653
|
}
|
|
363
|
-
|
|
364
|
-
|
|
654
|
+
if (sendingPromises.length > 0 || skipped > 0) {
|
|
655
|
+
await Promise.all(sendingPromises);
|
|
365
656
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
657
|
+
else {
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch (e) {
|
|
663
|
+
if (!upToDate) {
|
|
664
|
+
throw e;
|
|
665
|
+
}
|
|
666
|
+
upToDate.succeededCount = succeededCount;
|
|
667
|
+
upToDate.softFailedCount = softFailedCount;
|
|
668
|
+
upToDate.failedCount = failedCount;
|
|
669
|
+
if ((0, queues_1.isAbortedError)(e) || (((0, simple_errors_1.isSimpleError)(e) || (0, simple_errors_1.isSimpleErrors)(e)) && e.hasCode('SHUTDOWN'))) {
|
|
670
|
+
// Keep sending status: we'll resume after the reboot
|
|
671
|
+
await upToDate.save();
|
|
672
|
+
throw e;
|
|
673
|
+
}
|
|
674
|
+
upToDate.emailErrors = errorToSimpleErrors(e);
|
|
675
|
+
upToDate.status = structures_1.EmailStatus.Failed;
|
|
676
|
+
await upToDate.save();
|
|
677
|
+
throw e;
|
|
387
678
|
}
|
|
388
|
-
if (
|
|
389
|
-
|
|
679
|
+
if (upToDate.emailRecipientsCount === 0 && upToDate.userId === null) {
|
|
680
|
+
// We only delete automated emails (email type) if they have no recipients
|
|
681
|
+
console.log('No recipients found for email ', upToDate.id, ' deleting...');
|
|
682
|
+
await upToDate.delete();
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
console.log('Finished sending email', upToDate.id);
|
|
686
|
+
// Mark email as sent
|
|
687
|
+
if ((succeededCount + failedCount + softFailedCount) === 0) {
|
|
688
|
+
upToDate.status = structures_1.EmailStatus.Failed;
|
|
689
|
+
upToDate.emailErrors = new simple_errors_1.SimpleErrors(new simple_errors_1.SimpleError({
|
|
690
|
+
code: 'no_recipients',
|
|
691
|
+
message: 'No recipients',
|
|
692
|
+
human: $t(`Geen ontvangers gevonden`),
|
|
693
|
+
}));
|
|
390
694
|
}
|
|
391
695
|
else {
|
|
392
|
-
|
|
696
|
+
upToDate.status = structures_1.EmailStatus.Sent;
|
|
697
|
+
upToDate.emailErrors = null;
|
|
393
698
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
console.log('Finished sending email', upToDate.id);
|
|
402
|
-
// Mark email as sent
|
|
403
|
-
upToDate.status = structures_1.EmailStatus.Sent;
|
|
404
|
-
await upToDate.save();
|
|
405
|
-
return upToDate;
|
|
699
|
+
upToDate.succeededCount = succeededCount;
|
|
700
|
+
upToDate.softFailedCount = softFailedCount;
|
|
701
|
+
upToDate.failedCount = failedCount;
|
|
702
|
+
await upToDate.save();
|
|
703
|
+
return upToDate;
|
|
704
|
+
});
|
|
406
705
|
});
|
|
407
706
|
}
|
|
408
707
|
updateCount() {
|
|
409
708
|
const id = this.id;
|
|
410
|
-
queues_1.QueueHandler.
|
|
709
|
+
queues_1.QueueHandler.abort('email-count-' + this.id);
|
|
710
|
+
queues_1.QueueHandler.schedule('email-count-' + this.id, async function ({ abort }) {
|
|
411
711
|
let upToDate = await Email.getByID(id);
|
|
412
712
|
if (!upToDate || upToDate.sentAt || !upToDate.id || upToDate.status !== structures_1.EmailStatus.Draft) {
|
|
413
713
|
return;
|
|
@@ -429,10 +729,12 @@ class Email extends sql_1.QueryableModel {
|
|
|
429
729
|
limit: 1,
|
|
430
730
|
search: subfilter.search,
|
|
431
731
|
});
|
|
732
|
+
abort.throwIfAborted();
|
|
432
733
|
const c = await loader.count(request, subfilter.subfilter);
|
|
433
734
|
count += c;
|
|
434
735
|
}
|
|
435
|
-
|
|
736
|
+
abort.throwIfAborted();
|
|
737
|
+
// Check if we have a more reliable emailRecipientsCount in the meantime
|
|
436
738
|
upToDate = await Email.getByID(id);
|
|
437
739
|
if (!upToDate) {
|
|
438
740
|
return;
|
|
@@ -440,12 +742,25 @@ class Email extends sql_1.QueryableModel {
|
|
|
440
742
|
if (upToDate.recipientsStatus === structures_1.EmailRecipientsStatus.Created) {
|
|
441
743
|
return;
|
|
442
744
|
}
|
|
443
|
-
upToDate.
|
|
745
|
+
upToDate.emailRecipientsCount = count;
|
|
444
746
|
await upToDate.save();
|
|
445
747
|
}
|
|
446
748
|
catch (e) {
|
|
749
|
+
if ((0, queues_1.isAbortedError)(e)) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
447
752
|
console.error('Failed to update count for email', id);
|
|
448
753
|
console.error(e);
|
|
754
|
+
// Check if we have a more reliable emailRecipientsCount in the meantime
|
|
755
|
+
upToDate = await Email.getByID(id);
|
|
756
|
+
if (!upToDate) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (upToDate.recipientsStatus === structures_1.EmailRecipientsStatus.Created) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
upToDate.recipientsErrors = errorToSimpleErrors(e);
|
|
763
|
+
await upToDate.save();
|
|
449
764
|
}
|
|
450
765
|
}).catch(console.error);
|
|
451
766
|
}
|
|
@@ -466,7 +781,9 @@ class Email extends sql_1.QueryableModel {
|
|
|
466
781
|
// If it is already creating -> something went wrong (e.g. server restart) and we can safely try again
|
|
467
782
|
upToDate.recipientsStatus = structures_1.EmailRecipientsStatus.Creating;
|
|
468
783
|
await upToDate.save();
|
|
784
|
+
const membersSet = new Set();
|
|
469
785
|
let count = 0;
|
|
786
|
+
let countWithoutEmail = 0;
|
|
470
787
|
try {
|
|
471
788
|
// Delete all recipients
|
|
472
789
|
await sql_1.SQL
|
|
@@ -490,10 +807,9 @@ class Email extends sql_1.QueryableModel {
|
|
|
490
807
|
abort.throwIfAborted();
|
|
491
808
|
const response = await loader.fetch(request, subfilter.subfilter);
|
|
492
809
|
for (const item of response.results) {
|
|
493
|
-
if (!item.email) {
|
|
810
|
+
if (!item.email && !item.memberId && !item.userId) {
|
|
494
811
|
continue;
|
|
495
812
|
}
|
|
496
|
-
count += 1;
|
|
497
813
|
const recipient = new EmailRecipient_1.EmailRecipient();
|
|
498
814
|
recipient.emailType = upToDate.emailType;
|
|
499
815
|
recipient.objectId = item.objectId;
|
|
@@ -502,19 +818,35 @@ class Email extends sql_1.QueryableModel {
|
|
|
502
818
|
recipient.firstName = item.firstName;
|
|
503
819
|
recipient.lastName = item.lastName;
|
|
504
820
|
recipient.replacements = item.replacements;
|
|
821
|
+
recipient.memberId = item.memberId ?? null;
|
|
822
|
+
recipient.userId = item.userId ?? null;
|
|
823
|
+
recipient.organizationId = upToDate.organizationId ?? null;
|
|
505
824
|
await recipient.save();
|
|
825
|
+
if (recipient.memberId) {
|
|
826
|
+
membersSet.add(recipient.memberId);
|
|
827
|
+
}
|
|
828
|
+
if (!recipient.email) {
|
|
829
|
+
countWithoutEmail += 1;
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
count += 1;
|
|
833
|
+
}
|
|
506
834
|
}
|
|
507
835
|
request = response.next ?? null;
|
|
508
836
|
}
|
|
509
837
|
}
|
|
510
838
|
upToDate.recipientsStatus = structures_1.EmailRecipientsStatus.Created;
|
|
511
|
-
upToDate.
|
|
839
|
+
upToDate.emailRecipientsCount = count;
|
|
840
|
+
upToDate.otherRecipientsCount = countWithoutEmail;
|
|
841
|
+
upToDate.recipientsErrors = null;
|
|
842
|
+
upToDate.membersCount = membersSet.size;
|
|
512
843
|
await upToDate.save();
|
|
513
844
|
}
|
|
514
845
|
catch (e) {
|
|
515
846
|
console.error('Failed to build recipients for email', id);
|
|
516
847
|
console.error(e);
|
|
517
848
|
upToDate.recipientsStatus = structures_1.EmailRecipientsStatus.NotCreated;
|
|
849
|
+
upToDate.recipientsErrors = errorToSimpleErrors(e);
|
|
518
850
|
await upToDate.save();
|
|
519
851
|
}
|
|
520
852
|
});
|
|
@@ -610,6 +942,9 @@ tslib_1.__decorate([
|
|
|
610
942
|
tslib_1.__decorate([
|
|
611
943
|
(0, simple_database_1.column)({ type: 'string', nullable: true })
|
|
612
944
|
], Email.prototype, "organizationId", void 0);
|
|
945
|
+
tslib_1.__decorate([
|
|
946
|
+
(0, simple_database_1.column)({ type: 'string', nullable: true })
|
|
947
|
+
], Email.prototype, "senderId", void 0);
|
|
613
948
|
tslib_1.__decorate([
|
|
614
949
|
(0, simple_database_1.column)({ type: 'string', nullable: true })
|
|
615
950
|
], Email.prototype, "userId", void 0);
|
|
@@ -639,13 +974,43 @@ tslib_1.__decorate([
|
|
|
639
974
|
], Email.prototype, "fromName", void 0);
|
|
640
975
|
tslib_1.__decorate([
|
|
641
976
|
(0, simple_database_1.column)({ type: 'integer', nullable: true })
|
|
642
|
-
], Email.prototype, "
|
|
977
|
+
], Email.prototype, "emailRecipientsCount", void 0);
|
|
978
|
+
tslib_1.__decorate([
|
|
979
|
+
(0, simple_database_1.column)({ type: 'integer', nullable: true })
|
|
980
|
+
], Email.prototype, "otherRecipientsCount", void 0);
|
|
981
|
+
tslib_1.__decorate([
|
|
982
|
+
(0, simple_database_1.column)({ type: 'integer' })
|
|
983
|
+
], Email.prototype, "succeededCount", void 0);
|
|
984
|
+
tslib_1.__decorate([
|
|
985
|
+
(0, simple_database_1.column)({ type: 'integer' })
|
|
986
|
+
], Email.prototype, "softFailedCount", void 0);
|
|
987
|
+
tslib_1.__decorate([
|
|
988
|
+
(0, simple_database_1.column)({ type: 'integer' })
|
|
989
|
+
], Email.prototype, "failedCount", void 0);
|
|
990
|
+
tslib_1.__decorate([
|
|
991
|
+
(0, simple_database_1.column)({ type: 'integer' })
|
|
992
|
+
], Email.prototype, "membersCount", void 0);
|
|
993
|
+
tslib_1.__decorate([
|
|
994
|
+
(0, simple_database_1.column)({ type: 'integer' })
|
|
995
|
+
], Email.prototype, "hardBouncesCount", void 0);
|
|
996
|
+
tslib_1.__decorate([
|
|
997
|
+
(0, simple_database_1.column)({ type: 'integer' })
|
|
998
|
+
], Email.prototype, "softBouncesCount", void 0);
|
|
999
|
+
tslib_1.__decorate([
|
|
1000
|
+
(0, simple_database_1.column)({ type: 'integer' })
|
|
1001
|
+
], Email.prototype, "spamComplaintsCount", void 0);
|
|
643
1002
|
tslib_1.__decorate([
|
|
644
1003
|
(0, simple_database_1.column)({ type: 'string' })
|
|
645
1004
|
], Email.prototype, "status", void 0);
|
|
646
1005
|
tslib_1.__decorate([
|
|
647
1006
|
(0, simple_database_1.column)({ type: 'string' })
|
|
648
1007
|
], Email.prototype, "recipientsStatus", void 0);
|
|
1008
|
+
tslib_1.__decorate([
|
|
1009
|
+
(0, simple_database_1.column)({ type: 'json', nullable: true, decoder: simple_errors_1.SimpleErrors })
|
|
1010
|
+
], Email.prototype, "recipientsErrors", void 0);
|
|
1011
|
+
tslib_1.__decorate([
|
|
1012
|
+
(0, simple_database_1.column)({ type: 'json', nullable: true, decoder: simple_errors_1.SimpleErrors })
|
|
1013
|
+
], Email.prototype, "emailErrors", void 0);
|
|
649
1014
|
tslib_1.__decorate([
|
|
650
1015
|
(0, simple_database_1.column)({ type: 'json', decoder: new simple_encoding_1.ArrayDecoder(structures_1.EmailAttachment) })
|
|
651
1016
|
], Email.prototype, "attachments", void 0);
|