emailengine-app 2.61.5 → 2.62.1

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 (65) hide show
  1. package/CHANGELOG.md +88 -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/autodetect-imap-settings.js +5 -5
  11. package/lib/consts.js +16 -0
  12. package/lib/db.js +3 -0
  13. package/lib/email-client/base-client.js +6 -4
  14. package/lib/email-client/gmail-client.js +205 -35
  15. package/lib/email-client/imap/mailbox.js +99 -8
  16. package/lib/email-client/imap/subconnection.js +5 -5
  17. package/lib/email-client/imap-client.js +76 -19
  18. package/lib/email-client/message-builder.js +3 -1
  19. package/lib/email-client/notification-handler.js +12 -9
  20. package/lib/email-client/outlook-client.js +364 -73
  21. package/lib/email-client/smtp-pool-manager.js +1 -1
  22. package/lib/export.js +528 -0
  23. package/lib/oauth/gmail.js +24 -16
  24. package/lib/oauth/mail-ru.js +26 -13
  25. package/lib/oauth/outlook.js +29 -19
  26. package/lib/oauth/pubsub/google.js +5 -0
  27. package/lib/routes-ui.js +268 -9
  28. package/lib/schemas.js +274 -81
  29. package/lib/stream-encrypt.js +263 -0
  30. package/lib/sub-script.js +2 -2
  31. package/lib/tools.js +194 -12
  32. package/lib/ui-routes/account-routes.js +23 -0
  33. package/lib/ui-routes/admin-config-routes.js +13 -6
  34. package/lib/ui-routes/admin-entities-routes.js +18 -0
  35. package/lib/webhooks.js +16 -20
  36. package/package.json +20 -20
  37. package/sbom.json +1 -1
  38. package/server.js +66 -7
  39. package/static/js/ace/ace.js +1 -1
  40. package/static/js/ace/ext-language_tools.js +1 -1
  41. package/static/licenses.html +118 -149
  42. package/translations/de.mo +0 -0
  43. package/translations/de.po +63 -36
  44. package/translations/en.mo +0 -0
  45. package/translations/en.po +64 -37
  46. package/translations/et.mo +0 -0
  47. package/translations/et.po +63 -36
  48. package/translations/fr.mo +0 -0
  49. package/translations/fr.po +63 -36
  50. package/translations/ja.mo +0 -0
  51. package/translations/ja.po +63 -36
  52. package/translations/messages.pot +84 -51
  53. package/translations/nl.mo +0 -0
  54. package/translations/nl.po +63 -36
  55. package/translations/pl.mo +0 -0
  56. package/translations/pl.po +63 -36
  57. package/views/accounts/account.hbs +375 -2
  58. package/views/config/network.hbs +45 -0
  59. package/views/config/service.hbs +35 -0
  60. package/workers/api.js +130 -47
  61. package/workers/documents.js +3 -2
  62. package/workers/export.js +933 -0
  63. package/workers/imap.js +34 -1
  64. package/workers/submit.js +33 -6
  65. package/workers/webhooks.js +20 -4
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')
@@ -210,12 +213,25 @@ const settingsSchema = {
210
213
 
211
214
  proxyEnabled: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').description('Route outbound connections through a proxy server'),
212
215
  proxyUrl: Joi.string()
213
- .uri({ scheme: ['http', 'https', 'socks', 'socks4', 'socks5'], allowRelative: false })
216
+ .uri({ scheme: ['http', 'https', 'socks', 'socks4', 'socks4a', 'socks5'], allowRelative: false })
214
217
  .allow('')
215
218
  .example('socks5://proxy.example.com:1080')
216
219
  .description('Proxy server URL for outbound connections')
217
220
  .label('ProxyURL'),
218
221
 
222
+ /* ────────────── HTTP Proxy ────────────── */
223
+
224
+ httpProxyEnabled: Joi.boolean()
225
+ .truthy('Y', 'true', '1', 'on')
226
+ .falsy('N', 'false', 0, '')
227
+ .description('Route outbound HTTP/HTTPS requests (webhooks, OAuth, API calls) through an HTTP proxy'),
228
+ httpProxyUrl: Joi.string()
229
+ .uri({ scheme: ['http', 'https', 'socks', 'socks4', 'socks4a', 'socks5'], allowRelative: false })
230
+ .allow('')
231
+ .example('http://proxy.example.com:8080')
232
+ .description('HTTP proxy URL for outbound HTTP/HTTPS requests')
233
+ .label('HttpProxyURL'),
234
+
219
235
  /* ────────────── SMTP ────────────── */
220
236
 
221
237
  smtpEhloName: Joi.string()
@@ -373,17 +389,24 @@ const settingsSchema = {
373
389
  imapStrategy: Joi.string()
374
390
  .empty('')
375
391
  .valid(...ADDRESS_STRATEGIES.map(entry => entry.key))
376
- .description('IP address selection strategy for outbound IMAP connections when multiple local addresses are available'),
392
+ .description('IP address selection strategy for outbound IMAP connections when multiple local addresses are available')
393
+ .label('ImapStrategy'),
377
394
 
378
395
  smtpStrategy: Joi.string()
379
396
  .empty('')
380
397
  .valid(...ADDRESS_STRATEGIES.map(entry => entry.key))
381
- .description('IP address selection strategy for outbound SMTP connections when multiple local addresses are available'),
398
+ .description('IP address selection strategy for outbound SMTP connections when multiple local addresses are available')
399
+ .label('SmtpStrategy'),
382
400
 
383
401
  localAddresses: Joi.array()
384
- .items(Joi.string().ip({ version: ['ipv4', 'ipv6'], cidr: 'forbidden' }))
402
+ .items(
403
+ Joi.string()
404
+ .ip({ version: ['ipv4', 'ipv6'], cidr: 'forbidden' })
405
+ .label('LocalAddressEntry')
406
+ )
385
407
  .single()
386
- .description('List of local IP addresses to use for outbound connections (requires appropriate network configuration)'),
408
+ .description('List of local IP addresses to use for outbound connections (requires appropriate network configuration)')
409
+ .label('LocalAddresses'),
387
410
 
388
411
  /* ────────────── Built-in SMTP Server ────────────── */
389
412
 
@@ -473,7 +496,8 @@ const settingsSchema = {
473
496
  .max(100)
474
497
  .example('fr')
475
498
  .valid(...locales.map(l => l.locale))
476
- .description('Default language/locale for the user interface'),
499
+ .description('Default language/locale for the user interface')
500
+ .label('SettingsLocale'),
477
501
  timezone: Joi.string().max(100).example('Europe/Tallinn').description('Default timezone for date/time display (IANA timezone identifier)'),
478
502
  pageBrandName: Joi.string().allow('', null).max(1024).example('EmailEngine').description('Brand name displayed in page titles'),
479
503
 
@@ -494,13 +518,39 @@ const settingsSchema = {
494
518
  .allow('')
495
519
  .uri({ scheme: ['http', 'https', 'mailto'], allowRelative: false })
496
520
  .example('https://github.com/postalsys/emailengine/issues')
497
- .description('Support URL advertised via IMAP ID extension')
521
+ .description('Support URL advertised via IMAP ID extension'),
522
+
523
+ /* ────────────── Export ────────────── */
524
+
525
+ exportMaxConcurrent: Joi.number().integer().min(1).max(100).example(2).description('Maximum concurrent exports per account'),
526
+
527
+ exportMaxGlobalConcurrent: Joi.number().integer().min(1).max(100).example(8).description('Maximum concurrent exports system-wide across all accounts'),
528
+
529
+ gmailExportBatchSize: Joi.number()
530
+ .integer()
531
+ .min(1)
532
+ .max(50)
533
+ .example(10)
534
+ .description('Number of parallel message fetch requests for Gmail export operations (default: 10, max: 50)'),
535
+
536
+ outlookExportBatchSize: Joi.number()
537
+ .integer()
538
+ .min(1)
539
+ .max(20)
540
+ .example(20)
541
+ .description('Number of messages per batch request for Outlook export operations (default: 20, max: 20 - MS Graph API limit)'),
542
+
543
+ exportMaxMessages: Joi.number().integer().min(1).max(10000000).example(500000).description('Maximum number of messages per export (default: 500000)'),
544
+
545
+ exportMaxSize: Joi.number().integer().min(1).example(10737418240).description('Maximum export file size in bytes (default: 10GB)')
498
546
  };
499
547
 
500
548
  const addressSchema = Joi.object({
501
549
  name: Joi.string().trim().empty('').max(256).example('Some Name').description('Display name for the email address'),
502
550
  address: Joi.string().email({ ignoreLength: false }).example('user@example.com').required().description('Email address')
503
- }).description('Email address with optional display name');
551
+ })
552
+ .description('Email address with optional display name')
553
+ .label('EmailAddress');
504
554
 
505
555
  // generate a list of boolean values
506
556
  const settingsQuerySchema = Object.fromEntries(
@@ -527,6 +577,7 @@ const imapSchema = {
527
577
  .max(4 * 4096)
528
578
  .example(false)
529
579
  .description('OAuth2 access token (when using OAuth2 instead of password authentication)')
580
+ .label('ImapAccessToken')
530
581
  })
531
582
  .allow(false)
532
583
  .when('useAuthServer', {
@@ -535,7 +586,7 @@ const imapSchema = {
535
586
  otherwise: Joi.optional()
536
587
  })
537
588
  .description('Authentication credentials for the IMAP server')
538
- .label('Authentication'),
589
+ .label('ImapAuthentication'),
539
590
 
540
591
  useAuthServer: Joi.boolean().example(false).description('Use external authentication server to retrieve credentials dynamically'),
541
592
 
@@ -566,7 +617,7 @@ const imapSchema = {
566
617
  })
567
618
  .unknown()
568
619
  .description('Advanced TLS configuration options')
569
- .label('TLS'),
620
+ .label('ImapTlsOptions'),
570
621
  resyncDelay: Joi.number().integer().example(RESYNC_DELAY).description('Delay in seconds between full mailbox resynchronizations').default(RESYNC_DELAY),
571
622
  disabled: Joi.boolean().example(false).description('Temporarily disable IMAP operations for this account'),
572
623
 
@@ -614,6 +665,7 @@ const smtpSchema = {
614
665
  .max(4 * 4096)
615
666
  .example(false)
616
667
  .description('OAuth2 access token (when using OAuth2 instead of password authentication)')
668
+ .label('SmtpAccessToken')
617
669
  })
618
670
  .allow(false)
619
671
  .when('useAuthServer', {
@@ -622,7 +674,7 @@ const smtpSchema = {
622
674
  otherwise: Joi.optional()
623
675
  })
624
676
  .description('Authentication credentials for the SMTP server')
625
- .label('Authentication'),
677
+ .label('SmtpAuthentication'),
626
678
 
627
679
  useAuthServer: Joi.boolean().example(false).description('Use external authentication server to retrieve credentials dynamically'),
628
680
 
@@ -644,7 +696,7 @@ const smtpSchema = {
644
696
  })
645
697
  .unknown()
646
698
  .description('Advanced TLS configuration options')
647
- .label('TLS')
699
+ .label('SmtpTlsOptions')
648
700
  };
649
701
 
650
702
  const oauth2AuthSchema = Joi.object({
@@ -678,9 +730,10 @@ const oauth2Schema = {
678
730
  is: true,
679
731
  then: Joi.optional(),
680
732
  otherwise: Joi.optional().valid(false, null)
681
- }),
733
+ })
734
+ .label('OAuth2RedirectUrl'),
682
735
 
683
- provider: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 Application ID configured in EmailEngine'),
736
+ provider: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 Application ID configured in EmailEngine').label('OAuth2AppProvider'),
684
737
 
685
738
  auth: oauth2AuthSchema,
686
739
 
@@ -689,7 +742,8 @@ const oauth2Schema = {
689
742
  accessToken: Joi.string()
690
743
  .max(4 * 4096)
691
744
  .example('ya29.a0ARrdaM8a...')
692
- .description('OAuth2 access token for the email account'),
745
+ .description('OAuth2 access token for the email account')
746
+ .label('OAuth2SchemaAccessToken'),
693
747
  refreshToken: Joi.string()
694
748
  .max(4 * 4096)
695
749
  .example('1//09Ie3CtORQYm...')
@@ -728,6 +782,7 @@ const imapUpdateSchema = {
728
782
  .max(4 * 4096)
729
783
  .example(false)
730
784
  .description('OAuth2 access token (when using OAuth2 instead of password authentication)')
785
+ .label('ImapUpdateAccessToken')
731
786
  })
732
787
  .allow(false)
733
788
  .when('useAuthServer', {
@@ -736,7 +791,7 @@ const imapUpdateSchema = {
736
791
  otherwise: Joi.optional()
737
792
  })
738
793
  .description('Authentication credentials for the IMAP server')
739
- .label('Authentication'),
794
+ .label('ImapUpdateAuthentication'),
740
795
 
741
796
  useAuthServer: Joi.boolean().example(false).description('Use external authentication server to retrieve credentials dynamically'),
742
797
 
@@ -749,7 +804,7 @@ const imapUpdateSchema = {
749
804
  })
750
805
  .unknown()
751
806
  .description('Advanced TLS configuration options')
752
- .label('TLS'),
807
+ .label('ImapUpdateTlsOptions'),
753
808
  resyncDelay: Joi.number().integer().example(RESYNC_DELAY).description('Delay in seconds between full mailbox resynchronizations'),
754
809
 
755
810
  disabled: Joi.boolean().example(false).description('Temporarily disable IMAP operations for this account'),
@@ -779,6 +834,7 @@ const smtpUpdateSchema = {
779
834
  .max(4 * 4096)
780
835
  .example(false)
781
836
  .description('OAuth2 access token (when using OAuth2 instead of password authentication)')
837
+ .label('SmtpUpdateAccessToken')
782
838
  })
783
839
  .allow(false)
784
840
  .when('useAuthServer', {
@@ -787,7 +843,7 @@ const smtpUpdateSchema = {
787
843
  otherwise: Joi.optional()
788
844
  })
789
845
  .description('Authentication credentials for the SMTP server')
790
- .label('Authentication'),
846
+ .label('SmtpUpdateAuthentication'),
791
847
 
792
848
  useAuthServer: Joi.boolean().example(false).description('Use external authentication server to retrieve credentials dynamically'),
793
849
 
@@ -800,14 +856,14 @@ const smtpUpdateSchema = {
800
856
  })
801
857
  .unknown()
802
858
  .description('Advanced TLS configuration options')
803
- .label('TLS')
859
+ .label('SmtpUpdateTlsOptions')
804
860
  };
805
861
 
806
862
  const oauth2UpdateSchema = {
807
863
  partial: partialSchema,
808
864
 
809
865
  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'),
866
+ provider: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 Application ID configured in EmailEngine').label('OAuth2UpdateAppProvider'),
811
867
 
812
868
  auth: oauth2AuthSchema,
813
869
 
@@ -816,7 +872,8 @@ const oauth2UpdateSchema = {
816
872
  accessToken: Joi.string()
817
873
  .max(4 * 4096)
818
874
  .example('ya29.a0ARrdaM8a...')
819
- .description('OAuth2 access token for the email account'),
875
+ .description('OAuth2 access token for the email account')
876
+ .label('OAuth2UpdateAccessToken'),
820
877
  refreshToken: Joi.string()
821
878
  .max(4 * 4096)
822
879
  .example('1//09Ie3CtORQYm...')
@@ -839,7 +896,8 @@ const attachmentSchema = Joi.object({
839
896
  encodedSize: Joi.number()
840
897
  .integer()
841
898
  .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.'),
899
+ .description('Size of the attachment as stored in the email (base64 encoded). The actual decoded file size is approximately 75% of this value.')
900
+ .label('AttachmentEncodedSize'),
843
901
  embedded: Joi.boolean().example(true).description('Whether the attachment is embedded in the HTML content'),
844
902
  inline: Joi.boolean().example(true).description('Whether the attachment should be displayed inline rather than as a download'),
845
903
  contentId: Joi.string().example('<unique-image-id@localhost>').description('Content-ID header value used for embedding images in HTML'),
@@ -892,7 +950,9 @@ const messageEntrySchema = Joi.object({
892
950
  encodedSize: Joi.object({
893
951
  plain: Joi.number().integer().example(1013).description('Size of the plain text part in bytes'),
894
952
  html: Joi.number().integer().example(1013).description('Size of the HTML part in bytes')
895
- }).description('Sizes of different message parts')
953
+ })
954
+ .description('Sizes of different message parts')
955
+ .label('TextEncodedSize')
896
956
  }).label('TextInfo'),
897
957
 
898
958
  preview: Joi.string().description('Short preview of the message content')
@@ -947,7 +1007,9 @@ const messageDetailsSchema = Joi.object({
947
1007
  encodedSize: Joi.object({
948
1008
  plain: Joi.number().integer().example(1013).description('Size of the plain text part in bytes'),
949
1009
  html: Joi.number().integer().example(1013).description('Size of the HTML part in bytes')
950
- }).description('Sizes of different message parts'),
1010
+ })
1011
+ .description('Sizes of different message parts')
1012
+ .label('TextDetailEncodedSize'),
951
1013
  plain: Joi.string().example('Hello from myself!').description('Plain text version of the message'),
952
1014
  html: Joi.string().example('<p>Hello from myself!</p>').description('HTML version of the message'),
953
1015
  hasMore: Joi.boolean().example(false).description('Whether the message content was truncated (true if more content is available via separate API call)')
@@ -958,7 +1020,7 @@ const messageDetailsSchema = Joi.object({
958
1020
  Joi.object({
959
1021
  message: Joi.string().max(256).required().example('AAAAAQAACnA').description('EmailEngine identifier of the bounce notification'),
960
1022
  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.)'),
1023
+ action: Joi.string().example('failed').description('Bounce action (failed, delayed, etc.)').label('BounceAction'),
962
1024
  response: Joi.object({
963
1025
  message: Joi.string().example('550 5.1.1 No such user').description('Error message from the receiving server'),
964
1026
  status: Joi.string().example('5.1.1').description('SMTP status code')
@@ -1027,21 +1089,23 @@ const mailboxesSchema = Joi.array()
1027
1089
  )
1028
1090
  .label('MailboxesList');
1029
1091
 
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
- );
1092
+ const shortMailboxesSchema = Joi.array()
1093
+ .items(
1094
+ Joi.object({
1095
+ path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to the mailbox').label('MailboxPath'),
1096
+ delimiter: Joi.string().example('/').description('Hierarchy delimiter character used in paths'),
1097
+ parentPath: Joi.string().required().example('Kalender').description('Path to the parent mailbox').label('MailboxParentPath'),
1098
+ name: Joi.string().required().example('Sünnipäevad').description('Display name of the mailbox').label('MailboxName'),
1099
+ listed: Joi.boolean().example(true).description('Whether this mailbox appears in LIST command results').label('MailboxListed'),
1100
+ subscribed: Joi.boolean().example(true).description('Whether the user is subscribed to this mailbox').label('MailboxSubscribed'),
1101
+ specialUse: Joi.string()
1102
+ .example('\\Sent')
1103
+ .valid('\\All', '\\Archive', '\\Drafts', '\\Flagged', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
1104
+ .description('Special folder type (Inbox, Sent, Drafts, etc.)')
1105
+ .label('MailboxSpecialUse')
1106
+ }).label('MailboxShortResponseItem')
1107
+ )
1108
+ .label('ShortMailboxes');
1045
1109
 
1046
1110
  const licenseSchema = Joi.object({
1047
1111
  active: Joi.boolean().example(true).description('Whether a valid license is currently active'),
@@ -1056,18 +1120,25 @@ const licenseSchema = Joi.object({
1056
1120
  .allow(false)
1057
1121
  .label('LicenseDetails'),
1058
1122
  suspended: Joi.boolean().example(false).description('Whether email operations are suspended due to license issues')
1059
- });
1123
+ }).label('LicenseInfo');
1060
1124
 
1061
1125
  const lastErrorSchema = Joi.object({
1062
- response: Joi.string().example('Token request failed').description('Human-readable error message'),
1126
+ response: Joi.string()
1127
+ .example('Token request failed for gmail (refresh_token, HTTP 400): invalid_grant - Token has been expired or revoked.')
1128
+ .description('Human-readable error message'),
1063
1129
  serverResponseCode: Joi.string().example('OauthRenewError').description('Error code or classification'),
1064
1130
  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'),
1131
+ grant: Joi.string()
1132
+ .valid('refresh_token', 'authorization_code')
1133
+ .example('refresh_token')
1134
+ .description('OAuth2 grant type being requested')
1135
+ .label('OAuthGrant'),
1136
+ provider: Joi.string().max(256).example('gmail').description('OAuth2 provider name').label('OAuthProvider'),
1137
+ status: Joi.number().integer().example(400).description('HTTP status code from the OAuth2 server').label('OAuthStatusCode'),
1068
1138
  clientId: Joi.string()
1069
1139
  .example('1023289917884-h3nu00e9cb7h252e24c23sv19l8k57ah.apps.googleusercontent.com')
1070
- .description('OAuth2 client ID used for authentication'),
1140
+ .description('OAuth2 client ID used for authentication')
1141
+ .label('OAuthClientId'),
1071
1142
  scopes: Joi.array()
1072
1143
  .items(Joi.string().example('https://mail.google.com/').label('ScopeEntry').description('OAuth2 permission scope'))
1073
1144
  .description('Requested OAuth2 permission scopes')
@@ -1079,7 +1150,10 @@ const lastErrorSchema = Joi.object({
1079
1150
  })
1080
1151
  .description('Raw error response from the OAuth2 server')
1081
1152
  .unknown()
1082
- }).description('Details about the failed OAuth2 token request')
1153
+ .label('OAuthTokenErrorResponse')
1154
+ })
1155
+ .description('Details about the failed OAuth2 token request')
1156
+ .label('OAuthTokenRequestError')
1083
1157
  }).label('AccountErrorEntry');
1084
1158
 
1085
1159
  const templateSchemas = {
@@ -1266,17 +1340,24 @@ const accountSchemas = {
1266
1340
  .description(
1267
1341
  'Override global IMAP indexing strategy for this account. "full" tracks all changes including deletions, "fast" only detects new messages.'
1268
1342
  )
1269
- .label('IMAPIndexer')
1343
+ .label('AccountImapIndexer')
1270
1344
  };
1271
1345
 
1272
- const googleProjectIdSchema = Joi.string().trim().allow('', false, null).max(256).example('project-name-425411').description('Google Cloud Project ID');
1346
+ const googleProjectIdSchema = Joi.string()
1347
+ .trim()
1348
+ .allow('', false, null)
1349
+ .max(256)
1350
+ .example('project-name-425411')
1351
+ .description('Google Cloud Project ID')
1352
+ .label('GoogleProjectId');
1273
1353
  const googleTopicNameSchema = Joi.string()
1274
1354
  .trim()
1275
1355
  .allow('', false, null)
1276
1356
  .pattern(/^(?!goog)[A-Za-z][A-Za-z0-9\-_.~+%]{2,254}$/)
1277
1357
  .max(256)
1278
1358
  .example('ee-pub-12345')
1279
- .description('Google Pub/Sub topic name for Gmail push notifications');
1359
+ .description('Google Pub/Sub topic name for Gmail push notifications')
1360
+ .label('GoogleTopicName');
1280
1361
 
1281
1362
  const googleSubscriptionNameSchema = Joi.string()
1282
1363
  .trim()
@@ -1284,7 +1365,8 @@ const googleSubscriptionNameSchema = Joi.string()
1284
1365
  .pattern(/^(?!goog)[A-Za-z][A-Za-z0-9\-_.~+%]{2,254}$/)
1285
1366
  .max(256)
1286
1367
  .example('ee-sub-12345')
1287
- .description('Google Pub/Sub subscription name');
1368
+ .description('Google Pub/Sub subscription name')
1369
+ .label('GoogleSubscriptionName');
1288
1370
 
1289
1371
  const googleWorkspaceAccountsSchema = Joi.boolean()
1290
1372
  .truthy('Y', 'true', '1', 'on')
@@ -1304,7 +1386,8 @@ const oauthCreateSchema = {
1304
1386
  .valid(...Object.keys(OAUTH_PROVIDERS))
1305
1387
  .example('gmail')
1306
1388
  .required()
1307
- .description('OAuth2 provider type'),
1389
+ .description('OAuth2 provider type')
1390
+ .label('CreateOAuth2Provider'),
1308
1391
 
1309
1392
  enabled: Joi.boolean()
1310
1393
  .truthy('Y', 'true', '1', 'on')
@@ -1337,7 +1420,13 @@ const oauthCreateSchema = {
1337
1420
  .example('boT7Q~dUljnfFdVuqpC11g8nGMjO8kpRAv-ZB')
1338
1421
  .description('OAuth2 client secret from the provider'),
1339
1422
 
1340
- baseScopes: Joi.string().empty('').trim().valid('imap', 'api', 'pubsub').example('imap').description('Connection type (IMAP, API, or Pub/Sub)'),
1423
+ baseScopes: Joi.string()
1424
+ .empty('')
1425
+ .trim()
1426
+ .valid('imap', 'api', 'pubsub')
1427
+ .example('imap')
1428
+ .description('Connection type (IMAP, API, or Pub/Sub)')
1429
+ .label('OAuth2BaseScopes'),
1341
1430
 
1342
1431
  pubSubApp: Joi.string()
1343
1432
  .empty('')
@@ -1345,7 +1434,8 @@ const oauthCreateSchema = {
1345
1434
  .max(512)
1346
1435
  .example('AAAAAQAACnA')
1347
1436
  .allow(false, null)
1348
- .description('Pub/Sub application ID for Gmail push notifications'),
1437
+ .description('Pub/Sub application ID for Gmail push notifications')
1438
+ .label('PubSubAppId'),
1349
1439
 
1350
1440
  extraScopes: Joi.any()
1351
1441
  .alter({
@@ -1354,8 +1444,9 @@ const oauthCreateSchema = {
1354
1444
  .allow('')
1355
1445
  .trim()
1356
1446
  .example('User.Read')
1357
- .max(10 * 1024),
1358
- api: () => Joi.array().items(Joi.string().trim().max(255).example('User.Read'))
1447
+ .max(10 * 1024)
1448
+ .label('OAuth2ExtraScopesWeb'),
1449
+ api: () => Joi.array().items(Joi.string().trim().max(255).example('User.Read').label('ExtraScopeEntry')).label('OAuth2ExtraScopesApi')
1359
1450
  })
1360
1451
  .description('Additional OAuth2 permission scopes'),
1361
1452
 
@@ -1366,8 +1457,9 @@ const oauthCreateSchema = {
1366
1457
  .allow('')
1367
1458
  .trim()
1368
1459
  .example('SMTP.Send')
1369
- .max(10 * 1024),
1370
- api: () => Joi.array().items(Joi.string().trim().max(255).example('SMTP.Send'))
1460
+ .max(10 * 1024)
1461
+ .label('OAuth2SkipScopesWeb'),
1462
+ api: () => Joi.array().items(Joi.string().trim().max(255).example('SMTP.Send').label('SkipScopeEntry')).label('OAuth2SkipScopesApi')
1371
1463
  })
1372
1464
  .description('OAuth2 scopes to exclude from the default set'),
1373
1465
 
@@ -1381,7 +1473,8 @@ const oauthCreateSchema = {
1381
1473
  otherwise: Joi.optional().valid(false, null)
1382
1474
  })
1383
1475
  .example('7103296518315821565203')
1384
- .description('Service account unique ID (for 2-legged OAuth2)'),
1476
+ .description('Service account unique ID (for 2-legged OAuth2)')
1477
+ .label('ServiceClientId'),
1385
1478
 
1386
1479
  googleProjectId: googleProjectIdSchema,
1387
1480
  googleWorkspaceAccounts: googleWorkspaceAccountsSchema,
@@ -1459,15 +1552,16 @@ const oauthCreateSchema = {
1459
1552
  })
1460
1553
  .example('https://myservice.com/oauth')
1461
1554
  .description('OAuth2 redirect URI configured in the provider')
1555
+ .label('OAuth2AppRedirectUrl')
1462
1556
  };
1463
1557
 
1464
1558
  const tokenRestrictionsSchema = Joi.object({
1465
1559
  referrers: Joi.array()
1466
1560
  .items(Joi.string())
1467
- .empty('')
1561
+ .empty(Joi.valid('', false))
1468
1562
  .single()
1469
- .allow(false)
1470
- .default(false)
1563
+ .allow(null)
1564
+ .default(null)
1471
1565
  .example(['*web.domain.org/*', '*.domain.org/*', 'https://domain.org/*'])
1472
1566
  .label('ReferrerAllowlist')
1473
1567
  .description('HTTP referrer patterns that are allowed to use this token (wildcards supported)'),
@@ -1478,10 +1572,10 @@ const tokenRestrictionsSchema = Joi.object({
1478
1572
  cidr: 'optional'
1479
1573
  })
1480
1574
  )
1481
- .empty('')
1575
+ .empty(Joi.valid('', false))
1482
1576
  .single()
1483
- .allow(false)
1484
- .default(false)
1577
+ .allow(null)
1578
+ .default(null)
1485
1579
  .example(['1.2.3.4', '5.6.7.8', '127.0.0.0/8'])
1486
1580
  .label('AddressAllowlist')
1487
1581
  .description('IP addresses or CIDR ranges allowed to use this token'),
@@ -1489,14 +1583,15 @@ const tokenRestrictionsSchema = Joi.object({
1489
1583
  maxRequests: Joi.number().integer().min(1).example(20).description('Maximum requests allowed in the time window'),
1490
1584
  timeWindow: Joi.number().integer().min(1).example(2).description('Time window duration in seconds')
1491
1585
  })
1492
- .allow(false)
1493
- .default(false)
1586
+ .empty(Joi.valid('', false))
1587
+ .allow(null)
1588
+ .default(null)
1494
1589
  .example({ maxRequests: 20, timeWindow: 2 })
1495
1590
  .label('AddressRateLimit')
1496
1591
  .description('Rate limiting configuration for this token')
1497
1592
  })
1498
- .empty('')
1499
- .allow(false)
1593
+ .empty(Joi.valid('', false))
1594
+ .allow(null)
1500
1595
  .label('TokenRestrictions')
1501
1596
  .description('Security restrictions for API token usage');
1502
1597
 
@@ -1539,13 +1634,15 @@ const defaultAccountTypeSchema = Joi.string()
1539
1634
  const outboxEntrySchema = Joi.object({
1540
1635
  queueId: Joi.string().example('1869c5692565f756b33').description('Unique queue entry identifier'),
1541
1636
  account: accountIdSchema.required(),
1542
- source: Joi.string().example('smtp').valid('smtp', 'api').description('How this message entered the queue'),
1637
+ source: Joi.string().example('smtp').valid('smtp', 'api').description('How this message entered the queue').label('OutboxSource'),
1543
1638
 
1544
1639
  messageId: Joi.string().max(996).example('<test123@example.com>').description('Message-ID header value'),
1545
1640
  envelope: Joi.object({
1546
1641
  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'),
1642
+ to: Joi.array().items(Joi.string().email().required().example('recipient@example.com')).label('OutboxEnvelopeTo')
1643
+ })
1644
+ .description('SMTP envelope information')
1645
+ .label('OutboxEnvelope'),
1549
1646
 
1550
1647
  subject: Joi.string()
1551
1648
  .allow('')
@@ -1561,7 +1658,7 @@ const outboxEntrySchema = Joi.object({
1561
1658
  attempts: Joi.number().integer().example(3).description('Maximum delivery attempts before marking as failed'),
1562
1659
 
1563
1660
  progress: Joi.object({
1564
- status: Joi.string().valid('queued', 'processing', 'submitted', 'error').example('queued').description('Current delivery status'),
1661
+ status: Joi.string().valid('queued', 'processing', 'submitted', 'error').example('queued').description('Current delivery status').label('OutboxStatus'),
1565
1662
  response: Joi.string().example('250 Message Accepted').description('SMTP server response (when status is "processing")'),
1566
1663
  error: Joi.object({
1567
1664
  message: Joi.string().example('Authentication failed').description('Error description'),
@@ -1586,7 +1683,8 @@ const messageReferenceSchema = Joi.object({
1586
1683
  .valid('forward', 'reply', 'reply-all')
1587
1684
  .example('reply')
1588
1685
  .default('reply')
1589
- .description('Action type: "reply" (reply to sender), "reply-all" (reply to all recipients), or "forward" (forward to new recipients)'),
1686
+ .description('Action type: "reply" (reply to sender), "reply-all" (reply to all recipients), or "forward" (forward to new recipients)')
1687
+ .label('MessageAction'),
1590
1688
 
1591
1689
  inline: Joi.boolean()
1592
1690
  .truthy('Y', 'true', '1')
@@ -1643,6 +1741,96 @@ const headerTimeoutSchema = Joi.number()
1643
1741
  .description('Request timeout in milliseconds (overrides EENGINE_TIMEOUT environment variable)')
1644
1742
  .label('X-EE-Timeout');
1645
1743
 
1744
+ // Export schemas
1745
+ const exportRequestSchema = Joi.object({
1746
+ folders: Joi.array()
1747
+ .items(Joi.string().max(1024).example('INBOX'))
1748
+ .single()
1749
+ .description(
1750
+ '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.'
1751
+ )
1752
+ .label('ExportFolders'),
1753
+ startDate: Joi.date().iso().required().example('2024-01-01T00:00:00Z').description('Export messages from this date'),
1754
+ endDate: Joi.date().iso().required().example('2024-12-31T23:59:59Z').description('Export messages until this date'),
1755
+ textType: Joi.string()
1756
+ .valid('plain', 'html', '*')
1757
+ .default('*')
1758
+ .example('*')
1759
+ .description('Text content to include: "plain", "html", "*" (both), or omit for metadata only')
1760
+ .label('ExportTextType'),
1761
+ maxBytes: Joi.number()
1762
+ .integer()
1763
+ .min(0)
1764
+ .default(5 * 1024 * 1024)
1765
+ .example(5242880)
1766
+ .description('Maximum bytes for text content (0 = unlimited)'),
1767
+ includeAttachments: Joi.boolean()
1768
+ .truthy('Y', 'true', '1')
1769
+ .falsy('N', 'false', 0)
1770
+ .default(false)
1771
+ .description('Include attachment content as base64 blob in attachments array')
1772
+ })
1773
+ .custom((value, helpers) => {
1774
+ if (value.startDate && value.endDate && value.startDate >= value.endDate) {
1775
+ return helpers.error('any.invalid');
1776
+ }
1777
+ return value;
1778
+ }, 'date range validation')
1779
+ .messages({ 'any.invalid': 'startDate must be before endDate' })
1780
+ .label('ExportRequest');
1781
+
1782
+ const exportProgressSchema = Joi.object({
1783
+ foldersScanned: Joi.number().integer().example(1).description('Number of folders scanned'),
1784
+ foldersTotal: Joi.number().integer().example(2).description('Total number of folders to scan'),
1785
+ messagesQueued: Joi.number().integer().example(1500).description('Number of messages queued for export'),
1786
+ messagesExported: Joi.number().integer().example(500).description('Number of messages exported'),
1787
+ messagesSkipped: Joi.number().integer().example(5).description('Number of messages skipped (deleted or inaccessible)'),
1788
+ bytesWritten: Joi.number().integer().example(52428800).description('Bytes written to export file')
1789
+ }).label('ExportProgress');
1790
+
1791
+ const exportStatusSchema = Joi.object({
1792
+ exportId: Joi.string().example('exp_abc123def456abc123def456').description('Export job identifier'),
1793
+ status: Joi.string()
1794
+ .valid('queued', 'processing', 'completed', 'failed', 'cancelled')
1795
+ .example('processing')
1796
+ .description('Export status')
1797
+ .label('ExportStatusValue'),
1798
+ phase: Joi.string().valid('indexing', 'exporting', 'complete').example('indexing').description('Current export phase').label('ExportPhase'),
1799
+ folders: Joi.array().items(Joi.string().label('ExportFolderItem')).description('Folders being exported').label('ExportStatusFolders'),
1800
+ startDate: Joi.date().iso().example('2024-01-01T00:00:00Z').description('Export start date filter'),
1801
+ endDate: Joi.date().iso().example('2024-12-31T23:59:59Z').description('Export end date filter'),
1802
+ isEncrypted: Joi.boolean().example(false).description('Whether the export file is encrypted'),
1803
+ progress: exportProgressSchema,
1804
+ created: Joi.date().iso().example('2024-01-15T10:30:00Z').description('When export was created'),
1805
+ expiresAt: Joi.date().iso().example('2024-01-16T10:30:00Z').description('When export file expires'),
1806
+ error: Joi.string().allow(null).description('Error message if export failed')
1807
+ }).label('ExportStatus');
1808
+
1809
+ const exportListEntrySchema = Joi.object({
1810
+ exportId: Joi.string().example('exp_abc123def456abc123def456').description('Export job identifier'),
1811
+ status: Joi.string()
1812
+ .valid('queued', 'processing', 'completed', 'failed', 'cancelled')
1813
+ .example('completed')
1814
+ .description('Export status')
1815
+ .label('ExportListStatusValue'),
1816
+ created: Joi.date().iso().example('2024-01-15T10:30:00Z').description('When export was created'),
1817
+ expiresAt: Joi.date().iso().example('2024-01-16T10:30:00Z').description('When export file expires')
1818
+ }).label('ExportListEntry');
1819
+
1820
+ const exportListSchema = Joi.object({
1821
+ total: Joi.number().integer().example(5).description('Total number of exports'),
1822
+ page: Joi.number().integer().example(0).description('Current page number'),
1823
+ pages: Joi.number().integer().example(1).description('Total number of pages'),
1824
+ exports: Joi.array().items(exportListEntrySchema).description('Export entries').label('ExportEntries')
1825
+ }).label('ExportList');
1826
+
1827
+ const exportIdSchema = Joi.string()
1828
+ .pattern(/^exp_[a-f0-9]{24}$/)
1829
+ .required()
1830
+ .example('exp_abc123def456abc123def456')
1831
+ .description('Export job identifier')
1832
+ .label('ExportId');
1833
+
1646
1834
  module.exports = {
1647
1835
  ADDRESS_STRATEGIES,
1648
1836
 
@@ -1684,7 +1872,12 @@ module.exports = {
1684
1872
  googleWorkspaceAccountsSchema,
1685
1873
  messageReferenceSchema,
1686
1874
  idempotencyKeySchema,
1687
- headerTimeoutSchema
1875
+ headerTimeoutSchema,
1876
+ exportRequestSchema,
1877
+ exportStatusSchema,
1878
+ exportListSchema,
1879
+ exportProgressSchema,
1880
+ exportIdSchema
1688
1881
  };
1689
1882
 
1690
1883
  /*