emailengine-app 2.61.4 → 2.62.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/CHANGELOG.md +87 -0
- package/data/google-crawlers.json +1 -1
- package/lib/account.js +20 -7
- package/lib/api-routes/account-routes.js +28 -5
- package/lib/api-routes/chat-routes.js +1 -1
- package/lib/api-routes/export-routes.js +316 -0
- package/lib/api-routes/message-routes.js +28 -23
- package/lib/api-routes/template-routes.js +28 -7
- package/lib/arf-detect.js +1 -1
- package/lib/consts.js +16 -0
- package/lib/db.js +3 -0
- package/lib/email-client/base-client.js +6 -4
- package/lib/email-client/gmail-client.js +204 -33
- package/lib/email-client/imap/mailbox.js +99 -8
- package/lib/email-client/imap/subconnection.js +5 -5
- package/lib/email-client/imap-client.js +76 -16
- package/lib/email-client/message-builder.js +3 -1
- package/lib/email-client/notification-handler.js +12 -9
- package/lib/email-client/outlook-client.js +362 -69
- package/lib/email-client/smtp-pool-manager.js +1 -1
- package/lib/export.js +528 -0
- package/lib/oauth/gmail.js +21 -13
- package/lib/oauth/mail-ru.js +23 -10
- package/lib/oauth/outlook.js +26 -16
- package/lib/oauth/pubsub/google.js +5 -0
- package/lib/routes-ui.js +236 -2
- package/lib/schemas.js +260 -80
- package/lib/stream-encrypt.js +263 -0
- package/lib/tools.js +30 -4
- package/lib/ui-routes/account-routes.js +24 -1
- package/lib/ui-routes/admin-config-routes.js +11 -4
- package/lib/ui-routes/admin-entities-routes.js +18 -0
- package/lib/webhooks.js +16 -20
- package/package.json +17 -17
- package/sbom.json +1 -1
- package/server.js +41 -5
- package/static/js/ace/ace.js +1 -1
- package/static/js/ace/ext-language_tools.js +1 -1
- package/static/licenses.html +47 -127
- package/translations/de.mo +0 -0
- package/translations/de.po +63 -36
- package/translations/en.mo +0 -0
- package/translations/en.po +64 -37
- package/translations/et.mo +0 -0
- package/translations/et.po +63 -36
- package/translations/fr.mo +0 -0
- package/translations/fr.po +63 -36
- package/translations/ja.mo +0 -0
- package/translations/ja.po +63 -36
- package/translations/messages.pot +88 -55
- package/translations/nl.mo +0 -0
- package/translations/nl.po +63 -36
- package/translations/pl.mo +0 -0
- package/translations/pl.po +63 -36
- package/views/accounts/account.hbs +375 -2
- package/views/config/service.hbs +35 -0
- package/workers/api.js +124 -45
- package/workers/documents.js +1 -0
- package/workers/export.js +926 -0
- package/workers/imap.js +29 -0
- package/workers/submit.js +25 -5
- package/workers/webhooks.js +11 -2
package/lib/schemas.js
CHANGED
|
@@ -43,7 +43,10 @@ const settingsSchema = {
|
|
|
43
43
|
.example('https://api.example.com/email/webhooks')
|
|
44
44
|
.description('Target URL that will receive webhook notifications via POST requests'),
|
|
45
45
|
|
|
46
|
-
webhookEvents: Joi.array()
|
|
46
|
+
webhookEvents: Joi.array()
|
|
47
|
+
.items(Joi.string().max(256).example('messageNew').label('WebhookEventType'))
|
|
48
|
+
.description('List of event types that will trigger webhook notifications')
|
|
49
|
+
.label('WebhookEvents'),
|
|
47
50
|
|
|
48
51
|
webhooksCustomHeaders: Joi.array()
|
|
49
52
|
.items(
|
|
@@ -61,8 +64,9 @@ const settingsSchema = {
|
|
|
61
64
|
.label('WebhooksCustomHeaders'),
|
|
62
65
|
|
|
63
66
|
notifyHeaders: Joi.array()
|
|
64
|
-
.items(Joi.string().max(256).example('List-ID'))
|
|
65
|
-
.description('Email headers to include in webhook payloads for additional context')
|
|
67
|
+
.items(Joi.string().max(256).example('List-ID').label('NotifyHeaderEntry'))
|
|
68
|
+
.description('Email headers to include in webhook payloads for additional context')
|
|
69
|
+
.label('NotifyHeaders'),
|
|
66
70
|
|
|
67
71
|
/* ────────────── URLs ────────────── */
|
|
68
72
|
|
|
@@ -105,9 +109,8 @@ const settingsSchema = {
|
|
|
105
109
|
.trim()
|
|
106
110
|
.valid('full', 'fast')
|
|
107
111
|
.example('full')
|
|
108
|
-
.description(
|
|
109
|
-
|
|
110
|
-
),
|
|
112
|
+
.description('IMAP indexing strategy:\n * full - Detect new, changed, and deleted messages (slower but complete)\n * fast - Detect only new messages')
|
|
113
|
+
.label('ImapIndexer'),
|
|
111
114
|
|
|
112
115
|
resolveGmailCategories: Joi.boolean()
|
|
113
116
|
.truthy('Y', 'true', '1', 'on')
|
|
@@ -373,17 +376,24 @@ const settingsSchema = {
|
|
|
373
376
|
imapStrategy: Joi.string()
|
|
374
377
|
.empty('')
|
|
375
378
|
.valid(...ADDRESS_STRATEGIES.map(entry => entry.key))
|
|
376
|
-
.description('IP address selection strategy for outbound IMAP connections when multiple local addresses are available')
|
|
379
|
+
.description('IP address selection strategy for outbound IMAP connections when multiple local addresses are available')
|
|
380
|
+
.label('ImapStrategy'),
|
|
377
381
|
|
|
378
382
|
smtpStrategy: Joi.string()
|
|
379
383
|
.empty('')
|
|
380
384
|
.valid(...ADDRESS_STRATEGIES.map(entry => entry.key))
|
|
381
|
-
.description('IP address selection strategy for outbound SMTP connections when multiple local addresses are available')
|
|
385
|
+
.description('IP address selection strategy for outbound SMTP connections when multiple local addresses are available')
|
|
386
|
+
.label('SmtpStrategy'),
|
|
382
387
|
|
|
383
388
|
localAddresses: Joi.array()
|
|
384
|
-
.items(
|
|
389
|
+
.items(
|
|
390
|
+
Joi.string()
|
|
391
|
+
.ip({ version: ['ipv4', 'ipv6'], cidr: 'forbidden' })
|
|
392
|
+
.label('LocalAddressEntry')
|
|
393
|
+
)
|
|
385
394
|
.single()
|
|
386
|
-
.description('List of local IP addresses to use for outbound connections (requires appropriate network configuration)')
|
|
395
|
+
.description('List of local IP addresses to use for outbound connections (requires appropriate network configuration)')
|
|
396
|
+
.label('LocalAddresses'),
|
|
387
397
|
|
|
388
398
|
/* ────────────── Built-in SMTP Server ────────────── */
|
|
389
399
|
|
|
@@ -473,7 +483,8 @@ const settingsSchema = {
|
|
|
473
483
|
.max(100)
|
|
474
484
|
.example('fr')
|
|
475
485
|
.valid(...locales.map(l => l.locale))
|
|
476
|
-
.description('Default language/locale for the user interface')
|
|
486
|
+
.description('Default language/locale for the user interface')
|
|
487
|
+
.label('SettingsLocale'),
|
|
477
488
|
timezone: Joi.string().max(100).example('Europe/Tallinn').description('Default timezone for date/time display (IANA timezone identifier)'),
|
|
478
489
|
pageBrandName: Joi.string().allow('', null).max(1024).example('EmailEngine').description('Brand name displayed in page titles'),
|
|
479
490
|
|
|
@@ -494,13 +505,39 @@ const settingsSchema = {
|
|
|
494
505
|
.allow('')
|
|
495
506
|
.uri({ scheme: ['http', 'https', 'mailto'], allowRelative: false })
|
|
496
507
|
.example('https://github.com/postalsys/emailengine/issues')
|
|
497
|
-
.description('Support URL advertised via IMAP ID extension')
|
|
508
|
+
.description('Support URL advertised via IMAP ID extension'),
|
|
509
|
+
|
|
510
|
+
/* ────────────── Export ────────────── */
|
|
511
|
+
|
|
512
|
+
exportMaxConcurrent: Joi.number().integer().min(1).max(100).example(2).description('Maximum concurrent exports per account'),
|
|
513
|
+
|
|
514
|
+
exportMaxGlobalConcurrent: Joi.number().integer().min(1).max(100).example(8).description('Maximum concurrent exports system-wide across all accounts'),
|
|
515
|
+
|
|
516
|
+
gmailExportBatchSize: Joi.number()
|
|
517
|
+
.integer()
|
|
518
|
+
.min(1)
|
|
519
|
+
.max(50)
|
|
520
|
+
.example(10)
|
|
521
|
+
.description('Number of parallel message fetch requests for Gmail export operations (default: 10, max: 50)'),
|
|
522
|
+
|
|
523
|
+
outlookExportBatchSize: Joi.number()
|
|
524
|
+
.integer()
|
|
525
|
+
.min(1)
|
|
526
|
+
.max(20)
|
|
527
|
+
.example(20)
|
|
528
|
+
.description('Number of messages per batch request for Outlook export operations (default: 20, max: 20 - MS Graph API limit)'),
|
|
529
|
+
|
|
530
|
+
exportMaxMessages: Joi.number().integer().min(1).max(10000000).example(500000).description('Maximum number of messages per export (default: 500000)'),
|
|
531
|
+
|
|
532
|
+
exportMaxSize: Joi.number().integer().min(1).example(10737418240).description('Maximum export file size in bytes (default: 10GB)')
|
|
498
533
|
};
|
|
499
534
|
|
|
500
535
|
const addressSchema = Joi.object({
|
|
501
536
|
name: Joi.string().trim().empty('').max(256).example('Some Name').description('Display name for the email address'),
|
|
502
537
|
address: Joi.string().email({ ignoreLength: false }).example('user@example.com').required().description('Email address')
|
|
503
|
-
})
|
|
538
|
+
})
|
|
539
|
+
.description('Email address with optional display name')
|
|
540
|
+
.label('EmailAddress');
|
|
504
541
|
|
|
505
542
|
// generate a list of boolean values
|
|
506
543
|
const settingsQuerySchema = Object.fromEntries(
|
|
@@ -527,6 +564,7 @@ const imapSchema = {
|
|
|
527
564
|
.max(4 * 4096)
|
|
528
565
|
.example(false)
|
|
529
566
|
.description('OAuth2 access token (when using OAuth2 instead of password authentication)')
|
|
567
|
+
.label('ImapAccessToken')
|
|
530
568
|
})
|
|
531
569
|
.allow(false)
|
|
532
570
|
.when('useAuthServer', {
|
|
@@ -535,7 +573,7 @@ const imapSchema = {
|
|
|
535
573
|
otherwise: Joi.optional()
|
|
536
574
|
})
|
|
537
575
|
.description('Authentication credentials for the IMAP server')
|
|
538
|
-
.label('
|
|
576
|
+
.label('ImapAuthentication'),
|
|
539
577
|
|
|
540
578
|
useAuthServer: Joi.boolean().example(false).description('Use external authentication server to retrieve credentials dynamically'),
|
|
541
579
|
|
|
@@ -566,7 +604,7 @@ const imapSchema = {
|
|
|
566
604
|
})
|
|
567
605
|
.unknown()
|
|
568
606
|
.description('Advanced TLS configuration options')
|
|
569
|
-
.label('
|
|
607
|
+
.label('ImapTlsOptions'),
|
|
570
608
|
resyncDelay: Joi.number().integer().example(RESYNC_DELAY).description('Delay in seconds between full mailbox resynchronizations').default(RESYNC_DELAY),
|
|
571
609
|
disabled: Joi.boolean().example(false).description('Temporarily disable IMAP operations for this account'),
|
|
572
610
|
|
|
@@ -614,6 +652,7 @@ const smtpSchema = {
|
|
|
614
652
|
.max(4 * 4096)
|
|
615
653
|
.example(false)
|
|
616
654
|
.description('OAuth2 access token (when using OAuth2 instead of password authentication)')
|
|
655
|
+
.label('SmtpAccessToken')
|
|
617
656
|
})
|
|
618
657
|
.allow(false)
|
|
619
658
|
.when('useAuthServer', {
|
|
@@ -622,7 +661,7 @@ const smtpSchema = {
|
|
|
622
661
|
otherwise: Joi.optional()
|
|
623
662
|
})
|
|
624
663
|
.description('Authentication credentials for the SMTP server')
|
|
625
|
-
.label('
|
|
664
|
+
.label('SmtpAuthentication'),
|
|
626
665
|
|
|
627
666
|
useAuthServer: Joi.boolean().example(false).description('Use external authentication server to retrieve credentials dynamically'),
|
|
628
667
|
|
|
@@ -644,7 +683,7 @@ const smtpSchema = {
|
|
|
644
683
|
})
|
|
645
684
|
.unknown()
|
|
646
685
|
.description('Advanced TLS configuration options')
|
|
647
|
-
.label('
|
|
686
|
+
.label('SmtpTlsOptions')
|
|
648
687
|
};
|
|
649
688
|
|
|
650
689
|
const oauth2AuthSchema = Joi.object({
|
|
@@ -678,9 +717,10 @@ const oauth2Schema = {
|
|
|
678
717
|
is: true,
|
|
679
718
|
then: Joi.optional(),
|
|
680
719
|
otherwise: Joi.optional().valid(false, null)
|
|
681
|
-
})
|
|
720
|
+
})
|
|
721
|
+
.label('OAuth2RedirectUrl'),
|
|
682
722
|
|
|
683
|
-
provider: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 Application ID configured in EmailEngine'),
|
|
723
|
+
provider: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 Application ID configured in EmailEngine').label('OAuth2AppProvider'),
|
|
684
724
|
|
|
685
725
|
auth: oauth2AuthSchema,
|
|
686
726
|
|
|
@@ -689,7 +729,8 @@ const oauth2Schema = {
|
|
|
689
729
|
accessToken: Joi.string()
|
|
690
730
|
.max(4 * 4096)
|
|
691
731
|
.example('ya29.a0ARrdaM8a...')
|
|
692
|
-
.description('OAuth2 access token for the email account')
|
|
732
|
+
.description('OAuth2 access token for the email account')
|
|
733
|
+
.label('OAuth2SchemaAccessToken'),
|
|
693
734
|
refreshToken: Joi.string()
|
|
694
735
|
.max(4 * 4096)
|
|
695
736
|
.example('1//09Ie3CtORQYm...')
|
|
@@ -728,6 +769,7 @@ const imapUpdateSchema = {
|
|
|
728
769
|
.max(4 * 4096)
|
|
729
770
|
.example(false)
|
|
730
771
|
.description('OAuth2 access token (when using OAuth2 instead of password authentication)')
|
|
772
|
+
.label('ImapUpdateAccessToken')
|
|
731
773
|
})
|
|
732
774
|
.allow(false)
|
|
733
775
|
.when('useAuthServer', {
|
|
@@ -736,7 +778,7 @@ const imapUpdateSchema = {
|
|
|
736
778
|
otherwise: Joi.optional()
|
|
737
779
|
})
|
|
738
780
|
.description('Authentication credentials for the IMAP server')
|
|
739
|
-
.label('
|
|
781
|
+
.label('ImapUpdateAuthentication'),
|
|
740
782
|
|
|
741
783
|
useAuthServer: Joi.boolean().example(false).description('Use external authentication server to retrieve credentials dynamically'),
|
|
742
784
|
|
|
@@ -749,7 +791,7 @@ const imapUpdateSchema = {
|
|
|
749
791
|
})
|
|
750
792
|
.unknown()
|
|
751
793
|
.description('Advanced TLS configuration options')
|
|
752
|
-
.label('
|
|
794
|
+
.label('ImapUpdateTlsOptions'),
|
|
753
795
|
resyncDelay: Joi.number().integer().example(RESYNC_DELAY).description('Delay in seconds between full mailbox resynchronizations'),
|
|
754
796
|
|
|
755
797
|
disabled: Joi.boolean().example(false).description('Temporarily disable IMAP operations for this account'),
|
|
@@ -779,6 +821,7 @@ const smtpUpdateSchema = {
|
|
|
779
821
|
.max(4 * 4096)
|
|
780
822
|
.example(false)
|
|
781
823
|
.description('OAuth2 access token (when using OAuth2 instead of password authentication)')
|
|
824
|
+
.label('SmtpUpdateAccessToken')
|
|
782
825
|
})
|
|
783
826
|
.allow(false)
|
|
784
827
|
.when('useAuthServer', {
|
|
@@ -787,7 +830,7 @@ const smtpUpdateSchema = {
|
|
|
787
830
|
otherwise: Joi.optional()
|
|
788
831
|
})
|
|
789
832
|
.description('Authentication credentials for the SMTP server')
|
|
790
|
-
.label('
|
|
833
|
+
.label('SmtpUpdateAuthentication'),
|
|
791
834
|
|
|
792
835
|
useAuthServer: Joi.boolean().example(false).description('Use external authentication server to retrieve credentials dynamically'),
|
|
793
836
|
|
|
@@ -800,14 +843,14 @@ const smtpUpdateSchema = {
|
|
|
800
843
|
})
|
|
801
844
|
.unknown()
|
|
802
845
|
.description('Advanced TLS configuration options')
|
|
803
|
-
.label('
|
|
846
|
+
.label('SmtpUpdateTlsOptions')
|
|
804
847
|
};
|
|
805
848
|
|
|
806
849
|
const oauth2UpdateSchema = {
|
|
807
850
|
partial: partialSchema,
|
|
808
851
|
|
|
809
852
|
authorize: Joi.boolean().example(false).description('Request an OAuth2 authorization URL instead of directly configuring credentials'),
|
|
810
|
-
provider: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 Application ID configured in EmailEngine'),
|
|
853
|
+
provider: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 Application ID configured in EmailEngine').label('OAuth2UpdateAppProvider'),
|
|
811
854
|
|
|
812
855
|
auth: oauth2AuthSchema,
|
|
813
856
|
|
|
@@ -816,7 +859,8 @@ const oauth2UpdateSchema = {
|
|
|
816
859
|
accessToken: Joi.string()
|
|
817
860
|
.max(4 * 4096)
|
|
818
861
|
.example('ya29.a0ARrdaM8a...')
|
|
819
|
-
.description('OAuth2 access token for the email account')
|
|
862
|
+
.description('OAuth2 access token for the email account')
|
|
863
|
+
.label('OAuth2UpdateAccessToken'),
|
|
820
864
|
refreshToken: Joi.string()
|
|
821
865
|
.max(4 * 4096)
|
|
822
866
|
.example('1//09Ie3CtORQYm...')
|
|
@@ -839,7 +883,8 @@ const attachmentSchema = Joi.object({
|
|
|
839
883
|
encodedSize: Joi.number()
|
|
840
884
|
.integer()
|
|
841
885
|
.example(48)
|
|
842
|
-
.description('Size of the attachment as stored in the email (base64 encoded). The actual decoded file size is approximately 75% of this value.')
|
|
886
|
+
.description('Size of the attachment as stored in the email (base64 encoded). The actual decoded file size is approximately 75% of this value.')
|
|
887
|
+
.label('AttachmentEncodedSize'),
|
|
843
888
|
embedded: Joi.boolean().example(true).description('Whether the attachment is embedded in the HTML content'),
|
|
844
889
|
inline: Joi.boolean().example(true).description('Whether the attachment should be displayed inline rather than as a download'),
|
|
845
890
|
contentId: Joi.string().example('<unique-image-id@localhost>').description('Content-ID header value used for embedding images in HTML'),
|
|
@@ -892,7 +937,9 @@ const messageEntrySchema = Joi.object({
|
|
|
892
937
|
encodedSize: Joi.object({
|
|
893
938
|
plain: Joi.number().integer().example(1013).description('Size of the plain text part in bytes'),
|
|
894
939
|
html: Joi.number().integer().example(1013).description('Size of the HTML part in bytes')
|
|
895
|
-
})
|
|
940
|
+
})
|
|
941
|
+
.description('Sizes of different message parts')
|
|
942
|
+
.label('TextEncodedSize')
|
|
896
943
|
}).label('TextInfo'),
|
|
897
944
|
|
|
898
945
|
preview: Joi.string().description('Short preview of the message content')
|
|
@@ -947,7 +994,9 @@ const messageDetailsSchema = Joi.object({
|
|
|
947
994
|
encodedSize: Joi.object({
|
|
948
995
|
plain: Joi.number().integer().example(1013).description('Size of the plain text part in bytes'),
|
|
949
996
|
html: Joi.number().integer().example(1013).description('Size of the HTML part in bytes')
|
|
950
|
-
})
|
|
997
|
+
})
|
|
998
|
+
.description('Sizes of different message parts')
|
|
999
|
+
.label('TextDetailEncodedSize'),
|
|
951
1000
|
plain: Joi.string().example('Hello from myself!').description('Plain text version of the message'),
|
|
952
1001
|
html: Joi.string().example('<p>Hello from myself!</p>').description('HTML version of the message'),
|
|
953
1002
|
hasMore: Joi.boolean().example(false).description('Whether the message content was truncated (true if more content is available via separate API call)')
|
|
@@ -958,7 +1007,7 @@ const messageDetailsSchema = Joi.object({
|
|
|
958
1007
|
Joi.object({
|
|
959
1008
|
message: Joi.string().max(256).required().example('AAAAAQAACnA').description('EmailEngine identifier of the bounce notification'),
|
|
960
1009
|
recipient: Joi.string().email().example('recipient@example.com').description('Email address that bounced'),
|
|
961
|
-
action: Joi.string().example('failed').description('Bounce action (failed, delayed, etc.)'),
|
|
1010
|
+
action: Joi.string().example('failed').description('Bounce action (failed, delayed, etc.)').label('BounceAction'),
|
|
962
1011
|
response: Joi.object({
|
|
963
1012
|
message: Joi.string().example('550 5.1.1 No such user').description('Error message from the receiving server'),
|
|
964
1013
|
status: Joi.string().example('5.1.1').description('SMTP status code')
|
|
@@ -1027,21 +1076,23 @@ const mailboxesSchema = Joi.array()
|
|
|
1027
1076
|
)
|
|
1028
1077
|
.label('MailboxesList');
|
|
1029
1078
|
|
|
1030
|
-
const shortMailboxesSchema = Joi.array()
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
.
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
)
|
|
1079
|
+
const shortMailboxesSchema = Joi.array()
|
|
1080
|
+
.items(
|
|
1081
|
+
Joi.object({
|
|
1082
|
+
path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to the mailbox').label('MailboxPath'),
|
|
1083
|
+
delimiter: Joi.string().example('/').description('Hierarchy delimiter character used in paths'),
|
|
1084
|
+
parentPath: Joi.string().required().example('Kalender').description('Path to the parent mailbox').label('MailboxParentPath'),
|
|
1085
|
+
name: Joi.string().required().example('Sünnipäevad').description('Display name of the mailbox').label('MailboxName'),
|
|
1086
|
+
listed: Joi.boolean().example(true).description('Whether this mailbox appears in LIST command results').label('MailboxListed'),
|
|
1087
|
+
subscribed: Joi.boolean().example(true).description('Whether the user is subscribed to this mailbox').label('MailboxSubscribed'),
|
|
1088
|
+
specialUse: Joi.string()
|
|
1089
|
+
.example('\\Sent')
|
|
1090
|
+
.valid('\\All', '\\Archive', '\\Drafts', '\\Flagged', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
|
|
1091
|
+
.description('Special folder type (Inbox, Sent, Drafts, etc.)')
|
|
1092
|
+
.label('MailboxSpecialUse')
|
|
1093
|
+
}).label('MailboxShortResponseItem')
|
|
1094
|
+
)
|
|
1095
|
+
.label('ShortMailboxes');
|
|
1045
1096
|
|
|
1046
1097
|
const licenseSchema = Joi.object({
|
|
1047
1098
|
active: Joi.boolean().example(true).description('Whether a valid license is currently active'),
|
|
@@ -1056,18 +1107,25 @@ const licenseSchema = Joi.object({
|
|
|
1056
1107
|
.allow(false)
|
|
1057
1108
|
.label('LicenseDetails'),
|
|
1058
1109
|
suspended: Joi.boolean().example(false).description('Whether email operations are suspended due to license issues')
|
|
1059
|
-
});
|
|
1110
|
+
}).label('LicenseInfo');
|
|
1060
1111
|
|
|
1061
1112
|
const lastErrorSchema = Joi.object({
|
|
1062
|
-
response: Joi.string()
|
|
1113
|
+
response: Joi.string()
|
|
1114
|
+
.example('Token request failed for gmail (refresh_token, HTTP 400): invalid_grant - Token has been expired or revoked.')
|
|
1115
|
+
.description('Human-readable error message'),
|
|
1063
1116
|
serverResponseCode: Joi.string().example('OauthRenewError').description('Error code or classification'),
|
|
1064
1117
|
tokenRequest: Joi.object({
|
|
1065
|
-
grant: Joi.string()
|
|
1066
|
-
|
|
1067
|
-
|
|
1118
|
+
grant: Joi.string()
|
|
1119
|
+
.valid('refresh_token', 'authorization_code')
|
|
1120
|
+
.example('refresh_token')
|
|
1121
|
+
.description('OAuth2 grant type being requested')
|
|
1122
|
+
.label('OAuthGrant'),
|
|
1123
|
+
provider: Joi.string().max(256).example('gmail').description('OAuth2 provider name').label('OAuthProvider'),
|
|
1124
|
+
status: Joi.number().integer().example(400).description('HTTP status code from the OAuth2 server').label('OAuthStatusCode'),
|
|
1068
1125
|
clientId: Joi.string()
|
|
1069
1126
|
.example('1023289917884-h3nu00e9cb7h252e24c23sv19l8k57ah.apps.googleusercontent.com')
|
|
1070
|
-
.description('OAuth2 client ID used for authentication')
|
|
1127
|
+
.description('OAuth2 client ID used for authentication')
|
|
1128
|
+
.label('OAuthClientId'),
|
|
1071
1129
|
scopes: Joi.array()
|
|
1072
1130
|
.items(Joi.string().example('https://mail.google.com/').label('ScopeEntry').description('OAuth2 permission scope'))
|
|
1073
1131
|
.description('Requested OAuth2 permission scopes')
|
|
@@ -1079,7 +1137,10 @@ const lastErrorSchema = Joi.object({
|
|
|
1079
1137
|
})
|
|
1080
1138
|
.description('Raw error response from the OAuth2 server')
|
|
1081
1139
|
.unknown()
|
|
1082
|
-
|
|
1140
|
+
.label('OAuthTokenErrorResponse')
|
|
1141
|
+
})
|
|
1142
|
+
.description('Details about the failed OAuth2 token request')
|
|
1143
|
+
.label('OAuthTokenRequestError')
|
|
1083
1144
|
}).label('AccountErrorEntry');
|
|
1084
1145
|
|
|
1085
1146
|
const templateSchemas = {
|
|
@@ -1266,17 +1327,24 @@ const accountSchemas = {
|
|
|
1266
1327
|
.description(
|
|
1267
1328
|
'Override global IMAP indexing strategy for this account. "full" tracks all changes including deletions, "fast" only detects new messages.'
|
|
1268
1329
|
)
|
|
1269
|
-
.label('
|
|
1330
|
+
.label('AccountImapIndexer')
|
|
1270
1331
|
};
|
|
1271
1332
|
|
|
1272
|
-
const googleProjectIdSchema = Joi.string()
|
|
1333
|
+
const googleProjectIdSchema = Joi.string()
|
|
1334
|
+
.trim()
|
|
1335
|
+
.allow('', false, null)
|
|
1336
|
+
.max(256)
|
|
1337
|
+
.example('project-name-425411')
|
|
1338
|
+
.description('Google Cloud Project ID')
|
|
1339
|
+
.label('GoogleProjectId');
|
|
1273
1340
|
const googleTopicNameSchema = Joi.string()
|
|
1274
1341
|
.trim()
|
|
1275
1342
|
.allow('', false, null)
|
|
1276
1343
|
.pattern(/^(?!goog)[A-Za-z][A-Za-z0-9\-_.~+%]{2,254}$/)
|
|
1277
1344
|
.max(256)
|
|
1278
1345
|
.example('ee-pub-12345')
|
|
1279
|
-
.description('Google Pub/Sub topic name for Gmail push notifications')
|
|
1346
|
+
.description('Google Pub/Sub topic name for Gmail push notifications')
|
|
1347
|
+
.label('GoogleTopicName');
|
|
1280
1348
|
|
|
1281
1349
|
const googleSubscriptionNameSchema = Joi.string()
|
|
1282
1350
|
.trim()
|
|
@@ -1284,7 +1352,8 @@ const googleSubscriptionNameSchema = Joi.string()
|
|
|
1284
1352
|
.pattern(/^(?!goog)[A-Za-z][A-Za-z0-9\-_.~+%]{2,254}$/)
|
|
1285
1353
|
.max(256)
|
|
1286
1354
|
.example('ee-sub-12345')
|
|
1287
|
-
.description('Google Pub/Sub subscription name')
|
|
1355
|
+
.description('Google Pub/Sub subscription name')
|
|
1356
|
+
.label('GoogleSubscriptionName');
|
|
1288
1357
|
|
|
1289
1358
|
const googleWorkspaceAccountsSchema = Joi.boolean()
|
|
1290
1359
|
.truthy('Y', 'true', '1', 'on')
|
|
@@ -1304,7 +1373,8 @@ const oauthCreateSchema = {
|
|
|
1304
1373
|
.valid(...Object.keys(OAUTH_PROVIDERS))
|
|
1305
1374
|
.example('gmail')
|
|
1306
1375
|
.required()
|
|
1307
|
-
.description('OAuth2 provider type')
|
|
1376
|
+
.description('OAuth2 provider type')
|
|
1377
|
+
.label('CreateOAuth2Provider'),
|
|
1308
1378
|
|
|
1309
1379
|
enabled: Joi.boolean()
|
|
1310
1380
|
.truthy('Y', 'true', '1', 'on')
|
|
@@ -1337,7 +1407,13 @@ const oauthCreateSchema = {
|
|
|
1337
1407
|
.example('boT7Q~dUljnfFdVuqpC11g8nGMjO8kpRAv-ZB')
|
|
1338
1408
|
.description('OAuth2 client secret from the provider'),
|
|
1339
1409
|
|
|
1340
|
-
baseScopes: Joi.string()
|
|
1410
|
+
baseScopes: Joi.string()
|
|
1411
|
+
.empty('')
|
|
1412
|
+
.trim()
|
|
1413
|
+
.valid('imap', 'api', 'pubsub')
|
|
1414
|
+
.example('imap')
|
|
1415
|
+
.description('Connection type (IMAP, API, or Pub/Sub)')
|
|
1416
|
+
.label('OAuth2BaseScopes'),
|
|
1341
1417
|
|
|
1342
1418
|
pubSubApp: Joi.string()
|
|
1343
1419
|
.empty('')
|
|
@@ -1345,7 +1421,8 @@ const oauthCreateSchema = {
|
|
|
1345
1421
|
.max(512)
|
|
1346
1422
|
.example('AAAAAQAACnA')
|
|
1347
1423
|
.allow(false, null)
|
|
1348
|
-
.description('Pub/Sub application ID for Gmail push notifications')
|
|
1424
|
+
.description('Pub/Sub application ID for Gmail push notifications')
|
|
1425
|
+
.label('PubSubAppId'),
|
|
1349
1426
|
|
|
1350
1427
|
extraScopes: Joi.any()
|
|
1351
1428
|
.alter({
|
|
@@ -1354,8 +1431,9 @@ const oauthCreateSchema = {
|
|
|
1354
1431
|
.allow('')
|
|
1355
1432
|
.trim()
|
|
1356
1433
|
.example('User.Read')
|
|
1357
|
-
.max(10 * 1024)
|
|
1358
|
-
|
|
1434
|
+
.max(10 * 1024)
|
|
1435
|
+
.label('OAuth2ExtraScopesWeb'),
|
|
1436
|
+
api: () => Joi.array().items(Joi.string().trim().max(255).example('User.Read').label('ExtraScopeEntry')).label('OAuth2ExtraScopesApi')
|
|
1359
1437
|
})
|
|
1360
1438
|
.description('Additional OAuth2 permission scopes'),
|
|
1361
1439
|
|
|
@@ -1366,8 +1444,9 @@ const oauthCreateSchema = {
|
|
|
1366
1444
|
.allow('')
|
|
1367
1445
|
.trim()
|
|
1368
1446
|
.example('SMTP.Send')
|
|
1369
|
-
.max(10 * 1024)
|
|
1370
|
-
|
|
1447
|
+
.max(10 * 1024)
|
|
1448
|
+
.label('OAuth2SkipScopesWeb'),
|
|
1449
|
+
api: () => Joi.array().items(Joi.string().trim().max(255).example('SMTP.Send').label('SkipScopeEntry')).label('OAuth2SkipScopesApi')
|
|
1371
1450
|
})
|
|
1372
1451
|
.description('OAuth2 scopes to exclude from the default set'),
|
|
1373
1452
|
|
|
@@ -1381,7 +1460,8 @@ const oauthCreateSchema = {
|
|
|
1381
1460
|
otherwise: Joi.optional().valid(false, null)
|
|
1382
1461
|
})
|
|
1383
1462
|
.example('7103296518315821565203')
|
|
1384
|
-
.description('Service account unique ID (for 2-legged OAuth2)')
|
|
1463
|
+
.description('Service account unique ID (for 2-legged OAuth2)')
|
|
1464
|
+
.label('ServiceClientId'),
|
|
1385
1465
|
|
|
1386
1466
|
googleProjectId: googleProjectIdSchema,
|
|
1387
1467
|
googleWorkspaceAccounts: googleWorkspaceAccountsSchema,
|
|
@@ -1459,15 +1539,16 @@ const oauthCreateSchema = {
|
|
|
1459
1539
|
})
|
|
1460
1540
|
.example('https://myservice.com/oauth')
|
|
1461
1541
|
.description('OAuth2 redirect URI configured in the provider')
|
|
1542
|
+
.label('OAuth2AppRedirectUrl')
|
|
1462
1543
|
};
|
|
1463
1544
|
|
|
1464
1545
|
const tokenRestrictionsSchema = Joi.object({
|
|
1465
1546
|
referrers: Joi.array()
|
|
1466
1547
|
.items(Joi.string())
|
|
1467
|
-
.empty('')
|
|
1548
|
+
.empty(Joi.valid('', false))
|
|
1468
1549
|
.single()
|
|
1469
|
-
.allow(
|
|
1470
|
-
.default(
|
|
1550
|
+
.allow(null)
|
|
1551
|
+
.default(null)
|
|
1471
1552
|
.example(['*web.domain.org/*', '*.domain.org/*', 'https://domain.org/*'])
|
|
1472
1553
|
.label('ReferrerAllowlist')
|
|
1473
1554
|
.description('HTTP referrer patterns that are allowed to use this token (wildcards supported)'),
|
|
@@ -1478,10 +1559,10 @@ const tokenRestrictionsSchema = Joi.object({
|
|
|
1478
1559
|
cidr: 'optional'
|
|
1479
1560
|
})
|
|
1480
1561
|
)
|
|
1481
|
-
.empty('')
|
|
1562
|
+
.empty(Joi.valid('', false))
|
|
1482
1563
|
.single()
|
|
1483
|
-
.allow(
|
|
1484
|
-
.default(
|
|
1564
|
+
.allow(null)
|
|
1565
|
+
.default(null)
|
|
1485
1566
|
.example(['1.2.3.4', '5.6.7.8', '127.0.0.0/8'])
|
|
1486
1567
|
.label('AddressAllowlist')
|
|
1487
1568
|
.description('IP addresses or CIDR ranges allowed to use this token'),
|
|
@@ -1489,14 +1570,15 @@ const tokenRestrictionsSchema = Joi.object({
|
|
|
1489
1570
|
maxRequests: Joi.number().integer().min(1).example(20).description('Maximum requests allowed in the time window'),
|
|
1490
1571
|
timeWindow: Joi.number().integer().min(1).example(2).description('Time window duration in seconds')
|
|
1491
1572
|
})
|
|
1492
|
-
.
|
|
1493
|
-
.
|
|
1573
|
+
.empty(Joi.valid('', false))
|
|
1574
|
+
.allow(null)
|
|
1575
|
+
.default(null)
|
|
1494
1576
|
.example({ maxRequests: 20, timeWindow: 2 })
|
|
1495
1577
|
.label('AddressRateLimit')
|
|
1496
1578
|
.description('Rate limiting configuration for this token')
|
|
1497
1579
|
})
|
|
1498
|
-
.empty('')
|
|
1499
|
-
.allow(
|
|
1580
|
+
.empty(Joi.valid('', false))
|
|
1581
|
+
.allow(null)
|
|
1500
1582
|
.label('TokenRestrictions')
|
|
1501
1583
|
.description('Security restrictions for API token usage');
|
|
1502
1584
|
|
|
@@ -1539,13 +1621,15 @@ const defaultAccountTypeSchema = Joi.string()
|
|
|
1539
1621
|
const outboxEntrySchema = Joi.object({
|
|
1540
1622
|
queueId: Joi.string().example('1869c5692565f756b33').description('Unique queue entry identifier'),
|
|
1541
1623
|
account: accountIdSchema.required(),
|
|
1542
|
-
source: Joi.string().example('smtp').valid('smtp', 'api').description('How this message entered the queue'),
|
|
1624
|
+
source: Joi.string().example('smtp').valid('smtp', 'api').description('How this message entered the queue').label('OutboxSource'),
|
|
1543
1625
|
|
|
1544
1626
|
messageId: Joi.string().max(996).example('<test123@example.com>').description('Message-ID header value'),
|
|
1545
1627
|
envelope: Joi.object({
|
|
1546
1628
|
from: Joi.string().email().allow('').example('sender@example.com'),
|
|
1547
|
-
to: Joi.array().items(Joi.string().email().required().example('recipient@example.com'))
|
|
1548
|
-
})
|
|
1629
|
+
to: Joi.array().items(Joi.string().email().required().example('recipient@example.com')).label('OutboxEnvelopeTo')
|
|
1630
|
+
})
|
|
1631
|
+
.description('SMTP envelope information')
|
|
1632
|
+
.label('OutboxEnvelope'),
|
|
1549
1633
|
|
|
1550
1634
|
subject: Joi.string()
|
|
1551
1635
|
.allow('')
|
|
@@ -1561,7 +1645,7 @@ const outboxEntrySchema = Joi.object({
|
|
|
1561
1645
|
attempts: Joi.number().integer().example(3).description('Maximum delivery attempts before marking as failed'),
|
|
1562
1646
|
|
|
1563
1647
|
progress: Joi.object({
|
|
1564
|
-
status: Joi.string().valid('queued', 'processing', 'submitted', 'error').example('queued').description('Current delivery status'),
|
|
1648
|
+
status: Joi.string().valid('queued', 'processing', 'submitted', 'error').example('queued').description('Current delivery status').label('OutboxStatus'),
|
|
1565
1649
|
response: Joi.string().example('250 Message Accepted').description('SMTP server response (when status is "processing")'),
|
|
1566
1650
|
error: Joi.object({
|
|
1567
1651
|
message: Joi.string().example('Authentication failed').description('Error description'),
|
|
@@ -1586,7 +1670,8 @@ const messageReferenceSchema = Joi.object({
|
|
|
1586
1670
|
.valid('forward', 'reply', 'reply-all')
|
|
1587
1671
|
.example('reply')
|
|
1588
1672
|
.default('reply')
|
|
1589
|
-
.description('Action type: "reply" (reply to sender), "reply-all" (reply to all recipients), or "forward" (forward to new recipients)')
|
|
1673
|
+
.description('Action type: "reply" (reply to sender), "reply-all" (reply to all recipients), or "forward" (forward to new recipients)')
|
|
1674
|
+
.label('MessageAction'),
|
|
1590
1675
|
|
|
1591
1676
|
inline: Joi.boolean()
|
|
1592
1677
|
.truthy('Y', 'true', '1')
|
|
@@ -1643,6 +1728,96 @@ const headerTimeoutSchema = Joi.number()
|
|
|
1643
1728
|
.description('Request timeout in milliseconds (overrides EENGINE_TIMEOUT environment variable)')
|
|
1644
1729
|
.label('X-EE-Timeout');
|
|
1645
1730
|
|
|
1731
|
+
// Export schemas
|
|
1732
|
+
const exportRequestSchema = Joi.object({
|
|
1733
|
+
folders: Joi.array()
|
|
1734
|
+
.items(Joi.string().max(1024).example('INBOX'))
|
|
1735
|
+
.single()
|
|
1736
|
+
.description(
|
|
1737
|
+
'Folder paths or special-use flags (e.g., \\Inbox, \\Sent, \\All) to export from. If empty/omitted, Gmail/Outlook API accounts export from All Mail folder; other accounts export all folders except Junk and Trash.'
|
|
1738
|
+
)
|
|
1739
|
+
.label('ExportFolders'),
|
|
1740
|
+
startDate: Joi.date().iso().required().example('2024-01-01T00:00:00Z').description('Export messages from this date'),
|
|
1741
|
+
endDate: Joi.date().iso().required().example('2024-12-31T23:59:59Z').description('Export messages until this date'),
|
|
1742
|
+
textType: Joi.string()
|
|
1743
|
+
.valid('plain', 'html', '*')
|
|
1744
|
+
.default('*')
|
|
1745
|
+
.example('*')
|
|
1746
|
+
.description('Text content to include: "plain", "html", "*" (both), or omit for metadata only')
|
|
1747
|
+
.label('ExportTextType'),
|
|
1748
|
+
maxBytes: Joi.number()
|
|
1749
|
+
.integer()
|
|
1750
|
+
.min(0)
|
|
1751
|
+
.default(5 * 1024 * 1024)
|
|
1752
|
+
.example(5242880)
|
|
1753
|
+
.description('Maximum bytes for text content (0 = unlimited)'),
|
|
1754
|
+
includeAttachments: Joi.boolean()
|
|
1755
|
+
.truthy('Y', 'true', '1')
|
|
1756
|
+
.falsy('N', 'false', 0)
|
|
1757
|
+
.default(false)
|
|
1758
|
+
.description('Include attachment content as base64 blob in attachments array')
|
|
1759
|
+
})
|
|
1760
|
+
.custom((value, helpers) => {
|
|
1761
|
+
if (value.startDate && value.endDate && value.startDate >= value.endDate) {
|
|
1762
|
+
return helpers.error('any.invalid');
|
|
1763
|
+
}
|
|
1764
|
+
return value;
|
|
1765
|
+
}, 'date range validation')
|
|
1766
|
+
.messages({ 'any.invalid': 'startDate must be before endDate' })
|
|
1767
|
+
.label('ExportRequest');
|
|
1768
|
+
|
|
1769
|
+
const exportProgressSchema = Joi.object({
|
|
1770
|
+
foldersScanned: Joi.number().integer().example(1).description('Number of folders scanned'),
|
|
1771
|
+
foldersTotal: Joi.number().integer().example(2).description('Total number of folders to scan'),
|
|
1772
|
+
messagesQueued: Joi.number().integer().example(1500).description('Number of messages queued for export'),
|
|
1773
|
+
messagesExported: Joi.number().integer().example(500).description('Number of messages exported'),
|
|
1774
|
+
messagesSkipped: Joi.number().integer().example(5).description('Number of messages skipped (deleted or inaccessible)'),
|
|
1775
|
+
bytesWritten: Joi.number().integer().example(52428800).description('Bytes written to export file')
|
|
1776
|
+
}).label('ExportProgress');
|
|
1777
|
+
|
|
1778
|
+
const exportStatusSchema = Joi.object({
|
|
1779
|
+
exportId: Joi.string().example('exp_abc123def456abc123def456').description('Export job identifier'),
|
|
1780
|
+
status: Joi.string()
|
|
1781
|
+
.valid('queued', 'processing', 'completed', 'failed', 'cancelled')
|
|
1782
|
+
.example('processing')
|
|
1783
|
+
.description('Export status')
|
|
1784
|
+
.label('ExportStatusValue'),
|
|
1785
|
+
phase: Joi.string().valid('indexing', 'exporting', 'complete').example('indexing').description('Current export phase').label('ExportPhase'),
|
|
1786
|
+
folders: Joi.array().items(Joi.string().label('ExportFolderItem')).description('Folders being exported').label('ExportStatusFolders'),
|
|
1787
|
+
startDate: Joi.date().iso().example('2024-01-01T00:00:00Z').description('Export start date filter'),
|
|
1788
|
+
endDate: Joi.date().iso().example('2024-12-31T23:59:59Z').description('Export end date filter'),
|
|
1789
|
+
isEncrypted: Joi.boolean().example(false).description('Whether the export file is encrypted'),
|
|
1790
|
+
progress: exportProgressSchema,
|
|
1791
|
+
created: Joi.date().iso().example('2024-01-15T10:30:00Z').description('When export was created'),
|
|
1792
|
+
expiresAt: Joi.date().iso().example('2024-01-16T10:30:00Z').description('When export file expires'),
|
|
1793
|
+
error: Joi.string().allow(null).description('Error message if export failed')
|
|
1794
|
+
}).label('ExportStatus');
|
|
1795
|
+
|
|
1796
|
+
const exportListEntrySchema = Joi.object({
|
|
1797
|
+
exportId: Joi.string().example('exp_abc123def456abc123def456').description('Export job identifier'),
|
|
1798
|
+
status: Joi.string()
|
|
1799
|
+
.valid('queued', 'processing', 'completed', 'failed', 'cancelled')
|
|
1800
|
+
.example('completed')
|
|
1801
|
+
.description('Export status')
|
|
1802
|
+
.label('ExportListStatusValue'),
|
|
1803
|
+
created: Joi.date().iso().example('2024-01-15T10:30:00Z').description('When export was created'),
|
|
1804
|
+
expiresAt: Joi.date().iso().example('2024-01-16T10:30:00Z').description('When export file expires')
|
|
1805
|
+
}).label('ExportListEntry');
|
|
1806
|
+
|
|
1807
|
+
const exportListSchema = Joi.object({
|
|
1808
|
+
total: Joi.number().integer().example(5).description('Total number of exports'),
|
|
1809
|
+
page: Joi.number().integer().example(0).description('Current page number'),
|
|
1810
|
+
pages: Joi.number().integer().example(1).description('Total number of pages'),
|
|
1811
|
+
exports: Joi.array().items(exportListEntrySchema).description('Export entries').label('ExportEntries')
|
|
1812
|
+
}).label('ExportList');
|
|
1813
|
+
|
|
1814
|
+
const exportIdSchema = Joi.string()
|
|
1815
|
+
.pattern(/^exp_[a-f0-9]{24}$/)
|
|
1816
|
+
.required()
|
|
1817
|
+
.example('exp_abc123def456abc123def456')
|
|
1818
|
+
.description('Export job identifier')
|
|
1819
|
+
.label('ExportId');
|
|
1820
|
+
|
|
1646
1821
|
module.exports = {
|
|
1647
1822
|
ADDRESS_STRATEGIES,
|
|
1648
1823
|
|
|
@@ -1684,7 +1859,12 @@ module.exports = {
|
|
|
1684
1859
|
googleWorkspaceAccountsSchema,
|
|
1685
1860
|
messageReferenceSchema,
|
|
1686
1861
|
idempotencyKeySchema,
|
|
1687
|
-
headerTimeoutSchema
|
|
1862
|
+
headerTimeoutSchema,
|
|
1863
|
+
exportRequestSchema,
|
|
1864
|
+
exportStatusSchema,
|
|
1865
|
+
exportListSchema,
|
|
1866
|
+
exportProgressSchema,
|
|
1867
|
+
exportIdSchema
|
|
1688
1868
|
};
|
|
1689
1869
|
|
|
1690
1870
|
/*
|