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