emailengine-app 2.68.1 → 2.70.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 (95) hide show
  1. package/.github/workflows/deploy.yml +8 -3
  2. package/.github/workflows/release.yaml +6 -0
  3. package/CHANGELOG.md +59 -0
  4. package/Gruntfile.js +3 -1
  5. package/config/default.toml +2 -0
  6. package/data/google-crawlers.json +7 -1
  7. package/getswagger.sh +40 -4
  8. package/gettext-extract.js +163 -0
  9. package/lib/account.js +135 -72
  10. package/lib/api-routes/account-routes.js +684 -106
  11. package/lib/api-routes/blocklist-routes.js +344 -0
  12. package/lib/api-routes/chat-routes.js +32 -14
  13. package/lib/api-routes/delivery-test-routes.js +346 -0
  14. package/lib/api-routes/export-routes.js +28 -14
  15. package/lib/api-routes/gateway-routes.js +427 -0
  16. package/lib/api-routes/license-routes.js +156 -0
  17. package/lib/api-routes/mailbox-routes.js +344 -0
  18. package/lib/api-routes/message-routes.js +221 -187
  19. package/lib/api-routes/oauth2-app-routes.js +697 -0
  20. package/lib/api-routes/outbox-routes.js +185 -0
  21. package/lib/api-routes/pubsub-routes.js +102 -0
  22. package/lib/api-routes/route-helpers.js +58 -0
  23. package/lib/api-routes/settings-routes.js +357 -0
  24. package/lib/api-routes/stats-routes.js +111 -0
  25. package/lib/api-routes/submit-routes.js +461 -0
  26. package/lib/api-routes/template-routes.js +60 -75
  27. package/lib/api-routes/token-routes.js +297 -0
  28. package/lib/api-routes/webhook-route-routes.js +181 -0
  29. package/lib/autodetect-imap-settings.js +0 -2
  30. package/lib/consts.js +5 -0
  31. package/lib/email-client/base-client.js +28 -6
  32. package/lib/email-client/gmail-client.js +133 -112
  33. package/lib/email-client/imap/mailbox.js +34 -11
  34. package/lib/email-client/imap/subconnection.js +20 -13
  35. package/lib/email-client/imap/sync-operations.js +131 -3
  36. package/lib/email-client/imap-client.js +152 -75
  37. package/lib/email-client/notification-handler.js +1 -4
  38. package/lib/email-client/outlook-client.js +134 -75
  39. package/lib/export.js +97 -20
  40. package/lib/feature-flags.js +2 -2
  41. package/lib/gateway.js +4 -9
  42. package/lib/get-raw-email.js +5 -5
  43. package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
  44. package/lib/imapproxy/imap-core/lib/imap-command.js +6 -1
  45. package/lib/imapproxy/imap-core/lib/imap-connection.js +106 -24
  46. package/lib/imapproxy/imap-core/lib/imap-server.js +24 -0
  47. package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
  48. package/lib/logger.js +24 -21
  49. package/lib/message-port-stream.js +113 -16
  50. package/lib/metrics-collector.js +0 -2
  51. package/lib/oauth2-apps.js +13 -4
  52. package/lib/outbox.js +24 -40
  53. package/lib/redis-operations.js +1 -1
  54. package/lib/reject-worker-calls.js +42 -0
  55. package/lib/routes-ui.js +37 -8778
  56. package/lib/schemas.js +429 -84
  57. package/lib/sentry.js +139 -0
  58. package/lib/settings.js +9 -3
  59. package/lib/stream-encrypt.js +1 -1
  60. package/lib/templates.js +1 -1
  61. package/lib/tokens.js +5 -3
  62. package/lib/tools.js +70 -4
  63. package/lib/ui-routes/account-routes.js +45 -212
  64. package/lib/ui-routes/admin-config-routes.js +928 -489
  65. package/lib/ui-routes/admin-entities-routes.js +1 -0
  66. package/lib/ui-routes/auth-routes.js +1339 -0
  67. package/lib/ui-routes/dashboard-routes.js +188 -0
  68. package/lib/ui-routes/document-store-routes.js +800 -0
  69. package/lib/ui-routes/export-routes.js +217 -0
  70. package/lib/ui-routes/internals-routes.js +354 -0
  71. package/lib/ui-routes/network-config-routes.js +759 -0
  72. package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +369 -91
  73. package/lib/ui-routes/route-helpers.js +314 -0
  74. package/lib/ui-routes/smtp-test-routes.js +236 -0
  75. package/lib/ui-routes/unsubscribe-routes.js +232 -0
  76. package/lib/webhook-request.js +36 -0
  77. package/lib/webhooks.js +8 -4
  78. package/package.json +13 -12
  79. package/sbom.json +1 -1
  80. package/server.js +222 -39
  81. package/static/licenses.html +160 -300
  82. package/translations/messages.pot +112 -132
  83. package/update-info.sh +19 -1
  84. package/views/config/logging.hbs +48 -0
  85. package/views/dashboard.hbs +7 -26
  86. package/views/internals/index.hbs +15 -0
  87. package/views/tokens/index.hbs +9 -0
  88. package/workers/api.js +200 -4424
  89. package/workers/documents.js +2 -22
  90. package/workers/export.js +103 -104
  91. package/workers/imap-proxy.js +3 -23
  92. package/workers/imap.js +32 -36
  93. package/workers/smtp.js +2 -22
  94. package/workers/submit.js +26 -35
  95. package/workers/webhooks.js +9 -43
package/lib/schemas.js CHANGED
@@ -5,6 +5,7 @@ const config = require('@zone-eu/wild-config');
5
5
  const { getByteSize } = require('./tools');
6
6
  const { locales } = require('./translations');
7
7
  const { LEGACY_KEYS, OAUTH_PROVIDERS } = require('./oauth2-apps');
8
+ const { ACCOUNT_STATES } = require('./account/account-state');
8
9
  const { validateConfig: validateExternalAccountConfig } = require('./oauth/external-account-config');
9
10
 
10
11
  // Shared Joi custom validator for the gmailService external_account credential
@@ -47,6 +48,10 @@ const ADDRESS_STRATEGIES = [
47
48
 
48
49
  const accountIdSchema = Joi.string().empty('').trim().max(256).example('user123').description('Unique identifier for the email account');
49
50
 
51
+ // All states that the account state field can expose over the API. In addition to the state
52
+ // machine states, 'disconnected' and 'paused' are client-level display states set by imap-client
53
+ const ACCOUNT_DISPLAY_STATES = [...Object.values(ACCOUNT_STATES), 'disconnected', 'paused'];
54
+
50
55
  // Allowed configuration keys
51
56
  const settingsSchema = {
52
57
  /* ────────────── Webhooks ────────────── */
@@ -400,6 +405,23 @@ const settingsSchema = {
400
405
  maxLogLines: Joi.number().integer().min(0).max(1000000).default(10000).description('Maximum number of log entries to retain per account')
401
406
  }).label('LogSettings'),
402
407
 
408
+ /* ────────────── Error Reporting ────────────── */
409
+
410
+ sentryEnabled: Joi.boolean()
411
+ .truthy('Y', 'true', '1', 'on')
412
+ .falsy('N', 'false', 0, '')
413
+ .example(false)
414
+ .description(
415
+ 'Send unhandled error reports to a Sentry server. Applied at runtime, no restart needed. Ignored when the SENTRY_DSN environment variable is set'
416
+ ),
417
+
418
+ sentryDsn: Joi.string()
419
+ .uri({ scheme: ['http', 'https'], allowRelative: false })
420
+ .allow('', null)
421
+ .example('https://public-key@sentry.example.com/1')
422
+ .description('Sentry DSN to send error reports to. If empty, reports go to the Sentry instance run by the EmailEngine developers')
423
+ .label('SentryDsn'),
424
+
403
425
  /* ────────────── Local Address Strategy ────────────── */
404
426
 
405
427
  imapStrategy: Joi.string()
@@ -932,10 +954,27 @@ const attachmentSchema = Joi.object({
932
954
  method: Joi.string().example('REQUEST').description('Calendar method (REQUEST, REPLY, CANCEL, etc.) for iCalendar attachments')
933
955
  }).label('AttachmentEntry');
934
956
 
935
- const AddressListSchema = Joi.array().items(addressSchema.label('RcptAddressEntry')).description('List of email addresses').label('AddressList');
957
+ // Response-side address entry. Mail servers can return name-only entries (empty address) and
958
+ // group-syntax artifacts (e.g. "undisclosed-recipients@") that are not valid email addresses,
959
+ // so this is laxer than the request-side addressSchema.
960
+ const responseAddressSchema = Joi.object({
961
+ name: Joi.string().trim().empty('').max(256).example('Some Name').description('Display name for the email address'),
962
+ address: Joi.string().allow('').example('user@example.com').description('Email address (can be empty for name-only entries)')
963
+ })
964
+ .description('Email address with optional display name')
965
+ .label('EmailAddressResponse');
966
+
967
+ const AddressListSchema = Joi.array().items(responseAddressSchema.label('RcptAddressEntry')).description('List of email addresses').label('AddressList');
936
968
 
969
+ // Request-side sender address - keeps strict email validation
937
970
  const fromAddressSchema = addressSchema.example({ name: 'From Me', address: 'sender@example.com' }).description('Sender email address').label('FromAddress');
938
971
 
972
+ // Response-side sender address - lax, see responseAddressSchema
973
+ const responseFromAddressSchema = responseAddressSchema
974
+ .example({ name: 'From Me', address: 'sender@example.com' })
975
+ .description('Sender email address')
976
+ .label('FromAddressResponse');
977
+
939
978
  const emailIdSchema = Joi.string()
940
979
  .max(256)
941
980
  .example('1278455344230334865')
@@ -947,23 +986,56 @@ const threadIdSchema = Joi.string()
947
986
  .description('Thread identifier for email conversations (when supported by the email server)')
948
987
  .label('MessageThreadId');
949
988
 
989
+ const messageSpecialUseSchema = Joi.string()
990
+ .example('\\Sent')
991
+ .valid('\\Drafts', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
992
+ .description('Special folder type where this message is stored')
993
+ .label('MessageSpecialUse');
994
+
995
+ const messageCategorySchema = Joi.string()
996
+ .example('primary')
997
+ .valid('primary', 'social', 'promotions', 'updates', 'forums')
998
+ .description('Gmail inbox tab/category of the message (Gmail API accounts only)')
999
+ .label('MessageCategory');
1000
+
1001
+ const bounceListSchema = Joi.array()
1002
+ .items(
1003
+ Joi.object({
1004
+ message: Joi.string().max(256).required().example('AAAAAQAACnA').description('EmailEngine identifier of the bounce notification'),
1005
+ recipient: Joi.string().email().example('recipient@example.com').description('Email address that bounced'),
1006
+ action: Joi.string().example('failed').description('Bounce action (failed, delayed, etc.)').label('BounceAction'),
1007
+ response: Joi.object({
1008
+ message: Joi.string().example('550 5.1.1 No such user').description('Error message from the receiving server'),
1009
+ status: Joi.string().example('5.1.1').description('SMTP status code')
1010
+ }).label('BounceResponse'),
1011
+ date: Joi.date().iso().example('2021-03-22T13:13:31.000Z').description('When the bounce was detected')
1012
+ }).label('BounceEntry')
1013
+ )
1014
+ .description('Bounce notifications detected for this message (IMAP accounts only)')
1015
+ .label('BounceList');
1016
+
950
1017
  const messageEntrySchema = Joi.object({
951
1018
  id: Joi.string().example('AAAAAgAACrI').description('EmailEngine message identifier').label('MessageEntryId'),
952
- uid: Joi.number().integer().example(12345).description('IMAP UID (unique identifier within the mailbox)').label('MessageUid'),
1019
+ uid: Joi.number().integer().example(12345).description('IMAP UID (unique identifier within the mailbox). IMAP accounts only').label('MessageUid'),
953
1020
  emailId: emailIdSchema,
954
1021
  threadId: threadIdSchema,
1022
+ path: Joi.string()
1023
+ .example('INBOX')
1024
+ .description(
1025
+ 'Mailbox folder path that contains this message. Returned when listing a virtual folder like "\\\\All" or when extended info is requested'
1026
+ ),
955
1027
  date: Joi.date().iso().example('2021-03-22T13:13:31.000Z').description('Date when the message was received by the mail server'),
956
1028
  draft: Joi.boolean().example(false).description('Whether this message is a draft'),
957
1029
  unseen: Joi.boolean().example(true).description('Whether this message is unread'),
958
1030
  flagged: Joi.boolean().example(true).description('Whether this message is flagged/starred'),
1031
+ answered: Joi.boolean().example(false).description('Whether this message has been answered (IMAP accounts only)'),
959
1032
  size: Joi.number().integer().example(1040).description('Message size in bytes'),
960
1033
  subject: Joi.string().allow('').example('What a wonderful message').description('Message subject line'),
961
1034
 
962
- from: fromAddressSchema,
1035
+ from: responseFromAddressSchema,
963
1036
  replyTo: AddressListSchema,
964
1037
  to: AddressListSchema,
965
1038
  cc: AddressListSchema,
966
- bcc: AddressListSchema,
967
1039
  messageId: Joi.string().example('<test123@example.com>').description('Message-ID header value'),
968
1040
  inReplyTo: Joi.string().example('<7JBUMt0WOn+_==MOkaCOQ@mail.gmail.com>').description('Message-ID of the message this is replying to'),
969
1041
 
@@ -982,29 +1054,30 @@ const messageEntrySchema = Joi.object({
982
1054
  .label('TextEncodedSize')
983
1055
  }).label('TextInfo'),
984
1056
 
985
- preview: Joi.string().description('Short preview of the message content')
986
- }).label('MessageListEntry');
1057
+ preview: Joi.string().description('Short preview of the message content'),
987
1058
 
988
- const messageSpecialUseSchema = Joi.string()
989
- .example('\\Sent')
990
- .valid('\\Drafts', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
991
- .description('Special folder type where this message is stored')
992
- .label('MessageSpecialUse');
1059
+ category: messageCategorySchema,
1060
+ messageSpecialUse: messageSpecialUseSchema,
1061
+ isAutoReply: Joi.boolean().example(false).description('Whether this message appears to be an automatic reply (out of office, vacation responder, etc.)'),
1062
+ bounces: bounceListSchema
1063
+ }).label('MessageListEntry');
993
1064
 
994
1065
  const messageDetailsSchema = Joi.object({
995
1066
  id: Joi.string().example('AAAAAgAACrI').description('EmailEngine message identifier').label('MessageEntryId'),
996
- uid: Joi.number().integer().example(12345).description('IMAP UID (unique identifier within the mailbox)').label('MessageUid'),
1067
+ uid: Joi.number().integer().example(12345).description('IMAP UID (unique identifier within the mailbox). IMAP accounts only').label('MessageUid'),
997
1068
  emailId: emailIdSchema,
998
1069
  threadId: threadIdSchema,
1070
+ path: Joi.string().example('INBOX').description('Mailbox folder path that contains this message. Not returned for Gmail API accounts'),
999
1071
  date: Joi.date().iso().example('2021-03-22T13:13:31.000Z').description('Date when the message was received by the mail server'),
1000
1072
  draft: Joi.boolean().example(false).description('Whether this message is a draft'),
1001
1073
  unseen: Joi.boolean().example(true).description('Whether this message is unread'),
1002
1074
  flagged: Joi.boolean().example(true).description('Whether this message is flagged/starred'),
1003
- size: Joi.number().integer().example(1040).description('Message size in bytes'),
1075
+ answered: Joi.boolean().example(false).description('Whether this message has been answered (IMAP accounts only)'),
1076
+ size: Joi.number().integer().example(1040).description('Message size in bytes. Not returned for MS Graph API accounts'),
1004
1077
  subject: Joi.string().allow('').example('What a wonderful message').description('Message subject line'),
1005
1078
 
1006
- from: fromAddressSchema,
1007
- sender: fromAddressSchema,
1079
+ from: responseFromAddressSchema,
1080
+ sender: responseFromAddressSchema,
1008
1081
 
1009
1082
  to: AddressListSchema,
1010
1083
 
@@ -1024,9 +1097,7 @@ const messageDetailsSchema = Joi.object({
1024
1097
  headers: Joi.object()
1025
1098
  .example({ from: ['From Me <sender@example.com>'], subject: ['What a wonderful message'] })
1026
1099
  .label('MessageHeaders')
1027
- .description(
1028
- 'Raw email headers as key-value pairs (arrays contain multiple values for headers that appear multiple times). Not available for MS Graph API.'
1029
- )
1100
+ .description('Raw email headers as key-value pairs (arrays contain multiple values for headers that appear multiple times)')
1030
1101
  .unknown(),
1031
1102
 
1032
1103
  text: Joi.object({
@@ -1039,100 +1110,136 @@ const messageDetailsSchema = Joi.object({
1039
1110
  .label('TextDetailEncodedSize'),
1040
1111
  plain: Joi.string().example('Hello from myself!').description('Plain text version of the message'),
1041
1112
  html: Joi.string().example('<p>Hello from myself!</p>').description('HTML version of the message'),
1042
- hasMore: Joi.boolean().example(false).description('Whether the message content was truncated (true if more content is available via separate API call)')
1113
+ hasMore: Joi.boolean()
1114
+ .example(false)
1115
+ .description('Whether the message content was truncated (true if more content is available via separate API call)'),
1116
+ webSafe: Joi.boolean()
1117
+ .example(true)
1118
+ .description('Whether the HTML content has been processed into a web-safe version (set when webSafeHtml was requested)')
1043
1119
  }).label('TextInfoDetails'),
1044
1120
 
1045
- bounces: Joi.array()
1046
- .items(
1047
- Joi.object({
1048
- message: Joi.string().max(256).required().example('AAAAAQAACnA').description('EmailEngine identifier of the bounce notification'),
1049
- recipient: Joi.string().email().example('recipient@example.com').description('Email address that bounced'),
1050
- action: Joi.string().example('failed').description('Bounce action (failed, delayed, etc.)').label('BounceAction'),
1051
- response: Joi.object({
1052
- message: Joi.string().example('550 5.1.1 No such user').description('Error message from the receiving server'),
1053
- status: Joi.string().example('5.1.1').description('SMTP status code')
1054
- }).label('BounceResponse'),
1055
- date: Joi.date().iso().example('2021-03-22T13:13:31.000Z').description('When the bounce was detected')
1056
- }).label('BounceEntry')
1057
- )
1058
- .label('BounceList'),
1121
+ bounces: bounceListSchema,
1059
1122
 
1060
1123
  isAutoReply: Joi.boolean().example(false).description('Whether this message appears to be an automatic reply (out of office, vacation responder, etc.)'),
1061
1124
 
1125
+ preview: Joi.string().description('Short preview of the message content (Gmail API and MS Graph API accounts only)'),
1126
+ category: messageCategorySchema,
1127
+
1062
1128
  specialUse: Joi.string()
1063
1129
  .example('\\Sent')
1064
1130
  .valid('\\All', '\\Archive', '\\Drafts', '\\Flagged', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
1065
- .description('Special folder type of the mailbox containing this message')
1131
+ .description('Special folder type of the mailbox containing this message (IMAP accounts only)')
1066
1132
  .label('MailboxSpecialUse'),
1067
1133
  messageSpecialUse: messageSpecialUseSchema
1068
1134
  }).label('MessageDetails');
1069
1135
 
1136
+ // Combined Document Store query echoed back by search endpoints when the (hidden) exposeQuery
1137
+ // query parameter is enabled. Free-form object, mirrors whatever was sent to the backing index
1138
+ const documentStoreQuerySchema = Joi.object()
1139
+ .unknown()
1140
+ .description('Combined Document Store query. Only present when the exposeQuery query parameter is enabled')
1141
+ .label('ExposedDocumentStoreQuery')
1142
+ .meta({ swaggerHidden: true });
1143
+
1070
1144
  const messageListSchema = Joi.object({
1071
1145
  total: Joi.number()
1072
1146
  .integer()
1073
1147
  .example(120)
1074
- .description('Total number of messages matching the query (exact for IMAP, approximate for Gmail API)')
1148
+ .description(
1149
+ 'Total number of messages matching the query (exact for IMAP, approximate for Gmail API, can be omitted for MS Graph API and Outlook search results)'
1150
+ )
1075
1151
  .label('TotalNumber'),
1076
1152
  page: Joi.number().integer().example(0).description('Current page number (zero-based)').label('PageNumber'),
1077
- pages: Joi.number().integer().example(24).description('Total number of pages available (exact for IMAP, approximate for Gmail API)').label('PagesNumber'),
1153
+ pages: Joi.number()
1154
+ .integer()
1155
+ .example(24)
1156
+ .description('Total number of pages available (exact for IMAP, approximate for Gmail API, can be omitted for MS Graph API and Outlook search results)')
1157
+ .label('PagesNumber'),
1078
1158
  nextPageCursor: Joi.string()
1079
1159
  .allow(null)
1080
1160
  .example('imap_kcQIji3UobDDTxc')
1081
1161
  .description('Cursor for fetching the next page (null when no more pages)')
1082
1162
  .label('NextPageCursor'),
1083
1163
  prevPageCursor: Joi.string().allow(null).example('imap_kcQIji3UobDDTxc').description('Cursor for fetching the previous page').label('PrevPageCursor'),
1084
- messages: Joi.array().items(messageEntrySchema).label('PageMessages')
1164
+ messages: Joi.array().items(messageEntrySchema).label('PageMessages'),
1165
+ documentStoreQuery: documentStoreQuerySchema
1085
1166
  }).label('MessageList');
1086
1167
 
1168
+ // Fields shared by both mailbox listing item shapes (the full listing and the short listing in
1169
+ // the account verification response). Joi schema instances are immutable, so the same field
1170
+ // instances are safely referenced from both objects
1171
+ const mailboxBaseFields = {
1172
+ path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to the mailbox').label('MailboxPath'),
1173
+ delimiter: Joi.string()
1174
+ .allow(null)
1175
+ .example('/')
1176
+ .description('Hierarchy delimiter character used in paths (can be null when the server uses a NIL delimiter)'),
1177
+ parentPath: Joi.string()
1178
+ .allow('')
1179
+ .example('Kalender')
1180
+ .description('Path to the parent mailbox. Not set (or empty) for root level mailboxes')
1181
+ .label('MailboxParentPath'),
1182
+ name: Joi.string().required().example('Sünnipäevad').description('Display name of the mailbox').label('MailboxName'),
1183
+ listed: Joi.boolean().example(true).description('Whether this mailbox appears in LIST command results').label('MailboxListed'),
1184
+ subscribed: Joi.boolean().example(true).description('Whether the user is subscribed to this mailbox').label('MailboxSubscribed'),
1185
+ specialUse: Joi.string()
1186
+ .example('\\Sent')
1187
+ .valid('\\All', '\\Archive', '\\Drafts', '\\Flagged', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
1188
+ .description('Special folder type (Inbox, Sent, Drafts, etc.)')
1189
+ .label('MailboxSpecialUse'),
1190
+ specialUseSource: Joi.string()
1191
+ .example('extension')
1192
+ .valid('user', 'extension', 'name')
1193
+ .description('How the special use was determined: "user" (set via API), "extension" (server-provided), or "name" (guessed from folder name)')
1194
+ .label('MailboxSpecialUseSource'),
1195
+ noInferiors: Joi.boolean().example(false).description('Whether this mailbox can contain child mailboxes').label('MailboxNoInferiors')
1196
+ };
1197
+
1087
1198
  const mailboxesSchema = Joi.array()
1088
1199
  .items(
1089
1200
  Joi.object({
1090
- path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to the mailbox').label('MailboxPath'),
1091
- delimiter: Joi.string().example('/').description('Hierarchy delimiter character used in paths'),
1092
- parentPath: Joi.string().required().example('Kalender').description('Path to the parent mailbox').label('MailboxParentPath'),
1093
- name: Joi.string().required().example('Sünnipäevad').description('Display name of the mailbox').label('MailboxName'),
1094
- listed: Joi.boolean().example(true).description('Whether this mailbox appears in LIST command results').label('MailboxListed'),
1095
- subscribed: Joi.boolean().example(true).description('Whether the user is subscribed to this mailbox').label('MailboxSubscribed'),
1096
- specialUse: Joi.string()
1097
- .example('\\Sent')
1098
- .valid('\\All', '\\Archive', '\\Drafts', '\\Flagged', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
1099
- .description('Special folder type (Inbox, Sent, Drafts, etc.)')
1100
- .label('MailboxSpecialUse'),
1101
- specialUseSource: Joi.string()
1102
- .example('extension')
1103
- .valid('user', 'extension', 'name')
1104
- .description('How the special use was determined: "user" (set via API), "extension" (server-provided), or "name" (guessed from folder name)')
1105
- .label('MailboxSpecialUseSource'),
1106
- noInferiors: Joi.boolean().example(false).description('Whether this mailbox can contain child mailboxes').label('MailboxNoInferiors'),
1107
- messages: Joi.number().integer().example(120).description('Total number of messages in the mailbox').label('MailboxMessages'),
1108
- uidNext: Joi.number().integer().example(121).description('Next UID value that will be assigned').label('MailboxUidNext'),
1201
+ path: mailboxBaseFields.path,
1202
+ id: Joi.string()
1203
+ .example('AAMkAGVmMDEzMTM4LTZmYWUtNDdkNC1hMDZiLTU1OGY5OTZhYmY4OAAuAAAAAAAiQ8W967B7TKBjgx9rVEURAQAiIsqMbYjsT5e-T7KzowPTAAAAAAEMAAA=')
1204
+ .description('Stable folder ID (Gmail API and MS Graph API accounts only)')
1205
+ .label('MailboxId'),
1206
+ delimiter: mailboxBaseFields.delimiter,
1207
+ parentPath: mailboxBaseFields.parentPath,
1208
+ name: mailboxBaseFields.name,
1209
+ listed: mailboxBaseFields.listed,
1210
+ subscribed: mailboxBaseFields.subscribed,
1211
+ noSelect: Joi.boolean()
1212
+ .example(false)
1213
+ .description('Whether this mailbox can not be selected (virtual folders like "\\\\All")')
1214
+ .label('MailboxNoSelect'),
1215
+ specialUse: mailboxBaseFields.specialUse,
1216
+ specialUseSource: mailboxBaseFields.specialUseSource,
1217
+ noInferiors: mailboxBaseFields.noInferiors,
1218
+ messages: Joi.number()
1219
+ .integer()
1220
+ .allow(false)
1221
+ .example(120)
1222
+ .description('Total number of messages in the mailbox. False if the count is not known')
1223
+ .label('MailboxMessages'),
1224
+ uidNext: Joi.number()
1225
+ .integer()
1226
+ .allow(false)
1227
+ .example(121)
1228
+ .description('Next UID value that will be assigned. False if the value is not known')
1229
+ .label('MailboxUidNext'),
1109
1230
  status: Joi.object({
1110
1231
  messages: Joi.number().integer().example(120).description('Message count from STATUS command').label('StatusMessages'),
1111
- unseen: Joi.number().integer().example(120).description('Unread message count from STATUS command').label('StatusUnseenMessages')
1232
+ unseen: Joi.number().integer().example(120).description('Unread message count from STATUS command').label('StatusUnseenMessages'),
1233
+ error: Joi.string().description('Error message if the STATUS command failed for this mailbox').label('StatusError')
1112
1234
  })
1235
+ .unknown()
1113
1236
  .description('Additional mailbox statistics')
1114
1237
  .label('MailboxResponseStatus')
1115
1238
  }).label('MailboxResponseItem')
1116
1239
  )
1117
1240
  .label('MailboxesList');
1118
1241
 
1119
- const shortMailboxesSchema = Joi.array()
1120
- .items(
1121
- Joi.object({
1122
- path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to the mailbox').label('MailboxPath'),
1123
- delimiter: Joi.string().example('/').description('Hierarchy delimiter character used in paths'),
1124
- parentPath: Joi.string().required().example('Kalender').description('Path to the parent mailbox').label('MailboxParentPath'),
1125
- name: Joi.string().required().example('Sünnipäevad').description('Display name of the mailbox').label('MailboxName'),
1126
- listed: Joi.boolean().example(true).description('Whether this mailbox appears in LIST command results').label('MailboxListed'),
1127
- subscribed: Joi.boolean().example(true).description('Whether the user is subscribed to this mailbox').label('MailboxSubscribed'),
1128
- specialUse: Joi.string()
1129
- .example('\\Sent')
1130
- .valid('\\All', '\\Archive', '\\Drafts', '\\Flagged', '\\Junk', '\\Sent', '\\Trash', '\\Inbox')
1131
- .description('Special folder type (Inbox, Sent, Drafts, etc.)')
1132
- .label('MailboxSpecialUse')
1133
- }).label('MailboxShortResponseItem')
1134
- )
1135
- .label('ShortMailboxes');
1242
+ const shortMailboxesSchema = Joi.array().items(Joi.object(mailboxBaseFields).label('MailboxShortResponseItem')).label('ShortMailboxes');
1136
1243
 
1137
1244
  const licenseSchema = Joi.object({
1138
1245
  active: Joi.boolean().example(true).description('Whether a valid license is currently active'),
@@ -1142,7 +1249,10 @@ const licenseSchema = Joi.object({
1142
1249
  key: Joi.string().hex().example('1edf01e35e75ed3425808eba').description('License key'),
1143
1250
  licensedTo: Joi.string().example('Kreata OÜ').description('Organization or individual the license is issued to'),
1144
1251
  hostname: Joi.string().example('emailengine.example.com').description('Licensed hostname or environment'),
1145
- created: Joi.date().example('2021-10-13T07:47:42.695Z').description('License creation date')
1252
+ created: Joi.date().example('2021-10-13T07:47:42.695Z').description('License creation date'),
1253
+ trial: Joi.boolean().example(false).description('Whether this is a trial license'),
1254
+ lt: Joi.boolean().example(false).description('Whether this is a lifetime license'),
1255
+ expires: Joi.date().iso().example('2026-01-01T00:00:00.000Z').description('License expiration time, if the license is time-limited')
1146
1256
  })
1147
1257
  .allow(false)
1148
1258
  .label('LicenseDetails'),
@@ -1153,8 +1263,11 @@ const lastErrorSchema = Joi.object({
1153
1263
  response: Joi.string()
1154
1264
  .example('Token request failed for gmail (refresh_token, HTTP 400): invalid_grant - Token has been expired or revoked.')
1155
1265
  .description('Human-readable error message'),
1266
+ description: Joi.string().example('Authentication failed').description('Additional details about the error'),
1156
1267
  serverResponseCode: Joi.string().example('OauthRenewError').description('Error code or classification'),
1157
1268
  tokenRequest: Joi.object({
1269
+ url: Joi.string().example('https://oauth2.googleapis.com/token').description('OAuth2 token endpoint URL').label('OAuthTokenUrl'),
1270
+ method: Joi.string().example('post').description('HTTP method used for the token request').label('OAuthTokenMethod'),
1158
1271
  grant: Joi.string()
1159
1272
  .valid('refresh_token', 'authorization_code')
1160
1273
  .example('refresh_token')
@@ -1166,6 +1279,42 @@ const lastErrorSchema = Joi.object({
1166
1279
  .example('1023289917884-h3nu00e9cb7h252e24c23sv19l8k57ah.apps.googleusercontent.com')
1167
1280
  .description('OAuth2 client ID used for authentication')
1168
1281
  .label('OAuthClientId'),
1282
+ clientSecret: Joi.string()
1283
+ .example('GOCSPX...V-Da')
1284
+ .description('Partially masked OAuth2 client secret, included when the OAuth2 server indicates an invalid client secret')
1285
+ .label('OAuthClientSecretPartial'),
1286
+ authority: Joi.string().example('common').description('Microsoft tenant configuration (Outlook applications only)').label('OAuthAuthority'),
1287
+ serviceClient: Joi.string()
1288
+ .example('113328088575113530522')
1289
+ .description('Service client ID (2-legged OAuth2 applications only)')
1290
+ .label('OAuthServiceClient'),
1291
+ signer: Joi.string().example('serviceKey').description('Signing method used for service account token requests').label('OAuthSigner'),
1292
+ googleProjectId: Joi.string()
1293
+ .example('project-name-425411')
1294
+ .description('Google Cloud project ID (Gmail service applications only)')
1295
+ .label('OAuthGoogleProjectId'),
1296
+ serviceClientEmail: Joi.string()
1297
+ .example('service-account@project-name-425411.iam.gserviceaccount.com')
1298
+ .description('Service account email (Gmail service applications only)')
1299
+ .label('OAuthServiceClientEmail'),
1300
+ code: Joi.string()
1301
+ .example('AAABhaBPHscAAAAH')
1302
+ .description('Value of the authorization code or error code related to the failure')
1303
+ .label('OAuthTokenErrorCode'),
1304
+ flag: Joi.object({
1305
+ message: Joi.string().example('Please verify the OAuth2 application credentials').description('Description of the application-level problem'),
1306
+ code: Joi.string().example('INVALID_CLIENT_SECRET').description('Machine-readable code of the application-level problem'),
1307
+ url: Joi.string().example('https://console.developers.google.com/').description('Link with instructions to resolve the problem')
1308
+ })
1309
+ .unknown()
1310
+ .description('Application-level error flag raised by this failure')
1311
+ .label('OAuthTokenErrorFlag'),
1312
+ userFlag: Joi.object({
1313
+ message: Joi.string().example('The grant for this account has been revoked').description('Description of the account-level problem')
1314
+ })
1315
+ .unknown()
1316
+ .description('Account-level error flag raised by this failure')
1317
+ .label('OAuthTokenUserFlag'),
1169
1318
  scopes: Joi.array()
1170
1319
  .items(Joi.string().example('https://mail.google.com/').label('ScopeEntry').description('OAuth2 permission scope'))
1171
1320
  .description('Requested OAuth2 permission scopes')
@@ -1285,6 +1434,23 @@ const searchSchema = Joi.object({
1285
1434
 
1286
1435
  gmailRaw: Joi.string().max(1024).example('has:attachment in:unread').description('Gmail search syntax (only works with Gmail accounts)'),
1287
1436
 
1437
+ labels: Joi.object({
1438
+ has: Joi.array()
1439
+ .items(Joi.string().max(128))
1440
+ .single()
1441
+ .description('Match messages that contain ALL of these labels/categories. Supported for Gmail and MS Graph (Outlook) accounts only.')
1442
+ .example(['Horizon'])
1443
+ .label('HasLabels'),
1444
+ not: Joi.array()
1445
+ .items(Joi.string().max(128))
1446
+ .single()
1447
+ .description('Match messages that contain NONE of these labels/categories. Supported for Gmail and MS Graph (Outlook) accounts only.')
1448
+ .example(['Horizon'])
1449
+ .label('NotLabels')
1450
+ })
1451
+ .description('Filter by Gmail labels or MS Graph (Outlook) categories. Not supported for generic IMAP accounts.')
1452
+ .label('LabelFilter'),
1453
+
1288
1454
  emailIds: Joi.array()
1289
1455
  .items(emailIdSchema)
1290
1456
  .single()
@@ -1855,6 +2021,13 @@ const ipSchema = Joi.string()
1855
2021
  })
1856
2022
  .example('127.0.0.1');
1857
2023
 
2024
+ const tokenIdSchema = Joi.string()
2025
+ .length(64)
2026
+ .hex()
2027
+ .example('1bc12baf7f0d5e51fe0a4e0eda06e1be5b8d6cc2c66b95dc0fbe4d2e9f5d5e1a')
2028
+ .description('Token identifier (SHA-256 hash of the token)')
2029
+ .label('TokenId');
2030
+
1858
2031
  const accountCountersSchema = Joi.object({
1859
2032
  events: Joi.object()
1860
2033
  .unknown()
@@ -1885,7 +2058,7 @@ const defaultAccountTypeSchema = Joi.string()
1885
2058
  const outboxEntrySchema = Joi.object({
1886
2059
  queueId: Joi.string().example('1869c5692565f756b33').description('Unique queue entry identifier'),
1887
2060
  account: accountIdSchema.required(),
1888
- source: Joi.string().example('smtp').valid('smtp', 'api').description('How this message entered the queue').label('OutboxSource'),
2061
+ source: Joi.string().example('smtp').valid('smtp', 'api', 'test', 'ui').description('How this message entered the queue').label('OutboxSource'),
1889
2062
 
1890
2063
  messageId: Joi.string().max(996).example('<test123@example.com>').description('Message-ID header value'),
1891
2064
  envelope: Joi.object({
@@ -1896,25 +2069,50 @@ const outboxEntrySchema = Joi.object({
1896
2069
  .label('OutboxEnvelope'),
1897
2070
 
1898
2071
  subject: Joi.string()
1899
- .allow('')
2072
+ .allow('', null)
1900
2073
  .max(10 * 1024)
1901
2074
  .example('What a wonderful message')
1902
2075
  .description('Email subject line'),
1903
2076
 
2077
+ gateway: Joi.string().allow(null).max(256).example(null).description('Gateway ID used for delivery, or null when no gateway is set'),
2078
+ proxy: Joi.string().allow(null).example(null).description('Proxy URL used for delivery, or null when no proxy is set'),
2079
+ localAddress: Joi.string().allow(null).example(null).description('Local IP address used for delivery, or null when not set'),
2080
+ idempotencyKey: Joi.string().allow(null).max(1024).description('Idempotency key provided when the message was submitted'),
2081
+ useStructuredFormat: Joi.boolean().description('Whether delivery errors for this message are reported in a structured format'),
2082
+
1904
2083
  created: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('When this message was added to the queue'),
1905
2084
  scheduled: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Scheduled delivery time'),
1906
- nextAttempt: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Next delivery attempt time'),
2085
+ nextAttempt: Joi.date()
2086
+ .iso()
2087
+ .allow(false)
2088
+ .example('2021-02-17T13:43:18.860Z')
2089
+ .description(
2090
+ 'Next delivery attempt time (approximate, computed from the retry backoff without jitter). False when all delivery attempts have been used up'
2091
+ ),
1907
2092
 
1908
2093
  attemptsMade: Joi.number().integer().example(3).description('Number of delivery attempts made'),
1909
2094
  attempts: Joi.number().integer().example(3).description('Maximum delivery attempts before marking as failed'),
1910
2095
 
1911
2096
  progress: Joi.object({
1912
- status: Joi.string().valid('queued', 'processing', 'submitted', 'error').example('queued').description('Current delivery status').label('OutboxStatus'),
1913
- response: Joi.string().example('250 Message Accepted').description('SMTP server response (when status is "processing")'),
2097
+ status: Joi.string()
2098
+ .valid('queued', 'processing', 'smtp-starting', 'smtp-completed', 'submitted', 'error')
2099
+ .example('queued')
2100
+ .description('Current delivery status')
2101
+ .label('OutboxStatus'),
2102
+ response: Joi.string().example('250 Message Accepted').description('SMTP server response (when status is "submitted" or "smtp-completed")'),
2103
+ messageId: Joi.string().example('<test123@example.com>').description('Message-ID header value of the sent message (when status is "smtp-completed")'),
2104
+ originalMessageId: Joi.string()
2105
+ .example('<test123@example.com>')
2106
+ .description('Original Message-ID header value if the server assigned a new one (when status is "smtp-completed")'),
1914
2107
  error: Joi.object({
1915
2108
  message: Joi.string().example('Authentication failed').description('Error description'),
1916
2109
  code: Joi.string().example('EAUTH').description('Error code'),
1917
- statusCode: Joi.string().example(502).description('SMTP response code')
2110
+ statusCode: Joi.number().integer().example(502).description('SMTP response code'),
2111
+ networkRouting: Joi.object()
2112
+ .unknown()
2113
+ .allow(null)
2114
+ .description('Network routing information for the failed delivery attempt')
2115
+ .label('OutboxErrorNetworkRouting')
1918
2116
  })
1919
2117
  .label('OutboxListProgressError')
1920
2118
  .description('Error details (when status is "error")')
@@ -2011,7 +2209,7 @@ const exportRequestSchema = Joi.object({
2011
2209
  .valid('plain', 'html', '*')
2012
2210
  .default('*')
2013
2211
  .example('*')
2014
- .description('Text content to include: "plain", "html", "*" (both), or omit for metadata only')
2212
+ .description('Text content to include: "plain", "html", or "*" (both)')
2015
2213
  .label('ExportTextType'),
2016
2214
  maxBytes: Joi.number()
2017
2215
  .integer()
@@ -2058,7 +2256,9 @@ const exportStatusSchema = Joi.object({
2058
2256
  progress: exportProgressSchema,
2059
2257
  created: Joi.date().iso().example('2024-01-15T10:30:00Z').description('When export was created'),
2060
2258
  expiresAt: Joi.date().iso().example('2024-01-16T10:30:00Z').description('When export file expires'),
2061
- truncated: Joi.boolean().example(true).description('Whether the export was truncated due to message count or size limits'),
2259
+ truncated: Joi.boolean()
2260
+ .example(true)
2261
+ .description('Whether the export was truncated due to message count or size limits. Only present (as true) for truncated exports'),
2062
2262
  error: Joi.string().allow(null).description('Error message if export failed')
2063
2263
  }).label('ExportStatus');
2064
2264
 
@@ -2087,8 +2287,146 @@ const exportIdSchema = Joi.string()
2087
2287
  .description('Export job identifier')
2088
2288
  .label('ExportId');
2089
2289
 
2290
+ // Boom-style error payloads returned by failed API requests. These schemas are used for API
2291
+ // documentation only - they are rendered as error response examples in Swagger UI through the
2292
+ // plugins['hapi-swagger'].responses route option and are never used to validate or modify
2293
+ // actual responses at runtime.
2294
+
2295
+ // Builds one documented error response: a response description (shown next to the status code
2296
+ // in Swagger UI) paired with a labeled Joi schema for the Boom-style error payload. The
2297
+ // optional extra fields document values that EmailEngine adds to the base
2298
+ // { statusCode, error, message } payload for that error type.
2299
+ const errorResponseEntry = ({ statusCode, statusText, messageExample, label, title, description, fields }) => ({
2300
+ description,
2301
+ schema: Joi.object({
2302
+ statusCode: Joi.number().integer().example(statusCode).description('HTTP status code'),
2303
+ error: Joi.string().example(statusText).description('HTTP status text'),
2304
+ message: Joi.string().example(messageExample).description('Error message'),
2305
+ ...fields
2306
+ })
2307
+ .label(label)
2308
+ .description(title)
2309
+ });
2310
+
2311
+ const errorResponseSchemas = {
2312
+ 400: errorResponseEntry({
2313
+ statusCode: 400,
2314
+ statusText: 'Bad Request',
2315
+ messageExample: 'Invalid input',
2316
+ label: 'ValidationErrorResponse',
2317
+ title: 'Validation error',
2318
+ description: 'Invalid input - the path parameters, query arguments, or request payload did not pass validation',
2319
+ fields: {
2320
+ fields: Joi.array()
2321
+ .items(
2322
+ Joi.object({
2323
+ message: Joi.string().example('"pageSize" must be less than or equal to 1000').description('Description of the validation failure'),
2324
+ key: Joi.string().example('pageSize').description('Name of the input field that failed validation')
2325
+ }).label('ValidationErrorField')
2326
+ )
2327
+ .description('List of input fields that failed validation')
2328
+ .label('ValidationErrorFields')
2329
+ }
2330
+ }),
2331
+
2332
+ 401: errorResponseEntry({
2333
+ statusCode: 401,
2334
+ statusText: 'Unauthorized',
2335
+ messageExample: 'Missing authentication',
2336
+ label: 'AuthenticationErrorResponse',
2337
+ title: 'Authentication error',
2338
+ description: 'Authentication failed - the access token is missing, invalid, or was not accepted'
2339
+ }),
2340
+
2341
+ 403: errorResponseEntry({
2342
+ statusCode: 403,
2343
+ statusText: 'Forbidden',
2344
+ messageExample: 'Unauthorized scope',
2345
+ label: 'AccessDeniedErrorResponse',
2346
+ title: 'Access denied error',
2347
+ description: 'Access denied - the access token does not grant permission for this operation (token scope, account, or IP address restriction)'
2348
+ }),
2349
+
2350
+ 404: errorResponseEntry({
2351
+ statusCode: 404,
2352
+ statusText: 'Not Found',
2353
+ messageExample: 'Requested message was not found',
2354
+ label: 'NotFoundErrorResponse',
2355
+ title: 'Not found error',
2356
+ description: 'Not found - the requested entity (account, message, mailbox, etc.) does not exist',
2357
+ fields: {
2358
+ code: Joi.string().example('MessageNotFound').description('Machine-readable error code')
2359
+ }
2360
+ }),
2361
+
2362
+ 422: errorResponseEntry({
2363
+ statusCode: 422,
2364
+ statusText: 'Unprocessable Entity',
2365
+ messageExample: 'The mail server does not support the requested operation',
2366
+ label: 'UnprocessableErrorResponse',
2367
+ title: 'Unprocessable request error',
2368
+ description: 'Unprocessable request - the request is well-formed, but the email server cannot perform the requested operation',
2369
+ fields: {
2370
+ code: Joi.string().example('MissingServerExtension').description('Machine-readable error code')
2371
+ }
2372
+ }),
2373
+
2374
+ 429: errorResponseEntry({
2375
+ statusCode: 429,
2376
+ statusText: 'Too Many Requests',
2377
+ messageExample: 'Rate limit exceeded',
2378
+ label: 'RateLimitErrorResponse',
2379
+ title: 'Rate limit error',
2380
+ description: 'Rate limit exceeded - too many requests were made with this access token',
2381
+ fields: {
2382
+ ttl: Joi.number().integer().example(30).description('Seconds until the rate limit window resets')
2383
+ }
2384
+ }),
2385
+
2386
+ 500: errorResponseEntry({
2387
+ statusCode: 500,
2388
+ statusText: 'Internal Server Error',
2389
+ messageExample: 'An internal server error occurred',
2390
+ label: 'InternalErrorResponse',
2391
+ title: 'Internal server error',
2392
+ description: 'Internal server error - an unexpected error occurred while processing the request',
2393
+ fields: {
2394
+ code: Joi.string().example('InternalError').description('Machine-readable error code, if available'),
2395
+ details: Joi.any().description('Additional error details, if available')
2396
+ }
2397
+ }),
2398
+
2399
+ 503: errorResponseEntry({
2400
+ statusCode: 503,
2401
+ statusText: 'Service Unavailable',
2402
+ messageExample: 'No active handler for requested account. Try again later.',
2403
+ label: 'ServiceUnavailableErrorResponse',
2404
+ title: 'Service unavailable error',
2405
+ description: 'Service unavailable - the worker processing this account is not available, try again later',
2406
+ fields: {
2407
+ code: Joi.string().example('WorkerNotAvailable').description('Machine-readable error code')
2408
+ }
2409
+ })
2410
+ };
2411
+
2412
+ // Builds the value for the plugins['hapi-swagger'].responses route option from a list of HTTP
2413
+ // error status codes, so that error responses are rendered in Swagger UI in addition to the
2414
+ // 200 response that hapi-swagger picks up from the route's response.schema option. This is
2415
+ // documentation only and does not affect request processing in any way.
2416
+ function errorResponses(...statusCodes) {
2417
+ let responses = {};
2418
+ for (let statusCode of statusCodes) {
2419
+ if (!errorResponseSchemas[statusCode]) {
2420
+ throw new Error(`Missing error response schema for HTTP status code ${statusCode}`);
2421
+ }
2422
+ responses[statusCode] = errorResponseSchemas[statusCode];
2423
+ }
2424
+ return responses;
2425
+ }
2426
+
2090
2427
  module.exports = {
2091
2428
  ADDRESS_STRATEGIES,
2429
+ ACCOUNT_DISPLAY_STATES,
2092
2430
 
2093
2431
  settingsSchema,
2094
2432
  accountSchemas,
@@ -2104,6 +2442,7 @@ module.exports = {
2104
2442
  messageEntrySchema,
2105
2443
  messageDetailsSchema,
2106
2444
  messageListSchema,
2445
+ documentStoreQuerySchema,
2107
2446
  mailboxesSchema,
2108
2447
  shortMailboxesSchema,
2109
2448
  licenseSchema,
@@ -2117,11 +2456,16 @@ module.exports = {
2117
2456
  tokenRestrictionsSchema,
2118
2457
  accountIdSchema,
2119
2458
  ipSchema,
2459
+ tokenIdSchema,
2120
2460
  accountCountersSchema,
2121
2461
  accountPathSchema,
2122
2462
  messageSpecialUseSchema,
2123
2463
  defaultAccountTypeSchema,
2124
2464
  fromAddressSchema,
2465
+ responseAddressSchema,
2466
+ responseFromAddressSchema,
2467
+ emailIdSchema,
2468
+ threadIdSchema,
2125
2469
  outboxEntrySchema,
2126
2470
  googleProjectIdSchema,
2127
2471
  googleTopicNameSchema,
@@ -2135,7 +2479,8 @@ module.exports = {
2135
2479
  exportListSchema,
2136
2480
  exportProgressSchema,
2137
2481
  exportIdSchema,
2138
- pubSubErrorSchema
2482
+ pubSubErrorSchema,
2483
+ errorResponses
2139
2484
  };
2140
2485
 
2141
2486
  /*