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.
Files changed (62) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/data/google-crawlers.json +1 -1
  3. package/lib/account.js +20 -7
  4. package/lib/api-routes/account-routes.js +28 -5
  5. package/lib/api-routes/chat-routes.js +1 -1
  6. package/lib/api-routes/export-routes.js +316 -0
  7. package/lib/api-routes/message-routes.js +28 -23
  8. package/lib/api-routes/template-routes.js +28 -7
  9. package/lib/arf-detect.js +1 -1
  10. package/lib/consts.js +16 -0
  11. package/lib/db.js +3 -0
  12. package/lib/email-client/base-client.js +6 -4
  13. package/lib/email-client/gmail-client.js +204 -33
  14. package/lib/email-client/imap/mailbox.js +99 -8
  15. package/lib/email-client/imap/subconnection.js +5 -5
  16. package/lib/email-client/imap-client.js +76 -16
  17. package/lib/email-client/message-builder.js +3 -1
  18. package/lib/email-client/notification-handler.js +12 -9
  19. package/lib/email-client/outlook-client.js +362 -69
  20. package/lib/email-client/smtp-pool-manager.js +1 -1
  21. package/lib/export.js +528 -0
  22. package/lib/oauth/gmail.js +21 -13
  23. package/lib/oauth/mail-ru.js +23 -10
  24. package/lib/oauth/outlook.js +26 -16
  25. package/lib/oauth/pubsub/google.js +5 -0
  26. package/lib/routes-ui.js +236 -2
  27. package/lib/schemas.js +260 -80
  28. package/lib/stream-encrypt.js +263 -0
  29. package/lib/tools.js +30 -4
  30. package/lib/ui-routes/account-routes.js +24 -1
  31. package/lib/ui-routes/admin-config-routes.js +11 -4
  32. package/lib/ui-routes/admin-entities-routes.js +18 -0
  33. package/lib/webhooks.js +16 -20
  34. package/package.json +17 -17
  35. package/sbom.json +1 -1
  36. package/server.js +41 -5
  37. package/static/js/ace/ace.js +1 -1
  38. package/static/js/ace/ext-language_tools.js +1 -1
  39. package/static/licenses.html +47 -127
  40. package/translations/de.mo +0 -0
  41. package/translations/de.po +63 -36
  42. package/translations/en.mo +0 -0
  43. package/translations/en.po +64 -37
  44. package/translations/et.mo +0 -0
  45. package/translations/et.po +63 -36
  46. package/translations/fr.mo +0 -0
  47. package/translations/fr.po +63 -36
  48. package/translations/ja.mo +0 -0
  49. package/translations/ja.po +63 -36
  50. package/translations/messages.pot +88 -55
  51. package/translations/nl.mo +0 -0
  52. package/translations/nl.po +63 -36
  53. package/translations/pl.mo +0 -0
  54. package/translations/pl.po +63 -36
  55. package/views/accounts/account.hbs +375 -2
  56. package/views/config/service.hbs +35 -0
  57. package/workers/api.js +124 -45
  58. package/workers/documents.js +1 -0
  59. package/workers/export.js +926 -0
  60. package/workers/imap.js +29 -0
  61. package/workers/submit.js +25 -5
  62. 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().items(Joi.string().max(256).example('messageNew')).description('List of event types that will trigger webhook notifications'),
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
- 'IMAP indexing strategy:\n * full - Detect new, changed, and deleted messages (slower but complete)\n * fast - Detect only new messages'
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(Joi.string().ip({ version: ['ipv4', 'ipv6'], cidr: 'forbidden' }))
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
- }).description('Email address with optional display name');
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('Authentication'),
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('TLS'),
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('Authentication'),
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('TLS')
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('Authentication'),
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('TLS'),
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('Authentication'),
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('TLS')
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
- }).description('Sizes of different message parts')
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
- }).description('Sizes of different message parts'),
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().items(
1031
- Joi.object({
1032
- path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to the mailbox').label('MailboxPath'),
1033
- delimiter: Joi.string().example('/').description('Hierarchy delimiter character used in paths'),
1034
- parentPath: Joi.string().required().example('Kalender').description('Path to the parent mailbox').label('MailboxParentPath'),
1035
- name: Joi.string().required().example('Sünnipäevad').description('Display name of the mailbox').label('MailboxName'),
1036
- listed: Joi.boolean().example(true).description('Whether this mailbox appears in LIST command results').label('MailboxListed'),
1037
- subscribed: Joi.boolean().example(true).description('Whether the user is subscribed to this mailbox').label('MailboxSubscribed'),
1038
- specialUse: Joi.string()
1039
- .example('\\Sent')
1040
- .valid('\\All', '\\Archive', '\\Drafts', '\\Flagged', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
1041
- .description('Special folder type (Inbox, Sent, Drafts, etc.)')
1042
- .label('MailboxSpecialUse')
1043
- }).label('MailboxShortResponseItem')
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().example('Token request failed').description('Human-readable error message'),
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().valid('refresh_token', 'authorization_code').example('refresh_token').description('OAuth2 grant type being requested'),
1066
- provider: Joi.string().max(256).example('gmail').description('OAuth2 provider name'),
1067
- status: Joi.number().integer().example(400).description('HTTP status code from the OAuth2 server'),
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
- }).description('Details about the failed OAuth2 token request')
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('IMAPIndexer')
1330
+ .label('AccountImapIndexer')
1270
1331
  };
1271
1332
 
1272
- const googleProjectIdSchema = Joi.string().trim().allow('', false, null).max(256).example('project-name-425411').description('Google Cloud Project ID');
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().empty('').trim().valid('imap', 'api', 'pubsub').example('imap').description('Connection type (IMAP, API, or Pub/Sub)'),
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
- api: () => Joi.array().items(Joi.string().trim().max(255).example('User.Read'))
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
- api: () => Joi.array().items(Joi.string().trim().max(255).example('SMTP.Send'))
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(false)
1470
- .default(false)
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(false)
1484
- .default(false)
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
- .allow(false)
1493
- .default(false)
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(false)
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
- }).description('SMTP envelope information'),
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
  /*