@stamhoofd/models 2.97.3 → 2.98.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/migrations/1758034021-email-recipient-previous-error.sql +2 -0
- package/dist/src/models/Email.d.ts +3 -1
- package/dist/src/models/Email.d.ts.map +1 -1
- package/dist/src/models/Email.js +221 -160
- package/dist/src/models/Email.js.map +1 -1
- package/dist/src/models/Email.test.js +2 -2
- package/dist/src/models/Email.test.js.map +1 -1
- package/dist/src/models/EmailRecipient.d.ts +1 -0
- package/dist/src/models/EmailRecipient.d.ts.map +1 -1
- package/dist/src/models/EmailRecipient.js +4 -0
- package/dist/src/models/EmailRecipient.js.map +1 -1
- package/package.json +2 -2
- package/src/migrations/1758034021-email-recipient-previous-error.sql +2 -0
- package/src/models/Email.test.ts +2 -2
- package/src/models/Email.ts +244 -176
- package/src/models/EmailRecipient.ts +3 -0
|
@@ -121,7 +121,9 @@ export declare class Email extends QueryableModel {
|
|
|
121
121
|
static checkNeedsNotificationCountUpdate(emailId: string, didUpdate?: boolean): Promise<void>;
|
|
122
122
|
static updateNotificationsCounts(emailId: string): Promise<void>;
|
|
123
123
|
queueForSending(waitForSending?: boolean): Promise<Email | null>;
|
|
124
|
-
|
|
124
|
+
private loadAttachments;
|
|
125
|
+
private sendSingleRecipient;
|
|
126
|
+
resumeSending(singleRecipientId?: string | null): Promise<Email | null>;
|
|
125
127
|
updateCount(): void;
|
|
126
128
|
buildRecipients(): Promise<void>;
|
|
127
129
|
buildExampleRecipient(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Email.d.ts","sourceRoot":"","sources":["../../../src/models/Email.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,eAAe,EAAE,YAAY,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,cAAc,IAAI,oBAAoB,EAAE,WAAW,EAAE,KAAK,IAAI,WAAW,EAAE,iBAAiB,EAAE,mBAAmB,EAAkD,sBAAsB,EAAE,iBAAiB,EAAkC,eAAe,EAAoB,MAAM,uBAAuB,CAAC;AAIlb,OAAO,EAA8C,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAErG,OAAO,EAAuB,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAgC,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACtF,OAAO,EAAE,cAAc,EAA6G,MAAM,gBAAgB,CAAC;AAI3J,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"Email.d.ts","sourceRoot":"","sources":["../../../src/models/Email.ts"],"names":[],"mappings":"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"}
|
package/dist/src/models/Email.js
CHANGED
|
@@ -426,39 +426,174 @@ class Email extends sql_1.QueryableModel {
|
|
|
426
426
|
}
|
|
427
427
|
return this;
|
|
428
428
|
}
|
|
429
|
-
async
|
|
429
|
+
async loadAttachments() {
|
|
430
|
+
const attachments = [];
|
|
431
|
+
for (const attachment of this.attachments) {
|
|
432
|
+
if (!attachment.content && !attachment.file) {
|
|
433
|
+
console.warn('Attachment without content found, skipping', attachment);
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
let filename = $t('b1291584-d2ad-4ebd-88ed-cbda4f3755b4');
|
|
437
|
+
if (attachment.contentType === 'application/pdf') {
|
|
438
|
+
// tmp solution for pdf only
|
|
439
|
+
filename += '.pdf';
|
|
440
|
+
}
|
|
441
|
+
if (attachment.file?.name) {
|
|
442
|
+
filename = attachment.file.name.toLowerCase().replace(/[^a-z0-9.]+/g, '-').replace(/^-+/, '').replace(/-+$/, '');
|
|
443
|
+
}
|
|
444
|
+
// Correct file name if needed
|
|
445
|
+
if (attachment.filename) {
|
|
446
|
+
filename = attachment.filename.toLowerCase().replace(/[^a-z0-9.]+/g, '-').replace(/^-+/, '').replace(/-+$/, '');
|
|
447
|
+
}
|
|
448
|
+
if (attachment.content) {
|
|
449
|
+
attachments.push({
|
|
450
|
+
filename: filename,
|
|
451
|
+
content: attachment.content,
|
|
452
|
+
contentType: attachment.contentType ?? undefined,
|
|
453
|
+
encoding: 'base64',
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
// 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
|
|
458
|
+
const withSigned = await attachment.file.withSignedUrl();
|
|
459
|
+
if (!withSigned || !withSigned.signedUrl) {
|
|
460
|
+
throw new simple_errors_1.SimpleError({
|
|
461
|
+
code: 'attachment_not_found',
|
|
462
|
+
message: 'Attachment not found',
|
|
463
|
+
human: $t(`ce6ddaf0-8347-42c5-b4b7-fbe860c7b7f2`),
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
const filePath = withSigned.signedUrl;
|
|
467
|
+
let fileBuffer = null;
|
|
468
|
+
try {
|
|
469
|
+
const response = await fetch(filePath);
|
|
470
|
+
fileBuffer = Buffer.from(await response.arrayBuffer());
|
|
471
|
+
}
|
|
472
|
+
catch (e) {
|
|
473
|
+
throw new simple_errors_1.SimpleError({
|
|
474
|
+
code: 'attachment_not_found',
|
|
475
|
+
message: 'Attachment not found',
|
|
476
|
+
human: $t(`ce6ddaf0-8347-42c5-b4b7-fbe860c7b7f2`),
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
attachments.push({
|
|
480
|
+
filename: filename,
|
|
481
|
+
contentType: attachment.contentType ?? undefined,
|
|
482
|
+
content: fileBuffer,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return attachments;
|
|
487
|
+
}
|
|
488
|
+
async sendSingleRecipient(recipient, organization, data) {
|
|
489
|
+
let promiseResolve;
|
|
490
|
+
const promise = new Promise((resolve) => {
|
|
491
|
+
promiseResolve = resolve;
|
|
492
|
+
});
|
|
493
|
+
const virtualRecipient = recipient.getRecipient();
|
|
494
|
+
let resolved = false;
|
|
495
|
+
const callback = async (error) => {
|
|
496
|
+
if (resolved) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
resolved = true;
|
|
500
|
+
try {
|
|
501
|
+
if (error === null) {
|
|
502
|
+
// Mark saved
|
|
503
|
+
recipient.sentAt = new Date();
|
|
504
|
+
recipient.failErrorMessage = null;
|
|
505
|
+
if (recipient.failError) {
|
|
506
|
+
recipient.previousFailError = recipient.failError;
|
|
507
|
+
}
|
|
508
|
+
recipient.failError = null;
|
|
509
|
+
// Update repacements that have been generated
|
|
510
|
+
recipient.replacements = virtualRecipient.replacements;
|
|
511
|
+
await recipient.save();
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
recipient.failCount += 1;
|
|
515
|
+
recipient.failErrorMessage = error.message;
|
|
516
|
+
if (recipient.failError) {
|
|
517
|
+
recipient.previousFailError = recipient.failError;
|
|
518
|
+
}
|
|
519
|
+
recipient.failError = errorToSimpleErrors(error);
|
|
520
|
+
recipient.firstFailedAt = recipient.firstFailedAt ?? new Date();
|
|
521
|
+
recipient.lastFailedAt = new Date();
|
|
522
|
+
await recipient.save();
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch (e) {
|
|
526
|
+
console.error(e);
|
|
527
|
+
}
|
|
528
|
+
promiseResolve();
|
|
529
|
+
};
|
|
530
|
+
// Do send the email
|
|
531
|
+
// Create e-mail builder
|
|
532
|
+
const builder = await (0, EmailBuilder_1.getEmailBuilder)(organization ?? null, {
|
|
533
|
+
recipients: [
|
|
534
|
+
virtualRecipient,
|
|
535
|
+
],
|
|
536
|
+
from: data.from,
|
|
537
|
+
replyTo: data.replyTo,
|
|
538
|
+
subject: this.subject,
|
|
539
|
+
html: this.html,
|
|
540
|
+
type: this.emailType ? 'transactional' : 'broadcast',
|
|
541
|
+
attachments: data.attachments,
|
|
542
|
+
callback(error) {
|
|
543
|
+
callback(error).catch(console.error);
|
|
544
|
+
},
|
|
545
|
+
headers: {
|
|
546
|
+
'X-Email-Id': this.id,
|
|
547
|
+
'X-Email-Recipient-Id': recipient.id,
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
email_1.Email.schedule(builder);
|
|
551
|
+
return await promise;
|
|
552
|
+
}
|
|
553
|
+
async resumeSending(singleRecipientId = null) {
|
|
430
554
|
const id = this.id;
|
|
431
555
|
return await queues_1.QueueHandler.schedule('send-email', async ({ abort }) => {
|
|
432
556
|
return await this.lock(async function (upToDate) {
|
|
433
557
|
if (upToDate.status === structures_1.EmailStatus.Sent) {
|
|
434
558
|
// Already done
|
|
435
559
|
// In other cases -> queue has stopped and we can retry
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (upToDate.status === structures_1.EmailStatus.Sending) {
|
|
439
|
-
// This is an automatic retry.
|
|
440
|
-
if (upToDate.emailType) {
|
|
441
|
-
// Not eligible for retry
|
|
442
|
-
upToDate.status = structures_1.EmailStatus.Failed;
|
|
443
|
-
await upToDate.save();
|
|
560
|
+
if (!singleRecipientId) {
|
|
561
|
+
console.log('Email already sent, skipping...', upToDate.id);
|
|
444
562
|
return upToDate;
|
|
445
563
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
if (singleRecipientId) {
|
|
567
|
+
// Not possible
|
|
568
|
+
throw new simple_errors_1.SimpleError({
|
|
569
|
+
code: 'invalid_state',
|
|
570
|
+
message: 'Cannot retry single recipient for email that is not yet sent',
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
if (upToDate.status === structures_1.EmailStatus.Sending) {
|
|
574
|
+
// This is an automatic retry.
|
|
575
|
+
if (upToDate.emailType) {
|
|
576
|
+
// Not eligible for retry
|
|
577
|
+
upToDate.status = structures_1.EmailStatus.Failed;
|
|
578
|
+
await upToDate.save();
|
|
579
|
+
return upToDate;
|
|
580
|
+
}
|
|
581
|
+
if (upToDate.createdAt < new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 2)) {
|
|
582
|
+
// Too long
|
|
583
|
+
console.error('Email has been sending for too long. Marking as failed...', upToDate.id);
|
|
584
|
+
upToDate.status = structures_1.EmailStatus.Failed;
|
|
585
|
+
await upToDate.save();
|
|
586
|
+
return upToDate;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
else if (upToDate.status !== structures_1.EmailStatus.Queued) {
|
|
590
|
+
console.error('Email is not queued or sending, cannot send', upToDate.id, upToDate.status);
|
|
451
591
|
return upToDate;
|
|
452
592
|
}
|
|
453
593
|
}
|
|
454
|
-
else if (upToDate.status !== structures_1.EmailStatus.Queued) {
|
|
455
|
-
console.error('Email is not queued or sending, cannot send', upToDate.id, upToDate.status);
|
|
456
|
-
return upToDate;
|
|
457
|
-
}
|
|
458
594
|
const organization = upToDate.organizationId ? await Organization_1.Organization.getByID(upToDate.organizationId) : null;
|
|
459
595
|
let from = upToDate.getDefaultFromAddress(organization);
|
|
460
596
|
let replyTo = upToDate.getFromAddress();
|
|
461
|
-
const attachments = [];
|
|
462
597
|
let succeededCount = 0;
|
|
463
598
|
let softFailedCount = 0;
|
|
464
599
|
let failedCount = 0;
|
|
@@ -478,7 +613,9 @@ class Email extends sql_1.QueryableModel {
|
|
|
478
613
|
replyTo = null;
|
|
479
614
|
}
|
|
480
615
|
abort.throwIfAborted();
|
|
481
|
-
|
|
616
|
+
if (!singleRecipientId) {
|
|
617
|
+
upToDate.status = structures_1.EmailStatus.Sending;
|
|
618
|
+
}
|
|
482
619
|
upToDate.sentAt = upToDate.sentAt ?? new Date();
|
|
483
620
|
await upToDate.save();
|
|
484
621
|
// Create recipients if not yet created
|
|
@@ -503,67 +640,17 @@ class Email extends sql_1.QueryableModel {
|
|
|
503
640
|
}
|
|
504
641
|
// Create a buffer of all attachments
|
|
505
642
|
if (upToDate.sendAsEmail === true) {
|
|
506
|
-
|
|
507
|
-
if (!attachment.content && !attachment.file) {
|
|
508
|
-
console.warn('Attachment without content found, skipping', attachment);
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
let filename = $t('b1291584-d2ad-4ebd-88ed-cbda4f3755b4');
|
|
512
|
-
if (attachment.contentType === 'application/pdf') {
|
|
513
|
-
// tmp solution for pdf only
|
|
514
|
-
filename += '.pdf';
|
|
515
|
-
}
|
|
516
|
-
if (attachment.file?.name) {
|
|
517
|
-
filename = attachment.file.name.toLowerCase().replace(/[^a-z0-9.]+/g, '-').replace(/^-+/, '').replace(/-+$/, '');
|
|
518
|
-
}
|
|
519
|
-
// Correct file name if needed
|
|
520
|
-
if (attachment.filename) {
|
|
521
|
-
filename = attachment.filename.toLowerCase().replace(/[^a-z0-9.]+/g, '-').replace(/^-+/, '').replace(/-+$/, '');
|
|
522
|
-
}
|
|
523
|
-
if (attachment.content) {
|
|
524
|
-
attachments.push({
|
|
525
|
-
filename: filename,
|
|
526
|
-
content: attachment.content,
|
|
527
|
-
contentType: attachment.contentType ?? undefined,
|
|
528
|
-
encoding: 'base64',
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
// 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
|
|
533
|
-
const withSigned = await attachment.file.withSignedUrl();
|
|
534
|
-
if (!withSigned || !withSigned.signedUrl) {
|
|
535
|
-
throw new simple_errors_1.SimpleError({
|
|
536
|
-
code: 'attachment_not_found',
|
|
537
|
-
message: 'Attachment not found',
|
|
538
|
-
human: $t(`ce6ddaf0-8347-42c5-b4b7-fbe860c7b7f2`),
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
const filePath = withSigned.signedUrl;
|
|
542
|
-
let fileBuffer = null;
|
|
543
|
-
try {
|
|
544
|
-
const response = await fetch(filePath);
|
|
545
|
-
fileBuffer = Buffer.from(await response.arrayBuffer());
|
|
546
|
-
}
|
|
547
|
-
catch (e) {
|
|
548
|
-
throw new simple_errors_1.SimpleError({
|
|
549
|
-
code: 'attachment_not_found',
|
|
550
|
-
message: 'Attachment not found',
|
|
551
|
-
human: $t(`ce6ddaf0-8347-42c5-b4b7-fbe860c7b7f2`),
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
attachments.push({
|
|
555
|
-
filename: filename,
|
|
556
|
-
contentType: attachment.contentType ?? undefined,
|
|
557
|
-
content: fileBuffer,
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
}
|
|
643
|
+
const attachments = await upToDate.loadAttachments();
|
|
561
644
|
// Start actually sending in batches of recipients that are not yet sent
|
|
562
645
|
let idPointer = '';
|
|
563
646
|
const batchSize = 100;
|
|
564
647
|
let isSavingStatus = false;
|
|
565
648
|
let lastStatusSave = new Date();
|
|
566
649
|
async function saveStatus() {
|
|
650
|
+
if (singleRecipientId) {
|
|
651
|
+
// Don't save during looping
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
567
654
|
if (!upToDate) {
|
|
568
655
|
return;
|
|
569
656
|
}
|
|
@@ -621,71 +708,41 @@ class Email extends sql_1.QueryableModel {
|
|
|
621
708
|
skipped++;
|
|
622
709
|
continue;
|
|
623
710
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
let resolved = false;
|
|
630
|
-
const callback = async (error) => {
|
|
631
|
-
if (resolved) {
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
resolved = true;
|
|
635
|
-
try {
|
|
636
|
-
if (error === null) {
|
|
637
|
-
// Mark saved
|
|
638
|
-
recipient.sentAt = new Date();
|
|
639
|
-
// Update repacements that have been generated
|
|
640
|
-
recipient.replacements = virtualRecipient.replacements;
|
|
641
|
-
succeededCount += 1;
|
|
642
|
-
await recipient.save();
|
|
643
|
-
await saveStatus();
|
|
711
|
+
if (singleRecipientId) {
|
|
712
|
+
if (recipient.id !== singleRecipientId) {
|
|
713
|
+
// Failed or soft-failed
|
|
714
|
+
if (recipient.failError && (0, structures_1.isSoftEmailRecipientError)(recipient.failError)) {
|
|
715
|
+
softFailedCount += 1;
|
|
644
716
|
}
|
|
645
717
|
else {
|
|
646
|
-
|
|
647
|
-
recipient.failErrorMessage = error.message;
|
|
648
|
-
recipient.failError = errorToSimpleErrors(error);
|
|
649
|
-
recipient.firstFailedAt = recipient.firstFailedAt ?? new Date();
|
|
650
|
-
recipient.lastFailedAt = new Date();
|
|
651
|
-
if ((0, structures_1.isSoftEmailRecipientError)(recipient.failError)) {
|
|
652
|
-
softFailedCount += 1;
|
|
653
|
-
}
|
|
654
|
-
else {
|
|
655
|
-
failedCount += 1;
|
|
656
|
-
}
|
|
657
|
-
await recipient.save();
|
|
658
|
-
await saveStatus();
|
|
718
|
+
failedCount += 1;
|
|
659
719
|
}
|
|
720
|
+
skipped++;
|
|
721
|
+
await saveStatus();
|
|
722
|
+
continue;
|
|
660
723
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}
|
|
664
|
-
promiseResolve();
|
|
665
|
-
};
|
|
666
|
-
// Do send the email
|
|
667
|
-
// Create e-mail builder
|
|
668
|
-
const builder = await (0, EmailBuilder_1.getEmailBuilder)(organization ?? null, {
|
|
669
|
-
recipients: [
|
|
670
|
-
virtualRecipient,
|
|
671
|
-
],
|
|
724
|
+
}
|
|
725
|
+
const promise = upToDate.sendSingleRecipient(recipient, organization ?? null, {
|
|
672
726
|
from,
|
|
673
727
|
replyTo,
|
|
674
|
-
subject: upToDate.subject,
|
|
675
|
-
html: upToDate.html,
|
|
676
|
-
type: upToDate.emailType ? 'transactional' : 'broadcast',
|
|
677
728
|
attachments,
|
|
678
|
-
callback(error) {
|
|
679
|
-
callback(error).catch(console.error);
|
|
680
|
-
},
|
|
681
|
-
headers: {
|
|
682
|
-
'X-Email-Id': upToDate.id,
|
|
683
|
-
'X-Email-Recipient-Id': recipient.id,
|
|
684
|
-
},
|
|
685
729
|
});
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
730
|
+
sendingPromises.push(promise.then(async () => {
|
|
731
|
+
if (recipient.sentAt) {
|
|
732
|
+
succeededCount += 1;
|
|
733
|
+
await saveStatus();
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
// Failed or soft-failed
|
|
737
|
+
if (recipient.failError && (0, structures_1.isSoftEmailRecipientError)(recipient.failError)) {
|
|
738
|
+
softFailedCount += 1;
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
failedCount += 1;
|
|
742
|
+
}
|
|
743
|
+
await saveStatus();
|
|
744
|
+
}
|
|
745
|
+
}));
|
|
689
746
|
}
|
|
690
747
|
if (sendingPromises.length > 0 || skipped > 0) {
|
|
691
748
|
await Promise.all(sendingPromises);
|
|
@@ -700,38 +757,42 @@ class Email extends sql_1.QueryableModel {
|
|
|
700
757
|
if (!upToDate) {
|
|
701
758
|
throw e;
|
|
702
759
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
760
|
+
if (!singleRecipientId) {
|
|
761
|
+
upToDate.succeededCount = succeededCount;
|
|
762
|
+
upToDate.softFailedCount = softFailedCount;
|
|
763
|
+
upToDate.failedCount = failedCount;
|
|
764
|
+
if ((0, queues_1.isAbortedError)(e) || (((0, simple_errors_1.isSimpleError)(e) || (0, simple_errors_1.isSimpleErrors)(e)) && e.hasCode('SHUTDOWN'))) {
|
|
765
|
+
// Keep sending status: we'll resume after the reboot
|
|
766
|
+
await upToDate.save();
|
|
767
|
+
throw e;
|
|
768
|
+
}
|
|
769
|
+
upToDate.emailErrors = errorToSimpleErrors(e);
|
|
770
|
+
upToDate.status = structures_1.EmailStatus.Failed;
|
|
708
771
|
await upToDate.save();
|
|
709
|
-
throw e;
|
|
710
772
|
}
|
|
711
|
-
upToDate.emailErrors = errorToSimpleErrors(e);
|
|
712
|
-
upToDate.status = structures_1.EmailStatus.Failed;
|
|
713
|
-
await upToDate.save();
|
|
714
773
|
throw e;
|
|
715
774
|
}
|
|
716
|
-
if (
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
upToDate.
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
775
|
+
if (!singleRecipientId) {
|
|
776
|
+
if (upToDate.sendAsEmail && upToDate.emailRecipientsCount === 0 && upToDate.userId === null) {
|
|
777
|
+
// We only delete automated emails (email type) if they have no recipients
|
|
778
|
+
console.log('No recipients found for email ', upToDate.id, ' deleting...');
|
|
779
|
+
await upToDate.delete();
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
782
|
+
console.log('Finished sending email', upToDate.id);
|
|
783
|
+
// Mark email as sent
|
|
784
|
+
if (upToDate.sendAsEmail && !upToDate.showInMemberPortal && (succeededCount + failedCount + softFailedCount) === 0) {
|
|
785
|
+
upToDate.status = structures_1.EmailStatus.Failed;
|
|
786
|
+
upToDate.emailErrors = new simple_errors_1.SimpleErrors(new simple_errors_1.SimpleError({
|
|
787
|
+
code: 'no_recipients',
|
|
788
|
+
message: 'No recipients',
|
|
789
|
+
human: $t(`9fe3de8e-090c-4949-97da-4810ce9e61c7`),
|
|
790
|
+
}));
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
upToDate.status = structures_1.EmailStatus.Sent;
|
|
794
|
+
upToDate.emailErrors = null;
|
|
795
|
+
}
|
|
735
796
|
}
|
|
736
797
|
upToDate.succeededCount = succeededCount;
|
|
737
798
|
upToDate.softFailedCount = softFailedCount;
|