emailengine-app 2.61.1 → 2.61.2

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 (136) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/data/google-crawlers.json +1 -1
  3. package/lib/account/account-state.js +248 -0
  4. package/lib/account.js +17 -178
  5. package/lib/api-routes/account-routes.js +1006 -0
  6. package/lib/api-routes/message-routes.js +1377 -0
  7. package/lib/consts.js +12 -2
  8. package/lib/email-client/base-client.js +282 -771
  9. package/lib/email-client/gmail/gmail-api.js +243 -0
  10. package/lib/email-client/gmail-client.js +145 -53
  11. package/lib/email-client/imap/mailbox.js +24 -698
  12. package/lib/email-client/imap/sync-operations.js +812 -0
  13. package/lib/email-client/imap-client.js +1 -1
  14. package/lib/email-client/message-builder.js +566 -0
  15. package/lib/email-client/notification-handler.js +314 -0
  16. package/lib/email-client/outlook/graph-api.js +326 -0
  17. package/lib/email-client/outlook-client.js +159 -113
  18. package/lib/email-client/smtp-pool-manager.js +196 -0
  19. package/lib/imapproxy/imap-server.js +3 -12
  20. package/lib/oauth/gmail.js +4 -4
  21. package/lib/oauth/mail-ru.js +30 -5
  22. package/lib/oauth/outlook.js +57 -3
  23. package/lib/oauth/pubsub/google.js +30 -11
  24. package/lib/oauth/scope-checker.js +202 -0
  25. package/lib/oauth2-apps.js +8 -4
  26. package/lib/redis-operations.js +484 -0
  27. package/lib/routes-ui.js +283 -2582
  28. package/lib/tools.js +4 -196
  29. package/lib/ui-routes/account-routes.js +1931 -0
  30. package/lib/ui-routes/admin-config-routes.js +1233 -0
  31. package/lib/ui-routes/admin-entities-routes.js +2367 -0
  32. package/lib/ui-routes/oauth-routes.js +992 -0
  33. package/lib/utils/network.js +237 -0
  34. package/package.json +9 -9
  35. package/sbom.json +1 -1
  36. package/static/js/app.js +5 -5
  37. package/static/licenses.html +78 -18
  38. package/translations/de.mo +0 -0
  39. package/translations/de.po +85 -82
  40. package/translations/en.mo +0 -0
  41. package/translations/en.po +63 -71
  42. package/translations/et.mo +0 -0
  43. package/translations/et.po +84 -82
  44. package/translations/fr.mo +0 -0
  45. package/translations/fr.po +85 -82
  46. package/translations/ja.mo +0 -0
  47. package/translations/ja.po +84 -82
  48. package/translations/messages.pot +74 -87
  49. package/translations/nl.mo +0 -0
  50. package/translations/nl.po +86 -82
  51. package/translations/pl.mo +0 -0
  52. package/translations/pl.po +84 -82
  53. package/views/account/security.hbs +4 -4
  54. package/views/accounts/account.hbs +13 -13
  55. package/views/accounts/register/imap-server.hbs +12 -12
  56. package/views/config/document-store/pre-processing/index.hbs +4 -2
  57. package/views/config/oauth/app.hbs +6 -7
  58. package/views/config/oauth/index.hbs +2 -2
  59. package/views/config/service.hbs +3 -4
  60. package/views/dashboard.hbs +5 -7
  61. package/views/error.hbs +22 -7
  62. package/views/gateways/gateway.hbs +2 -2
  63. package/views/partials/add_account_modal.hbs +7 -10
  64. package/views/partials/document_store_header.hbs +1 -1
  65. package/views/partials/editor_scope_info.hbs +0 -1
  66. package/views/partials/oauth_config_header.hbs +1 -1
  67. package/views/partials/side_menu.hbs +3 -3
  68. package/views/partials/webhook_form.hbs +2 -2
  69. package/views/templates/index.hbs +1 -1
  70. package/views/templates/template.hbs +8 -8
  71. package/views/tokens/index.hbs +6 -6
  72. package/views/tokens/new.hbs +1 -1
  73. package/views/webhooks/index.hbs +4 -4
  74. package/views/webhooks/webhook.hbs +7 -7
  75. package/workers/api.js +148 -2436
  76. package/workers/smtp.js +2 -1
  77. package/lib/imapproxy/imap-core/test/client.js +0 -46
  78. package/lib/imapproxy/imap-core/test/fixtures/append.eml +0 -1196
  79. package/lib/imapproxy/imap-core/test/fixtures/chunks.js +0 -44
  80. package/lib/imapproxy/imap-core/test/fixtures/fix1.eml +0 -6
  81. package/lib/imapproxy/imap-core/test/fixtures/fix2.eml +0 -599
  82. package/lib/imapproxy/imap-core/test/fixtures/fix3.eml +0 -32
  83. package/lib/imapproxy/imap-core/test/fixtures/fix4.eml +0 -6
  84. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.eml +0 -599
  85. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.js +0 -2740
  86. package/lib/imapproxy/imap-core/test/fixtures/mimetorture.json +0 -1411
  87. package/lib/imapproxy/imap-core/test/fixtures/mimetree.js +0 -85
  88. package/lib/imapproxy/imap-core/test/fixtures/nodemailer.eml +0 -582
  89. package/lib/imapproxy/imap-core/test/fixtures/ryan_finnie_mime_torture.eml +0 -599
  90. package/lib/imapproxy/imap-core/test/fixtures/simple.eml +0 -42
  91. package/lib/imapproxy/imap-core/test/fixtures/simple.json +0 -164
  92. package/lib/imapproxy/imap-core/test/imap-compile-stream-test.js +0 -671
  93. package/lib/imapproxy/imap-core/test/imap-compiler-test.js +0 -272
  94. package/lib/imapproxy/imap-core/test/imap-indexer-test.js +0 -236
  95. package/lib/imapproxy/imap-core/test/imap-parser-test.js +0 -922
  96. package/lib/imapproxy/imap-core/test/memory-notifier.js +0 -129
  97. package/lib/imapproxy/imap-core/test/prepare.sh +0 -74
  98. package/lib/imapproxy/imap-core/test/protocol-test.js +0 -1756
  99. package/lib/imapproxy/imap-core/test/search-test.js +0 -1356
  100. package/lib/imapproxy/imap-core/test/test-client.js +0 -152
  101. package/lib/imapproxy/imap-core/test/test-server.js +0 -623
  102. package/lib/imapproxy/imap-core/test/tools-test.js +0 -22
  103. package/test/api-test.js +0 -899
  104. package/test/autoreply-test.js +0 -327
  105. package/test/bounce-test.js +0 -151
  106. package/test/complaint-test.js +0 -256
  107. package/test/fixtures/autoreply/LICENSE +0 -27
  108. package/test/fixtures/autoreply/rfc3834-01.eml +0 -23
  109. package/test/fixtures/autoreply/rfc3834-02.eml +0 -24
  110. package/test/fixtures/autoreply/rfc3834-03.eml +0 -26
  111. package/test/fixtures/autoreply/rfc3834-04.eml +0 -48
  112. package/test/fixtures/autoreply/rfc3834-05.eml +0 -19
  113. package/test/fixtures/autoreply/rfc3834-06.eml +0 -59
  114. package/test/fixtures/bounces/163.eml +0 -2521
  115. package/test/fixtures/bounces/fastmail.eml +0 -242
  116. package/test/fixtures/bounces/gmail.eml +0 -252
  117. package/test/fixtures/bounces/hotmail.eml +0 -655
  118. package/test/fixtures/bounces/mailru.eml +0 -121
  119. package/test/fixtures/bounces/outlook.eml +0 -1107
  120. package/test/fixtures/bounces/postfix.eml +0 -101
  121. package/test/fixtures/bounces/rambler.eml +0 -116
  122. package/test/fixtures/bounces/workmail.eml +0 -142
  123. package/test/fixtures/bounces/yahoo.eml +0 -139
  124. package/test/fixtures/bounces/zoho.eml +0 -83
  125. package/test/fixtures/bounces/zonemta.eml +0 -100
  126. package/test/fixtures/complaints/LICENSE +0 -27
  127. package/test/fixtures/complaints/amazonses.eml +0 -72
  128. package/test/fixtures/complaints/dmarc.eml +0 -59
  129. package/test/fixtures/complaints/hotmail.eml +0 -49
  130. package/test/fixtures/complaints/optout.eml +0 -40
  131. package/test/fixtures/complaints/standard-arf.eml +0 -68
  132. package/test/fixtures/complaints/yahoo.eml +0 -68
  133. package/test/oauth2-apps-test.js +0 -301
  134. package/test/sendonly-test.js +0 -160
  135. package/test/test-config.js +0 -34
  136. package/test/webhooks-server.js +0 -39
package/workers/api.js CHANGED
@@ -34,15 +34,14 @@ const {
34
34
  runPrechecks,
35
35
  matcher,
36
36
  readEnvValue,
37
- matchIp,
38
37
  getSignedFormData,
39
38
  threadStats,
40
- detectAutomatedRequest,
41
39
  hasEnvValue,
42
40
  getBoolean,
43
41
  loadTlsConfig,
44
42
  retryAgent
45
43
  } = require('../lib/tools');
44
+ const { matchIp, detectAutomatedRequest } = require('../lib/utils/network');
46
45
 
47
46
  const Bugsnag = require('@bugsnag/js');
48
47
  if (readEnvValue('BUGSNAG_API_KEY')) {
@@ -131,33 +130,26 @@ const { fetch: fetchCmd } = require('undici');
131
130
  const templateRoutes = require('../lib/api-routes/template-routes');
132
131
  const chatRoutes = require('../lib/api-routes/chat-routes');
133
132
  const bullBoardRoutes = require('../lib/api-routes/bull-board-routes');
133
+ const accountRoutes = require('../lib/api-routes/account-routes');
134
+ const messageRoutes = require('../lib/api-routes/message-routes');
134
135
 
135
136
  const {
136
137
  settingsSchema,
137
138
  addressSchema,
138
139
  settingsQuerySchema,
139
140
  imapSchema,
140
- imapUpdateSchema,
141
141
  smtpSchema,
142
- smtpUpdateSchema,
143
142
  oauth2Schema,
144
- oauth2UpdateSchema,
145
- messageDetailsSchema,
146
- messageListSchema,
147
143
  mailboxesSchema,
148
144
  shortMailboxesSchema,
149
145
  licenseSchema,
150
146
  lastErrorSchema,
151
147
  templateSchemas,
152
- documentStoreSchema,
153
- searchSchema,
154
- messageUpdateSchema,
155
148
  accountSchemas,
156
149
  oauthCreateSchema,
157
150
  tokenRestrictionsSchema,
158
151
  accountIdSchema,
159
152
  ipSchema,
160
- accountCountersSchema,
161
153
  accountPathSchema,
162
154
  defaultAccountTypeSchema,
163
155
  fromAddressSchema,
@@ -171,9 +163,6 @@ const {
171
163
  headerTimeoutSchema
172
164
  } = require('../lib/schemas');
173
165
 
174
- const listMessageFolderPathDescription =
175
- 'Mailbox folder path. Can use special use labels like "\\Sent". Special value "\\All" is available for Gmail IMAP, Gmail API, MS Graph API accounts.';
176
-
177
166
  const OAuth2ProviderSchema = Joi.string()
178
167
  .valid(...Object.keys(OAUTH_PROVIDERS))
179
168
  .required()
@@ -842,7 +831,7 @@ const init = async () => {
842
831
  'error',
843
832
  {
844
833
  pageTitle: 'Access Denied',
845
- message: 'Access Denied'
834
+ message: `You don't have permission to view this page`
846
835
  },
847
836
  {
848
837
  layout: 'public'
@@ -1803,18 +1792,16 @@ Include your token in requests using one of these methods:
1803
1792
  for (let entry of (request.payload && request.payload.value) || []) {
1804
1793
  // enumerate and queue all entries
1805
1794
  if (entry.subscriptionId !== outlookSubscription.id || entry.clientState !== outlookSubscription.clientState) {
1795
+ // Security: Log webhook validation failures - could indicate spoofed notifications
1806
1796
  request.logger.warn({
1807
- msg: 'Invalid subscription details',
1797
+ msg: 'Webhook validation failed - potential security issue',
1798
+ securityEvent: 'webhook_validation_failure',
1808
1799
  account: request.query.account,
1809
- expected: {
1810
- subscriptionId: outlookSubscription.id,
1811
- clientState: outlookSubscription.clientState
1812
- },
1813
- actual: {
1814
- subscriptionId: entry.subscriptionId,
1815
- clientState: entry.clientState
1816
- },
1817
- entry
1800
+ subscriptionIdMatch: entry.subscriptionId === outlookSubscription.id,
1801
+ clientStateMatch: entry.clientState === outlookSubscription.clientState,
1802
+ receivedSubscriptionId: entry.subscriptionId,
1803
+ changeType: entry.changeType,
1804
+ resource: entry.resource
1818
1805
  });
1819
1806
  continue;
1820
1807
  }
@@ -1905,18 +1892,15 @@ Include your token in requests using one of these methods:
1905
1892
 
1906
1893
  // enumerate and queue all entries
1907
1894
  if (entry.subscriptionId !== outlookSubscription.id || entry.clientState !== outlookSubscription.clientState) {
1908
- request.logger.error({
1909
- msg: 'Invalid subscription details',
1895
+ // Security: Log lifecycle webhook validation failures - could indicate spoofed notifications
1896
+ request.logger.warn({
1897
+ msg: 'Lifecycle webhook validation failed - potential security issue',
1898
+ securityEvent: 'lifecycle_webhook_validation_failure',
1910
1899
  account: request.query.account,
1911
- expected: {
1912
- subscriptionId: outlookSubscription.id,
1913
- clientState: outlookSubscription.clientState
1914
- },
1915
- actual: {
1916
- subscriptionId: entry.subscriptionId,
1917
- clientState: entry.clientState
1918
- },
1919
- entry
1900
+ subscriptionIdMatch: entry.subscriptionId === outlookSubscription.id,
1901
+ clientStateMatch: entry.clientState === outlookSubscription.clientState,
1902
+ receivedSubscriptionId: entry.subscriptionId,
1903
+ lifecycleEvent: entry.lifecycleEvent
1920
1904
  });
1921
1905
  continue;
1922
1906
  }
@@ -2042,6 +2026,13 @@ Include your token in requests using one of these methods:
2042
2026
  throw error;
2043
2027
  }
2044
2028
 
2029
+ // Validate nonce format: 16 bytes base64url encoded = 21-22 characters
2030
+ const stateNonce = request.query.state.slice('account:add:'.length);
2031
+ if (!/^[A-Za-z0-9_-]{21,22}$/.test(stateNonce)) {
2032
+ let error = Boom.boomify(new Error(`Oauth failed: invalid state format`), { statusCode: 400 });
2033
+ throw error;
2034
+ }
2035
+
2045
2036
  let [[, accountData]] = await redis.multi().get(`${REDIS_PREFIX}${request.query.state}`).del(`${REDIS_PREFIX}${request.query.state}`).exec();
2046
2037
  if (!accountData) {
2047
2038
  let error = Boom.boomify(new Error(`Oauth failed: session expired`), { statusCode: 400 });
@@ -2766,208 +2757,6 @@ Include your token in requests using one of these methods:
2766
2757
  }
2767
2758
  });
2768
2759
 
2769
- server.route({
2770
- method: 'POST',
2771
- path: '/v1/account',
2772
-
2773
- async handler(request) {
2774
- let accountObject = new Account({
2775
- redis,
2776
- call,
2777
- secret: await getSecret(),
2778
- timeout: request.headers['x-ee-timeout']
2779
- });
2780
-
2781
- try {
2782
- if (request.payload.oauth2 && request.payload.oauth2.authorize) {
2783
- // redirect to OAuth2 consent screen
2784
-
2785
- const oAuth2Client = await oauth2Apps.getClient(request.payload.oauth2.provider);
2786
- const nonce = crypto.randomBytes(NONCE_BYTES).toString('base64url');
2787
-
2788
- const accountData = request.payload;
2789
-
2790
- if (accountData.oauth2.redirectUrl) {
2791
- accountData._meta = {
2792
- redirectUrl: accountData.oauth2.redirectUrl
2793
- };
2794
- delete accountData.oauth2.redirectUrl;
2795
- }
2796
-
2797
- delete accountData.oauth2.authorize; // do not store this property
2798
- // store account data
2799
- await redis
2800
- .multi()
2801
- .set(`${REDIS_PREFIX}account:add:${nonce}`, JSON.stringify(accountData))
2802
- .expire(`${REDIS_PREFIX}account:add:${nonce}`, Math.floor(MAX_FORM_TTL / 1000))
2803
- .exec();
2804
-
2805
- // Generate the url that will be used for the consent dialog.
2806
- let authorizeUrl;
2807
- switch (oAuth2Client.provider) {
2808
- case 'gmail': {
2809
- let requestData = {
2810
- state: `account:add:${nonce}`
2811
- };
2812
-
2813
- if (accountData.email) {
2814
- requestData.email = accountData.email;
2815
- }
2816
-
2817
- authorizeUrl = oAuth2Client.generateAuthUrl(requestData);
2818
-
2819
- break;
2820
- }
2821
-
2822
- case 'outlook':
2823
- case 'mailRu':
2824
- authorizeUrl = oAuth2Client.generateAuthUrl({
2825
- state: `account:add:${nonce}`
2826
- });
2827
- break;
2828
-
2829
- default: {
2830
- let error = Boom.boomify(new Error('Unknown OAuth provider'), { statusCode: 400 });
2831
- throw error;
2832
- }
2833
- }
2834
-
2835
- return {
2836
- redirect: authorizeUrl
2837
- };
2838
- }
2839
-
2840
- let result = await accountObject.create(request.payload);
2841
- return result;
2842
- } catch (err) {
2843
- request.logger.error({ msg: 'API request failed', err });
2844
- if (Boom.isBoom(err)) {
2845
- throw err;
2846
- }
2847
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
2848
- if (err.code) {
2849
- error.output.payload.code = err.code;
2850
- }
2851
- throw error;
2852
- }
2853
- },
2854
-
2855
- options: {
2856
- description: 'Register new account',
2857
- notes: 'Registers new IMAP account to be synced',
2858
- tags: ['api', 'Account'],
2859
-
2860
- plugins: {},
2861
-
2862
- auth: {
2863
- strategy: 'api-token',
2864
- mode: 'required'
2865
- },
2866
- cors: CORS_CONFIG,
2867
-
2868
- validate: {
2869
- options: {
2870
- stripUnknown: false,
2871
- abortEarly: false,
2872
- convert: true
2873
- },
2874
- failAction,
2875
-
2876
- payload: Joi.object({
2877
- account: Joi.string()
2878
- .empty('')
2879
- .trim()
2880
- .max(256)
2881
- .allow(null)
2882
- .example('example')
2883
- .description(
2884
- 'Account ID. If set to `null`, a unique ID will be generated automatically. If you provide an existing account ID, the settings for that account will be updated instead'
2885
- )
2886
- .required(),
2887
-
2888
- name: Joi.string().max(256).required().example('My Email Account').description('Display name for the account'),
2889
- email: Joi.string().empty('').email().example('user@example.com').description('Default email address of the account'),
2890
-
2891
- path: accountPathSchema.example(['*']).label('AccountPath'),
2892
-
2893
- subconnections: accountSchemas.subconnections,
2894
-
2895
- webhooks: Joi.string()
2896
- .uri({
2897
- scheme: ['http', 'https'],
2898
- allowRelative: false
2899
- })
2900
- .allow('')
2901
- .example('https://myservice.com/imap/webhooks')
2902
- .description('Account-specific webhook URL'),
2903
-
2904
- copy: Joi.boolean()
2905
- .allow(null)
2906
- .example(null)
2907
- .description('Copy submitted messages to Sent folder. Set to `null` to unset and use provider specific default.'),
2908
-
2909
- logs: Joi.boolean().example(false).description('Store recent logs').default(false),
2910
-
2911
- notifyFrom: accountSchemas.notifyFrom.default('now'),
2912
- syncFrom: accountSchemas.syncFrom.default(null),
2913
-
2914
- proxy: settingsSchema.proxyUrl,
2915
- smtpEhloName: settingsSchema.smtpEhloName,
2916
-
2917
- imapIndexer: accountSchemas.imapIndexer,
2918
-
2919
- imap: Joi.object(imapSchema).allow(false).description('IMAP configuration').label('ImapConfiguration'),
2920
-
2921
- smtp: Joi.object(smtpSchema).allow(false).description('SMTP configuration').label('SmtpConfiguration'),
2922
-
2923
- oauth2: Joi.object(oauth2Schema).allow(false).description('OAuth2 configuration').label('OAuth2'),
2924
-
2925
- webhooksCustomHeaders: settingsSchema.webhooksCustomHeaders.label('AccountWebhooksCustomHeaders'),
2926
-
2927
- locale: Joi.string().empty('').max(100).example('fr').description('Optional locale'),
2928
- tz: Joi.string().empty('').max(100).example('Europe/Tallinn').description('Optional timezone')
2929
- })
2930
- .label('CreateAccount')
2931
- .example({
2932
- account: 'example',
2933
- name: 'Nyan Cat',
2934
- email: 'nyan.cat@example.com',
2935
- imap: {
2936
- auth: {
2937
- user: 'nyan.cat',
2938
- pass: 'sercretpass'
2939
- },
2940
- host: 'mail.example.com',
2941
- port: 993,
2942
- secure: true
2943
- },
2944
- smtp: {
2945
- auth: {
2946
- user: 'nyan.cat',
2947
- pass: 'secretpass'
2948
- },
2949
- host: 'mail.example.com',
2950
- port: 465,
2951
- secure: true
2952
- }
2953
- })
2954
- },
2955
-
2956
- response: {
2957
- schema: Joi.object({
2958
- account: accountIdSchema.required(),
2959
- state: Joi.string()
2960
- .required()
2961
- .valid('existing', 'new')
2962
- .example('new')
2963
- .description('Is the account new or updated existing')
2964
- .label('CreateAccountState')
2965
- }).label('CreateAccountResponse'),
2966
- failAction: 'log'
2967
- }
2968
- }
2969
- });
2970
-
2971
2760
  server.route({
2972
2761
  method: 'POST',
2973
2762
  path: '/v1/authentication/form',
@@ -3119,8 +2908,8 @@ Include your token in requests using one of these methods:
3119
2908
  });
3120
2909
 
3121
2910
  server.route({
3122
- method: 'PUT',
3123
- path: '/v1/account/{account}',
2911
+ method: 'GET',
2912
+ path: '/v1/account/{account}/mailboxes',
3124
2913
 
3125
2914
  async handler(request) {
3126
2915
  let accountObject = new Account({
@@ -3132,7 +2921,25 @@ Include your token in requests using one of these methods:
3132
2921
  });
3133
2922
 
3134
2923
  try {
3135
- return await accountObject.update(request.payload);
2924
+ let mailboxes = await accountObject.getMailboxListing(request.query);
2925
+
2926
+ if (mailboxes && Array.isArray(mailboxes)) {
2927
+ mailboxes = mailboxes.sort((a, b) => {
2928
+ if (a.specialUse && !b.specialUse) {
2929
+ return -1;
2930
+ }
2931
+ if (!a.specialUse && b.specialUse) {
2932
+ return 1;
2933
+ }
2934
+ if (a.specialUse && b.specialUse) {
2935
+ return FLAG_SORT_ORDER.indexOf(a.specialUse) - FLAG_SORT_ORDER.indexOf(b.specialUse);
2936
+ }
2937
+
2938
+ return a.path.localeCompare(b.path);
2939
+ });
2940
+ }
2941
+
2942
+ return { mailboxes };
3136
2943
  } catch (err) {
3137
2944
  request.logger.error({ msg: 'API request failed', err });
3138
2945
  if (Boom.isBoom(err)) {
@@ -3145,12 +2952,11 @@ Include your token in requests using one of these methods:
3145
2952
  throw error;
3146
2953
  }
3147
2954
  },
3148
- options: {
3149
- description: 'Update account info',
3150
- notes: 'Updates account information',
3151
- tags: ['api', 'Account'],
3152
2955
 
3153
- plugins: {},
2956
+ options: {
2957
+ description: 'List mailboxes',
2958
+ notes: 'Lists all available mailboxes',
2959
+ tags: ['api', 'Mailbox'],
3154
2960
 
3155
2961
  auth: {
3156
2962
  strategy: 'api-token',
@@ -3170,72 +2976,28 @@ Include your token in requests using one of these methods:
3170
2976
  account: accountIdSchema.required()
3171
2977
  }),
3172
2978
 
3173
- payload: Joi.object({
3174
- name: Joi.string().max(256).example('My Email Account').description('Display name for the account'),
3175
- email: Joi.string().empty('').email().example('user@example.com').description('Default email address of the account'),
3176
-
3177
- path: accountPathSchema.example(['*']).label('AccountPath'),
3178
-
3179
- subconnections: accountSchemas.subconnections,
3180
-
3181
- webhooks: Joi.string()
3182
- .uri({
3183
- scheme: ['http', 'https'],
3184
- allowRelative: false
3185
- })
3186
- .allow('')
3187
- .example('https://myservice.com/imap/webhooks')
3188
- .description('Account-specific webhook URL'),
3189
-
3190
- copy: Joi.boolean()
3191
- .allow(null)
3192
- .example(null)
3193
- .description('Copy submitted messages to Sent folder. Set to `null` to unset and use provider specific default.'),
3194
-
3195
- logs: Joi.boolean().example(false).description('Store recent logs'),
3196
-
3197
- notifyFrom: accountSchemas.notifyFrom,
3198
- syncFrom: accountSchemas.syncFrom,
3199
-
3200
- proxy: settingsSchema.proxyUrl,
3201
- smtpEhloName: settingsSchema.smtpEhloName,
3202
-
3203
- imap: Joi.object(imapUpdateSchema).allow(false).description('IMAP configuration').label('IMAPUpdate'),
3204
- smtp: Joi.object(smtpUpdateSchema).allow(false).description('SMTP configuration').label('SMTPUpdate'),
3205
- oauth2: Joi.object(oauth2UpdateSchema).allow(false).description('OAuth2 configuration').label('OAuth2Update'),
3206
-
3207
- webhooksCustomHeaders: settingsSchema.webhooksCustomHeaders.label('AccountWebhooksCustomHeaders'),
3208
-
3209
- locale: Joi.string().empty('').max(100).example('fr').description('Optional locale'),
3210
- tz: Joi.string().empty('').max(100).example('Europe/Tallinn').description('Optional timezone')
3211
- })
3212
- .label('UpdateAccount')
3213
- .example({
3214
- name: 'Nyan Cat',
3215
- email: 'nyan.cat@example.com',
3216
- imap: {
3217
- partial: true,
3218
- disabled: true
3219
- },
3220
- smtp: {
3221
- partial: true,
3222
- host: 'mail.example.com'
3223
- }
3224
- })
2979
+ query: Joi.object({
2980
+ counters: Joi.boolean()
2981
+ .truthy('Y', 'true', '1')
2982
+ .falsy('N', 'false', 0)
2983
+ .default(false)
2984
+ .description('If true, then includes message counters in the response')
2985
+ .label('MailboxCounters')
2986
+ }).label('MailboxListQuery')
3225
2987
  },
3226
2988
 
3227
2989
  response: {
3228
2990
  schema: Joi.object({
3229
- account: accountIdSchema.required()
3230
- }),
2991
+ mailboxes: mailboxesSchema
2992
+ }).label('MailboxesFilterResponse'),
3231
2993
  failAction: 'log'
3232
2994
  }
3233
2995
  }
3234
2996
  });
3235
2997
 
3236
2998
  server.route({
3237
- method: 'PUT',
3238
- path: '/v1/account/{account}/reconnect',
2999
+ method: 'POST',
3000
+ path: '/v1/account/{account}/mailbox',
3239
3001
 
3240
3002
  async handler(request) {
3241
3003
  let accountObject = new Account({
@@ -3247,7 +3009,7 @@ Include your token in requests using one of these methods:
3247
3009
  });
3248
3010
 
3249
3011
  try {
3250
- return { reconnect: await accountObject.requestReconnect(request.payload) };
3012
+ return await accountObject.createMailbox(request.payload.path);
3251
3013
  } catch (err) {
3252
3014
  request.logger.error({ msg: 'API request failed', err });
3253
3015
  if (Boom.isBoom(err)) {
@@ -3257,13 +3019,17 @@ Include your token in requests using one of these methods:
3257
3019
  if (err.code) {
3258
3020
  error.output.payload.code = err.code;
3259
3021
  }
3022
+ if (err.info) {
3023
+ error.output.payload.details = err.info;
3024
+ }
3260
3025
  throw error;
3261
3026
  }
3262
3027
  },
3028
+
3263
3029
  options: {
3264
- description: 'Request reconnect',
3265
- notes: 'Requests connection to be reconnected',
3266
- tags: ['api', 'Account'],
3030
+ description: 'Create mailbox',
3031
+ notes: 'Create new mailbox folder',
3032
+ tags: ['api', 'Mailbox'],
3267
3033
 
3268
3034
  plugins: {},
3269
3035
 
@@ -3286,14 +3052,21 @@ Include your token in requests using one of these methods:
3286
3052
  }),
3287
3053
 
3288
3054
  payload: Joi.object({
3289
- reconnect: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Only reconnect if true')
3290
- }).label('RequestReconnect')
3055
+ path: Joi.array()
3056
+ .items(Joi.string().max(256))
3057
+ .single()
3058
+ .example(['Parent folder', 'Subfolder'])
3059
+ .description('Mailbox path as an array or a string. If account is namespaced then namespace prefix is added by default.')
3060
+ .label('MailboxPath')
3061
+ }).label('CreateMailbox')
3291
3062
  },
3292
3063
 
3293
3064
  response: {
3294
3065
  schema: Joi.object({
3295
- reconnect: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Reconnection status')
3296
- }).label('RequestReconnectResponse'),
3066
+ path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to mailbox').label('MailboxPath'),
3067
+ mailboxId: Joi.string().example('1439876283476').description('Mailbox ID (if server has support)').label('MailboxId'),
3068
+ created: Joi.boolean().example(true).description('Was the mailbox created')
3069
+ }).label('CreateMailboxResponse'),
3297
3070
  failAction: 'log'
3298
3071
  }
3299
3072
  }
@@ -3301,7 +3074,7 @@ Include your token in requests using one of these methods:
3301
3074
 
3302
3075
  server.route({
3303
3076
  method: 'PUT',
3304
- path: '/v1/account/{account}/sync',
3077
+ path: '/v1/account/{account}/mailbox',
3305
3078
 
3306
3079
  async handler(request) {
3307
3080
  let accountObject = new Account({
@@ -3313,7 +3086,7 @@ Include your token in requests using one of these methods:
3313
3086
  });
3314
3087
 
3315
3088
  try {
3316
- return { sync: await accountObject.requestSync(request.payload) };
3089
+ return await accountObject.modifyMailbox(request.payload.path, request.payload.newPath, request.payload.subscribed);
3317
3090
  } catch (err) {
3318
3091
  request.logger.error({ msg: 'API request failed', err });
3319
3092
  if (Boom.isBoom(err)) {
@@ -3323,13 +3096,17 @@ Include your token in requests using one of these methods:
3323
3096
  if (err.code) {
3324
3097
  error.output.payload.code = err.code;
3325
3098
  }
3099
+ if (err.info) {
3100
+ error.output.payload.details = err.info;
3101
+ }
3326
3102
  throw error;
3327
3103
  }
3328
3104
  },
3105
+
3329
3106
  options: {
3330
- description: 'Request syncing',
3331
- notes: 'Immediately trigger account syncing for IMAP accounts',
3332
- tags: ['api', 'Account'],
3107
+ description: 'Modify mailbox',
3108
+ notes: 'Modify an existing mailbox folder (rename or change subscription status)',
3109
+ tags: ['api', 'Mailbox'],
3333
3110
 
3334
3111
  plugins: {},
3335
3112
 
@@ -3352,1846 +3129,49 @@ Include your token in requests using one of these methods:
3352
3129
  }),
3353
3130
 
3354
3131
  payload: Joi.object({
3355
- sync: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Only sync if true')
3356
- }).label('RequestSync')
3357
- },
3358
-
3359
- response: {
3360
- schema: Joi.object({
3361
- sync: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Sync status')
3362
- }).label('RequestSyncResponse'),
3363
- failAction: 'log'
3364
- }
3365
- }
3366
- });
3367
-
3368
- server.route({
3369
- method: 'DELETE',
3370
- path: '/v1/account/{account}',
3371
-
3372
- async handler(request) {
3373
- let accountObject = new Account({
3374
- redis,
3375
- account: request.params.account,
3376
- documentsQueue,
3377
- call,
3378
- secret: await getSecret(),
3379
- timeout: request.headers['x-ee-timeout']
3380
- });
3381
-
3382
- try {
3383
- return await accountObject.delete();
3384
- } catch (err) {
3385
- request.logger.error({ msg: 'API request failed', err });
3386
- if (Boom.isBoom(err)) {
3387
- throw err;
3388
- }
3389
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
3390
- if (err.code) {
3391
- error.output.payload.code = err.code;
3392
- }
3393
- throw error;
3394
- }
3395
- },
3396
- options: {
3397
- description: 'Remove account',
3398
- notes: "Stop processing and clear the account's cache",
3399
-
3400
- tags: ['api', 'Account'],
3401
-
3402
- plugins: {},
3403
-
3404
- auth: {
3405
- strategy: 'api-token',
3406
- mode: 'required'
3407
- },
3408
- cors: CORS_CONFIG,
3409
-
3410
- validate: {
3411
- options: {
3412
- stripUnknown: false,
3413
- abortEarly: false,
3414
- convert: true
3415
- },
3416
- failAction,
3417
-
3418
- params: Joi.object({
3419
- account: accountIdSchema.required()
3420
- })
3421
- },
3422
-
3423
- response: {
3424
- schema: Joi.object({
3425
- account: accountIdSchema.required(),
3426
- deleted: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(true).description('Was the account deleted')
3427
- }).label('DeleteRequestResponse'),
3428
- failAction: 'log'
3429
- }
3430
- }
3431
- });
3432
-
3433
- server.route({
3434
- method: 'PUT',
3435
- path: '/v1/account/{account}/flush',
3436
-
3437
- async handler(request, h) {
3438
- let accountObject = new Account({
3439
- redis,
3440
- account: request.params.account,
3441
- call,
3442
- secret: await getSecret(),
3443
- esClient: await h.getESClient(request.logger),
3444
- timeout: request.headers['x-ee-timeout']
3445
- });
3446
-
3447
- try {
3448
- return { flush: await accountObject.flush(request.payload) };
3449
- } catch (err) {
3450
- request.logger.error({ msg: 'API request failed', err });
3451
- if (Boom.isBoom(err)) {
3452
- throw err;
3453
- }
3454
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
3455
- if (err.code) {
3456
- error.output.payload.code = err.code;
3457
- }
3458
- throw error;
3459
- }
3460
- },
3461
- options: {
3462
- description: 'Request account flush',
3463
- notes: 'Deletes all email indexes from Redis and ElasticSearch and re-creates the index for that account. You can only run a single flush operation at a time, so you must wait until the previous flush has finished before initiating a new one.',
3464
- tags: ['api', 'Account'],
3465
-
3466
- plugins: {},
3467
-
3468
- auth: {
3469
- strategy: 'api-token',
3470
- mode: 'required'
3471
- },
3472
- cors: CORS_CONFIG,
3473
-
3474
- validate: {
3475
- options: {
3476
- stripUnknown: false,
3477
- abortEarly: false,
3478
- convert: true
3479
- },
3480
- failAction,
3481
-
3482
- params: Joi.object({
3483
- account: accountIdSchema.required()
3484
- }),
3485
-
3486
- payload: Joi.object({
3487
- flush: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Only flush the account if true'),
3488
- notifyFrom: accountSchemas.notifyFrom.default('now'),
3489
- imapIndexer: accountSchemas.imapIndexer,
3490
- syncFrom: accountSchemas.syncFrom
3491
- }).label('RequestFlush')
3492
- },
3493
-
3494
- response: {
3495
- schema: Joi.object({
3496
- flush: Joi.boolean().truthy('Y', 'true', '1').falsy('N', 'false', 0).default(false).description('Flush status')
3497
- }).label('RequestFlushResponse'),
3498
- failAction: 'log'
3499
- }
3500
- }
3501
- });
3502
-
3503
- server.route({
3504
- method: 'GET',
3505
- path: '/v1/accounts',
3506
-
3507
- async handler(request) {
3508
- try {
3509
- let accountObject = new Account({
3510
- redis,
3511
- account: request.params.account,
3512
- call,
3513
- secret: await getSecret(),
3514
- timeout: request.headers['x-ee-timeout']
3515
- });
3516
-
3517
- return await accountObject.listAccounts(request.query.state, request.query.query, request.query.page, request.query.pageSize);
3518
- } catch (err) {
3519
- request.logger.error({ msg: 'API request failed', err });
3520
- if (Boom.isBoom(err)) {
3521
- throw err;
3522
- }
3523
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
3524
- if (err.code) {
3525
- error.output.payload.code = err.code;
3526
- }
3527
- throw error;
3528
- }
3529
- },
3530
-
3531
- options: {
3532
- description: 'List accounts',
3533
- notes: 'Lists registered accounts',
3534
- tags: ['api', 'Account'],
3535
-
3536
- plugins: {},
3537
-
3538
- auth: {
3539
- strategy: 'api-token',
3540
- mode: 'required'
3541
- },
3542
- cors: CORS_CONFIG,
3543
-
3544
- validate: {
3545
- options: {
3546
- stripUnknown: false,
3547
- abortEarly: false,
3548
- convert: true
3549
- },
3550
- failAction,
3551
-
3552
- query: Joi.object({
3553
- page: Joi.number()
3554
- .integer()
3555
- .min(0)
3556
- .max(1024 * 1024)
3557
- .default(0)
3558
- .example(0)
3559
- .description('Page number (zero indexed, so use 0 for first page)')
3560
- .label('PageNumber'),
3561
- pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page').label('PageSize'),
3562
- state: Joi.string()
3563
- .valid('init', 'syncing', 'connecting', 'connected', 'authenticationError', 'connectError', 'unset', 'disconnected')
3564
- .example('connected')
3565
- .description('Filter accounts by state')
3566
- .label('AccountState'),
3567
- query: Joi.string().example('user@example.com').description('Filter accounts by string match').label('AccountQuery')
3568
- }).label('AccountsFilter')
3569
- },
3570
-
3571
- response: {
3572
- schema: Joi.object({
3573
- total: Joi.number().integer().example(120).description('How many matching entries').label('TotalNumber'),
3574
- page: Joi.number().integer().example(0).description('Current page (0-based index)').label('PageNumber'),
3575
- pages: Joi.number().integer().example(24).description('Total page count').label('PagesNumber'),
3576
-
3577
- accounts: Joi.array()
3578
- .items(
3579
- Joi.object({
3580
- account: accountIdSchema.required(),
3581
- name: Joi.string().max(256).example('My Email Account').description('Display name for the account'),
3582
- email: Joi.string().empty('').email().example('user@example.com').description('Default email address of the account'),
3583
- type: AccountTypeSchema,
3584
- app: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
3585
- state: Joi.string()
3586
- .required()
3587
- .valid('init', 'syncing', 'connecting', 'connected', 'authenticationError', 'connectError', 'unset', 'disconnected')
3588
- .example('connected')
3589
- .description('Account state'),
3590
- webhooks: Joi.string()
3591
- .uri({
3592
- scheme: ['http', 'https'],
3593
- allowRelative: false
3594
- })
3595
- .example('https://myservice.com/imap/webhooks')
3596
- .description('Account-specific webhook URL'),
3597
- proxy: settingsSchema.proxyUrl,
3598
- smtpEhloName: settingsSchema.smtpEhloName,
3599
-
3600
- counters: accountCountersSchema,
3601
-
3602
- syncTime: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Last sync time'),
3603
- lastError: lastErrorSchema.allow(null)
3604
- }).label('AccountResponseItem')
3605
- )
3606
- .label('AccountEntries')
3607
- }).label('AccountsFilterResponse'),
3608
- failAction: 'log'
3609
- }
3610
- }
3611
- });
3612
-
3613
- server.route({
3614
- method: 'GET',
3615
- path: '/v1/account/{account}',
3616
-
3617
- async handler(request) {
3618
- let accountObject = new Account({
3619
- redis,
3620
- account: request.params.account,
3621
- call,
3622
- secret: await getSecret(),
3623
- timeout: request.headers['x-ee-timeout']
3624
- });
3625
- try {
3626
- let accountData = await accountObject.loadAccountData();
3627
-
3628
- // remove secrets
3629
- for (let type of ['imap', 'smtp', 'oauth2']) {
3630
- if (accountData[type] && accountData[type].auth) {
3631
- for (let key of ['pass', 'accessToken', 'refreshToken']) {
3632
- if (key in accountData[type].auth) {
3633
- accountData[type].auth[key] = '******';
3634
- }
3635
- }
3636
- }
3637
-
3638
- if (accountData[type]) {
3639
- for (let key of ['accessToken', 'refreshToken']) {
3640
- if (key in accountData[type]) {
3641
- accountData[type][key] = '******';
3642
- }
3643
- }
3644
- }
3645
- }
3646
-
3647
- let result = {};
3648
-
3649
- for (let key of [
3650
- 'account',
3651
- 'name',
3652
- 'email',
3653
- 'copy',
3654
- 'logs',
3655
- 'notifyFrom',
3656
- 'syncFrom',
3657
- 'path',
3658
- 'subconnections',
3659
- 'webhooks',
3660
- 'proxy',
3661
- 'smtpEhloName',
3662
- 'imapIndexer',
3663
- 'imap',
3664
- 'smtp',
3665
- 'oauth2',
3666
- 'state',
3667
- 'smtpStatus',
3668
- 'syncError',
3669
- 'connections',
3670
- 'webhooksCustomHeaders',
3671
- 'locale',
3672
- 'tz'
3673
- ]) {
3674
- if (key in accountData) {
3675
- result[key] = accountData[key];
3676
- }
3677
- }
3678
-
3679
- // default false
3680
- for (let key of ['logs']) {
3681
- result[key] = !!result[key];
3682
- }
3683
-
3684
- // default null
3685
- for (let key of ['notifyFrom', 'syncFrom', 'lastError', 'smtpStatus']) {
3686
- result[key] = result[key] || null;
3687
- }
3688
-
3689
- let oauth2App;
3690
- if (accountData.oauth2 && accountData.oauth2.provider) {
3691
- oauth2App = await oauth2Apps.get(accountData.oauth2.provider);
3692
-
3693
- if (oauth2App) {
3694
- // Check if account is already marked as send-only
3695
- if (accountData.sendOnly) {
3696
- result.sendOnly = true;
3697
- } else {
3698
- result.type = oauth2App.provider;
3699
- }
3700
- if (oauth2App.id !== oauth2App.provider) {
3701
- result.app = oauth2App.id;
3702
- }
3703
- result.baseScopes = oauth2App.baseScope || 'imap';
3704
- } else {
3705
- result.type = 'oauth2';
3706
- }
3707
- } else if (accountData.oauth2 && accountData.oauth2.auth && accountData.oauth2.auth.delegatedAccount) {
3708
- result.type = 'delegated';
3709
- } else if (accountData.imap && !accountData.imap.disabled) {
3710
- result.type = 'imap';
3711
- } else {
3712
- result.type = 'sending';
3713
- result.sendOnly = true;
3714
- }
3715
-
3716
- if ((accountData.imap || (oauth2App && (!oauth2App.baseScopes || oauth2App.baseScopes === 'imap'))) && !result.imapIndexer) {
3717
- result.imapIndexer = 'full';
3718
- }
3719
-
3720
- if (accountData.sync) {
3721
- result.syncTime = accountData.sync;
3722
- }
3723
-
3724
- if (accountData.state) {
3725
- result.lastError = accountData.state === 'connected' ? null : accountData.lastErrorState;
3726
- }
3727
-
3728
- if (accountData.counters) {
3729
- result.counters = accountData.counters;
3730
- }
3731
-
3732
- if (request.query.quota && !result.sendOnly) {
3733
- result.quota = await accountObject.getQuota();
3734
- }
3735
-
3736
- return result;
3737
- } catch (err) {
3738
- request.logger.error({ msg: 'API request failed', err });
3739
- if (Boom.isBoom(err)) {
3740
- throw err;
3741
- }
3742
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
3743
- if (err.code) {
3744
- error.output.payload.code = err.code;
3745
- }
3746
- throw error;
3747
- }
3748
- },
3749
- options: {
3750
- description: 'Get account info',
3751
- notes: 'Returns stored information about the account. Passwords are not included.',
3752
- tags: ['api', 'Account'],
3753
-
3754
- auth: {
3755
- strategy: 'api-token',
3756
- mode: 'required'
3757
- },
3758
- cors: CORS_CONFIG,
3759
-
3760
- validate: {
3761
- options: {
3762
- stripUnknown: false,
3763
- abortEarly: false,
3764
- convert: true
3765
- },
3766
- failAction,
3767
-
3768
- params: Joi.object({
3769
- account: accountIdSchema.required()
3770
- }),
3771
-
3772
- query: Joi.object({
3773
- quota: Joi.boolean()
3774
- .truthy('Y', 'true', '1')
3775
- .falsy('N', 'false', 0)
3776
- .default(false)
3777
- .description('If true, then include quota information in the response')
3778
- .label('AccountQuota')
3779
- })
3780
- },
3781
-
3782
- response: {
3783
- schema: Joi.object({
3784
- account: accountIdSchema.required(),
3785
-
3786
- name: Joi.string().max(256).example('My Email Account').description('Display name for the account'),
3787
- email: Joi.string().empty('').email().example('user@example.com').description('Default email address of the account'),
3788
-
3789
- copy: Joi.boolean().example(true).description('Copy submitted messages to Sent folder'),
3790
- logs: Joi.boolean().example(false).description('Store recent logs'),
3791
-
3792
- notifyFrom: accountSchemas.notifyFrom,
3793
- syncFrom: accountSchemas.syncFrom,
3794
-
3795
- path: accountPathSchema.example(['*']).label('AccountPath'),
3796
-
3797
- imapIndexer: accountSchemas.imapIndexer,
3798
-
3799
- subconnections: accountSchemas.subconnections,
3800
-
3801
- webhooks: Joi.string()
3802
- .uri({
3803
- scheme: ['http', 'https'],
3804
- allowRelative: false
3805
- })
3806
- .example('https://myservice.com/imap/webhooks')
3807
- .description('Account-specific webhook URL'),
3808
- proxy: settingsSchema.proxyUrl,
3809
- smtpEhloName: settingsSchema.smtpEhloName,
3810
-
3811
- imap: Joi.object(imapSchema).description('IMAP configuration').label('IMAPResponse'),
3812
-
3813
- smtp: Joi.object(smtpSchema).description('SMTP configuration').label('SMTPResponse'),
3814
-
3815
- oauth2: Joi.object(oauth2Schema).description('OAuth2 configuration').label('Oauth2Response'),
3816
-
3817
- state: Joi.string()
3818
- .valid('init', 'syncing', 'connecting', 'connected', 'authenticationError', 'connectError', 'unset', 'disconnected')
3819
- .example('connected')
3820
- .description('Informational account state')
3821
- .label('AccountInfoState'),
3822
-
3823
- smtpStatus: Joi.object({
3824
- created: Joi.date()
3825
- .iso()
3826
- .allow(null)
3827
- .example('2021-07-08T07:06:34.336Z')
3828
- .description('When was the status for SMTP connection last updated'),
3829
- status: Joi.string().valid('ok', 'error').description('Was the last SMTP attempt successful or not').label('SMTPStatusStatus'),
3830
- response: Joi.string().example('250 OK').description('SMTP response message for delivery attempt'),
3831
- description: Joi.string().example('Authentication failed').description('Error information'),
3832
- responseCode: Joi.number().integer().example(500).description('Error status code'),
3833
- code: Joi.string().example('EAUTH').description('Error type identifier'),
3834
- command: Joi.string().example('AUTH PLAIN').description('SMTP command that failed')
3835
- })
3836
- .description('Information about the last SMTP connection attempt')
3837
- .label('SMTPInfoStatus'),
3838
-
3839
- webhooksCustomHeaders: settingsSchema.webhooksCustomHeaders.label('AccountWebhooksCustomHeaders'),
3840
-
3841
- locale: Joi.string().empty('').max(100).example('fr').description('Optional locale'),
3842
- tz: Joi.string().empty('').max(100).example('Europe/Tallinn').description('Optional timezone'),
3843
-
3844
- type: AccountTypeSchema,
3845
- app: Joi.string().max(256).example('AAABhaBPHscAAAAH').description('OAuth2 application ID'),
3846
- baseScopes: Joi.string()
3847
- .empty('')
3848
- .trim()
3849
- .valid(...['imap', 'api', 'pubsub'])
3850
- .example('imap')
3851
- .description('OAuth2 Base Scopes'),
3852
-
3853
- counters: accountCountersSchema,
3854
-
3855
- quota: Joi.object({
3856
- usage: Joi.number().integer().example(8547884032).description('How many bytes has the account stored in emails'),
3857
- limit: Joi.number().integer().example(16106127360).description('How many bytes can the account store emails'),
3858
- status: Joi.string().example('53%').description('Textual information about the usage')
3859
- })
3860
- .label('AccountQuota')
3861
- .allow(false)
3862
- .description(
3863
- 'Account quota information if query argument quota=true. This value will be false if the server does not provide quota information.'
3864
- ),
3865
-
3866
- syncTime: Joi.date().iso().example('2021-02-17T13:43:18.860Z').description('Last sync time'),
3867
-
3868
- lastError: lastErrorSchema.allow(null)
3869
- }).label('AccountResponse'),
3870
- failAction: 'log'
3871
- }
3872
- }
3873
- });
3874
-
3875
- server.route({
3876
- method: 'GET',
3877
- path: '/v1/account/{account}/mailboxes',
3878
-
3879
- async handler(request) {
3880
- let accountObject = new Account({
3881
- redis,
3882
- account: request.params.account,
3883
- call,
3884
- secret: await getSecret(),
3885
- timeout: request.headers['x-ee-timeout']
3886
- });
3887
-
3888
- try {
3889
- let mailboxes = await accountObject.getMailboxListing(request.query);
3890
-
3891
- if (mailboxes && Array.isArray(mailboxes)) {
3892
- mailboxes = mailboxes.sort((a, b) => {
3893
- if (a.specialUse && !b.specialUse) {
3894
- return -1;
3895
- }
3896
- if (!a.specialUse && b.specialUse) {
3897
- return 1;
3898
- }
3899
- if (a.specialUse && b.specialUse) {
3900
- return FLAG_SORT_ORDER.indexOf(a.specialUse) - FLAG_SORT_ORDER.indexOf(b.specialUse);
3901
- }
3902
-
3903
- return a.path.localeCompare(b.path);
3904
- });
3905
- }
3906
-
3907
- return { mailboxes };
3908
- } catch (err) {
3909
- request.logger.error({ msg: 'API request failed', err });
3910
- if (Boom.isBoom(err)) {
3911
- throw err;
3912
- }
3913
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
3914
- if (err.code) {
3915
- error.output.payload.code = err.code;
3916
- }
3917
- throw error;
3918
- }
3919
- },
3920
-
3921
- options: {
3922
- description: 'List mailboxes',
3923
- notes: 'Lists all available mailboxes',
3924
- tags: ['api', 'Mailbox'],
3925
-
3926
- auth: {
3927
- strategy: 'api-token',
3928
- mode: 'required'
3929
- },
3930
- cors: CORS_CONFIG,
3931
-
3932
- validate: {
3933
- options: {
3934
- stripUnknown: false,
3935
- abortEarly: false,
3936
- convert: true
3937
- },
3938
- failAction,
3939
-
3940
- params: Joi.object({
3941
- account: accountIdSchema.required()
3942
- }),
3943
-
3944
- query: Joi.object({
3945
- counters: Joi.boolean()
3946
- .truthy('Y', 'true', '1')
3947
- .falsy('N', 'false', 0)
3948
- .default(false)
3949
- .description('If true, then includes message counters in the response')
3950
- .label('MailboxCounters')
3951
- }).label('MailboxListQuery')
3952
- },
3953
-
3954
- response: {
3955
- schema: Joi.object({
3956
- mailboxes: mailboxesSchema
3957
- }).label('MailboxesFilterResponse'),
3958
- failAction: 'log'
3959
- }
3960
- }
3961
- });
3962
-
3963
- server.route({
3964
- method: 'POST',
3965
- path: '/v1/account/{account}/mailbox',
3966
-
3967
- async handler(request) {
3968
- let accountObject = new Account({
3969
- redis,
3970
- account: request.params.account,
3971
- call,
3972
- secret: await getSecret(),
3973
- timeout: request.headers['x-ee-timeout']
3974
- });
3975
-
3976
- try {
3977
- return await accountObject.createMailbox(request.payload.path);
3978
- } catch (err) {
3979
- request.logger.error({ msg: 'API request failed', err });
3980
- if (Boom.isBoom(err)) {
3981
- throw err;
3982
- }
3983
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
3984
- if (err.code) {
3985
- error.output.payload.code = err.code;
3986
- }
3987
- if (err.info) {
3988
- error.output.payload.details = err.info;
3989
- }
3990
- throw error;
3991
- }
3992
- },
3993
-
3994
- options: {
3995
- description: 'Create mailbox',
3996
- notes: 'Create new mailbox folder',
3997
- tags: ['api', 'Mailbox'],
3998
-
3999
- plugins: {},
4000
-
4001
- auth: {
4002
- strategy: 'api-token',
4003
- mode: 'required'
4004
- },
4005
- cors: CORS_CONFIG,
4006
-
4007
- validate: {
4008
- options: {
4009
- stripUnknown: false,
4010
- abortEarly: false,
4011
- convert: true
4012
- },
4013
- failAction,
4014
-
4015
- params: Joi.object({
4016
- account: accountIdSchema.required()
4017
- }),
4018
-
4019
- payload: Joi.object({
4020
- path: Joi.array()
4021
- .items(Joi.string().max(256))
4022
- .single()
4023
- .example(['Parent folder', 'Subfolder'])
4024
- .description('Mailbox path as an array or a string. If account is namespaced then namespace prefix is added by default.')
4025
- .label('MailboxPath')
4026
- }).label('CreateMailbox')
4027
- },
4028
-
4029
- response: {
4030
- schema: Joi.object({
4031
- path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to mailbox').label('MailboxPath'),
4032
- mailboxId: Joi.string().example('1439876283476').description('Mailbox ID (if server has support)').label('MailboxId'),
4033
- created: Joi.boolean().example(true).description('Was the mailbox created')
4034
- }).label('CreateMailboxResponse'),
4035
- failAction: 'log'
4036
- }
4037
- }
4038
- });
4039
-
4040
- server.route({
4041
- method: 'PUT',
4042
- path: '/v1/account/{account}/mailbox',
4043
-
4044
- async handler(request) {
4045
- let accountObject = new Account({
4046
- redis,
4047
- account: request.params.account,
4048
- call,
4049
- secret: await getSecret(),
4050
- timeout: request.headers['x-ee-timeout']
4051
- });
4052
-
4053
- try {
4054
- return await accountObject.modifyMailbox(request.payload.path, request.payload.newPath, request.payload.subscribed);
4055
- } catch (err) {
4056
- request.logger.error({ msg: 'API request failed', err });
4057
- if (Boom.isBoom(err)) {
4058
- throw err;
4059
- }
4060
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
4061
- if (err.code) {
4062
- error.output.payload.code = err.code;
4063
- }
4064
- if (err.info) {
4065
- error.output.payload.details = err.info;
4066
- }
4067
- throw error;
4068
- }
4069
- },
4070
-
4071
- options: {
4072
- description: 'Modify mailbox',
4073
- notes: 'Modify an existing mailbox folder (rename or change subscription status)',
4074
- tags: ['api', 'Mailbox'],
4075
-
4076
- plugins: {},
4077
-
4078
- auth: {
4079
- strategy: 'api-token',
4080
- mode: 'required'
4081
- },
4082
- cors: CORS_CONFIG,
4083
-
4084
- validate: {
4085
- options: {
4086
- stripUnknown: false,
4087
- abortEarly: false,
4088
- convert: true
4089
- },
4090
- failAction,
4091
-
4092
- params: Joi.object({
4093
- account: accountIdSchema.required()
4094
- }),
4095
-
4096
- payload: Joi.object({
4097
- path: Joi.string().required().example('Folder Name').description('Mailbox folder path to modify').label('ExistingMailboxPath'),
4098
- newPath: Joi.array()
4099
- .items(Joi.string().max(256))
4100
- .single()
4101
- .example(['Parent folder', 'Subfolder'])
4102
- .description('New mailbox path as an array or a string. If account is namespaced then namespace prefix is added by default. Optional.')
4103
- .label('TargetMailboxPath'),
4104
- subscribed: Joi.boolean()
4105
- .example(true)
4106
- .description('Change mailbox subscription status. Only applies to IMAP accounts, ignored for Gmail and Outlook.')
4107
- .label('SubscriptionStatus')
4108
- })
4109
- .or('newPath', 'subscribed')
4110
- .label('ModifyMailbox')
4111
- },
4112
-
4113
- response: {
4114
- schema: Joi.object({
4115
- path: Joi.string().required().example('Mail').description('Mailbox folder path').label('ExistingMailboxPath'),
4116
- newPath: Joi.string().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to mailbox if renamed').label('NewMailboxPath'),
4117
- renamed: Joi.boolean().example(true).description('Was the mailbox renamed'),
4118
- subscribed: Joi.boolean().example(true).description('Subscription status after modification')
4119
- }).label('ModifyMailboxResponse'),
4120
- failAction: 'log'
4121
- }
4122
- }
4123
- });
4124
-
4125
- server.route({
4126
- method: 'DELETE',
4127
- path: '/v1/account/{account}/mailbox',
4128
-
4129
- async handler(request) {
4130
- let accountObject = new Account({
4131
- redis,
4132
- account: request.params.account,
4133
- call,
4134
- secret: await getSecret(),
4135
- timeout: request.headers['x-ee-timeout']
4136
- });
4137
-
4138
- try {
4139
- return await accountObject.deleteMailbox(request.query.path);
4140
- } catch (err) {
4141
- request.logger.error({ msg: 'API request failed', err });
4142
- if (Boom.isBoom(err)) {
4143
- throw err;
4144
- }
4145
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
4146
- if (err.code) {
4147
- error.output.payload.code = err.code;
4148
- }
4149
- throw error;
4150
- }
4151
- },
4152
-
4153
- options: {
4154
- description: 'Delete mailbox',
4155
- notes: 'Delete existing mailbox folder',
4156
- tags: ['api', 'Mailbox'],
4157
-
4158
- plugins: {},
4159
-
4160
- auth: {
4161
- strategy: 'api-token',
4162
- mode: 'required'
4163
- },
4164
- cors: CORS_CONFIG,
4165
-
4166
- validate: {
4167
- options: {
4168
- stripUnknown: false,
4169
- abortEarly: false,
4170
- convert: true
4171
- },
4172
- failAction,
4173
-
4174
- params: Joi.object({
4175
- account: accountIdSchema.required()
4176
- }),
4177
-
4178
- query: Joi.object({
4179
- path: Joi.string().required().example('My Outdated Mail').description('Mailbox folder path to delete').label('MailboxPath')
4180
- }).label('DeleteMailbox')
4181
- },
4182
-
4183
- response: {
4184
- schema: Joi.object({
4185
- path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to mailbox').label('MailboxPath'),
4186
- deleted: Joi.boolean().example(true).description('Was the mailbox deleted')
4187
- }).label('DeleteMailboxResponse'),
4188
- failAction: 'log'
4189
- }
4190
- }
4191
- });
4192
-
4193
- server.route({
4194
- method: 'GET',
4195
- path: '/v1/account/{account}/message/{message}/source',
4196
-
4197
- async handler(request, h) {
4198
- let accountObject = new Account({
4199
- redis,
4200
- account: request.params.account,
4201
- call,
4202
- secret: await getSecret(),
4203
- timeout: request.headers['x-ee-timeout']
4204
- });
4205
-
4206
- try {
4207
- const response = await accountObject.getRawMessage(request.params.message);
4208
- return h.response(response);
4209
- } catch (err) {
4210
- request.logger.error({ msg: 'API request failed', err });
4211
- if (Boom.isBoom(err)) {
4212
- throw err;
4213
- }
4214
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
4215
- if (err.code) {
4216
- error.output.payload.code = err.code;
4217
- }
4218
- throw error;
4219
- }
4220
- },
4221
- options: {
4222
- description: 'Download raw message',
4223
- notes: 'Fetches raw message as a stream',
4224
- tags: ['api', 'Message'],
4225
-
4226
- auth: {
4227
- strategy: 'api-token',
4228
- mode: 'required'
4229
- },
4230
- cors: CORS_CONFIG,
4231
-
4232
- plugins: {
4233
- 'hapi-swagger': {
4234
- produces: ['message/rfc822']
4235
- }
4236
- },
4237
-
4238
- validate: {
4239
- options: {
4240
- stripUnknown: false,
4241
- abortEarly: false,
4242
- convert: true
4243
- },
4244
- failAction,
4245
-
4246
- params: Joi.object({
4247
- account: accountIdSchema.required(),
4248
- message: Joi.string().base64({ paddingRequired: false, urlSafe: true }).max(256).example('AAAAAQAACnA').required().description('Message ID')
4249
- }).label('RawMessageRequest')
4250
- } /*,
4251
-
4252
- response: {
4253
- schema: Joi.binary().example('MIME-Version: 1.0...').description('RFC822 formatted email').label('RawMessageResponse'),
4254
- failAction: 'log'
4255
- }
4256
- */
4257
- }
4258
- });
4259
-
4260
- server.route({
4261
- method: 'GET',
4262
- path: '/v1/account/{account}/attachment/{attachment}',
4263
-
4264
- async handler(request) {
4265
- let accountObject = new Account({
4266
- redis,
4267
- account: request.params.account,
4268
- call,
4269
- secret: await getSecret(),
4270
- timeout: request.headers['x-ee-timeout']
4271
- });
4272
-
4273
- try {
4274
- return await accountObject.getAttachment(request.params.attachment);
4275
- } catch (err) {
4276
- request.logger.error({ msg: 'API request failed', err });
4277
- if (Boom.isBoom(err)) {
4278
- throw err;
4279
- }
4280
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
4281
- if (err.code) {
4282
- error.output.payload.code = err.code;
4283
- }
4284
- throw error;
4285
- }
4286
- },
4287
- options: {
4288
- description: 'Download attachment',
4289
- notes: 'Fetches attachment file as a binary stream',
4290
- tags: ['api', 'Message'],
4291
-
4292
- auth: {
4293
- strategy: 'api-token',
4294
- mode: 'required'
4295
- },
4296
- cors: CORS_CONFIG,
4297
-
4298
- plugins: {
4299
- 'hapi-swagger': {
4300
- produces: ['application/octet-stream']
4301
- }
4302
- },
4303
-
4304
- validate: {
4305
- options: {
4306
- stripUnknown: false,
4307
- abortEarly: false,
4308
- convert: true
4309
- },
4310
- failAction,
4311
-
4312
- params: Joi.object({
4313
- account: accountIdSchema.required(),
4314
- attachment: Joi.string()
4315
- .base64({ paddingRequired: false, urlSafe: true })
4316
- .max(2 * 1024)
4317
- .required()
4318
- .example('AAAAAQAACnAcde')
4319
- .description('Attachment ID')
4320
- })
4321
- }
4322
- }
4323
- });
4324
-
4325
- server.route({
4326
- method: 'GET',
4327
- path: '/v1/account/{account}/message/{message}',
4328
-
4329
- async handler(request, h) {
4330
- let accountObject = new Account({
4331
- redis,
4332
- account: request.params.account,
4333
- call,
4334
- secret: await getSecret(),
4335
- esClient: await h.getESClient(request.logger),
4336
- timeout: request.headers['x-ee-timeout']
4337
- });
4338
-
4339
- try {
4340
- return await accountObject.getMessage(request.params.message, request.query);
4341
- } catch (err) {
4342
- request.logger.error({ msg: 'API request failed', err });
4343
- if (Boom.isBoom(err)) {
4344
- throw err;
4345
- }
4346
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
4347
- if (err.code) {
4348
- error.output.payload.code = err.code;
4349
- }
4350
- throw error;
4351
- }
4352
- },
4353
- options: {
4354
- description: 'Get message information',
4355
- notes: 'Returns details of a specific message. By default text content is not included, use textType value to force retrieving text',
4356
- tags: ['api', 'Message'],
4357
-
4358
- auth: {
4359
- strategy: 'api-token',
4360
- mode: 'required'
4361
- },
4362
- cors: CORS_CONFIG,
4363
-
4364
- validate: {
4365
- options: {
4366
- stripUnknown: false,
4367
- abortEarly: false,
4368
- convert: true
4369
- },
4370
- failAction,
4371
-
4372
- query: Joi.object({
4373
- maxBytes: Joi.number()
4374
- .integer()
4375
- .min(0)
4376
- .max(1024 * 1024 * 1024)
4377
- .example(5 * 1025 * 1024)
4378
- .description('Max length of text content'),
4379
- textType: Joi.string()
4380
- .lowercase()
4381
- .valid('html', 'plain', '*')
4382
- .example('*')
4383
- .description('Which text content to return, use * for all. By default text content is not returned.'),
4384
-
4385
- webSafeHtml: Joi.boolean()
4386
- .truthy('Y', 'true', '1')
4387
- .falsy('N', 'false', 0)
4388
- .default(false)
4389
- .description(
4390
- 'Shorthand option to fetch and preprocess HTML and inline images. Overrides `textType`, `preProcessHtml`, and `embedAttachedImages` options.'
4391
- )
4392
- .label('WebSafeHtml'),
4393
-
4394
- embedAttachedImages: Joi.boolean()
4395
- .truthy('Y', 'true', '1')
4396
- .falsy('N', 'false', 0)
4397
- .default(false)
4398
- .description('If true, then fetches attached images and embeds these in the HTML as data URIs')
4399
- .label('EmbedImages'),
4400
-
4401
- preProcessHtml: Joi.boolean()
4402
- .truthy('Y', 'true', '1')
4403
- .falsy('N', 'false', 0)
4404
- .default(false)
4405
- .description('If true, then pre-processes HTML for compatibility')
4406
- .label('PreProcess'),
4407
-
4408
- markAsSeen: Joi.boolean()
4409
- .truthy('Y', 'true', '1')
4410
- .falsy('N', 'false', 0)
4411
- .default(false)
4412
- .description('If true, then marks unseen email as seen while returning the message')
4413
- .label('MarkAsSeen'),
4414
-
4415
- documentStore: documentStoreSchema.default(false)
4416
- }),
4417
-
4418
- params: Joi.object({
4419
- account: accountIdSchema.required(),
4420
- message: Joi.string().base64({ paddingRequired: false, urlSafe: true }).max(256).required().example('AAAAAQAACnA').description('Message ID')
4421
- })
4422
- },
4423
-
4424
- response: {
4425
- schema: messageDetailsSchema,
4426
- failAction: 'log'
4427
- }
4428
- }
4429
- });
4430
-
4431
- server.route({
4432
- method: 'POST',
4433
- path: '/v1/account/{account}/message',
4434
-
4435
- async handler(request) {
4436
- let accountObject = new Account({
4437
- redis,
4438
- account: request.params.account,
4439
- call,
4440
- secret: await getSecret(),
4441
- timeout: request.headers['x-ee-timeout']
4442
- });
4443
-
4444
- try {
4445
- return await accountObject.uploadMessage(request.payload);
4446
- } catch (err) {
4447
- request.logger.error({ msg: 'API request failed', err });
4448
- if (Boom.isBoom(err)) {
4449
- throw err;
4450
- }
4451
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
4452
- if (err.code) {
4453
- error.output.payload.code = err.code;
4454
- }
4455
- throw error;
4456
- }
4457
- },
4458
- options: {
4459
- payload: {
4460
- maxBytes: MAX_BODY_SIZE,
4461
- timeout: MAX_PAYLOAD_TIMEOUT
4462
- },
4463
-
4464
- description: 'Upload message',
4465
- notes: 'Upload a message structure, compile it into an EML file and store it into selected mailbox.',
4466
- tags: ['api', 'Message'],
4467
-
4468
- plugins: {},
4469
-
4470
- auth: {
4471
- strategy: 'api-token',
4472
- mode: 'required'
4473
- },
4474
- cors: CORS_CONFIG,
4475
-
4476
- validate: {
4477
- options: {
4478
- stripUnknown: false,
4479
- abortEarly: false,
4480
- convert: true
4481
- },
4482
- failAction,
4483
-
4484
- params: Joi.object({
4485
- account: accountIdSchema.required()
4486
- }),
4487
-
4488
- payload: Joi.object({
4489
- path: Joi.string().required().example('INBOX').description('Target mailbox folder path'),
4490
-
4491
- flags: Joi.array().items(Joi.string().max(128)).example(['\\Seen', '\\Draft']).default([]).description('Message flags').label('Flags'),
4492
- internalDate: Joi.date().iso().example('2021-07-08T07:06:34.336Z').description('Sets the internal date for this message'),
4493
-
4494
- reference: messageReferenceSchema,
4495
-
4496
- raw: Joi.string()
4497
- .base64()
4498
- .max(MAX_ATTACHMENT_SIZE)
4499
- .example('TUlNRS1WZXJzaW9uOiAxLjANClN1YmplY3Q6IGhlbGxvIHdvcmxkDQoNCkhlbGxvIQ0K')
4500
- .description(
4501
- 'A Base64-encoded email message in RFC 822 format. If you provide other fields along with raw, those fields will override the corresponding values in the raw message.'
4502
- )
4503
- .label('RFC822Raw'),
4504
-
4505
- from: fromAddressSchema,
4506
-
4507
- to: Joi.array()
4508
- .items(addressSchema)
4509
- .single()
4510
- .description('List of addresses')
4511
- .example([{ address: 'recipient@example.com' }])
4512
- .label('AddressList'),
4513
-
4514
- cc: Joi.array().items(addressSchema).single().description('List of addresses').label('AddressList'),
4515
-
4516
- bcc: Joi.array().items(addressSchema).single().description('List of addresses').label('AddressList'),
4517
-
4518
- subject: Joi.string()
4519
- .allow('')
4520
- .max(10 * 1024)
4521
- .example('What a wonderful message')
4522
- .description('Message subject'),
4523
-
4524
- text: Joi.string().max(MAX_ATTACHMENT_SIZE).example('Hello from myself!').description('Message Text'),
4525
-
4526
- html: Joi.string().max(MAX_ATTACHMENT_SIZE).example('<p>Hello from myself!</p>').description('Message HTML'),
4527
-
4528
- attachments: Joi.array()
4529
- .items(
4530
- Joi.object({
4531
- filename: Joi.string().max(256).example('transparent.gif'),
4532
- content: Joi.string()
4533
- .base64()
4534
- .max(MAX_ATTACHMENT_SIZE)
4535
- .required()
4536
- .example('R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=')
4537
- .description('Base64 formatted attachment file')
4538
- .when('reference', {
4539
- is: Joi.exist().not(false, null),
4540
- then: Joi.forbidden(),
4541
- otherwise: Joi.required()
4542
- }),
4543
-
4544
- contentType: Joi.string().lowercase().max(256).example('image/gif'),
4545
- contentDisposition: Joi.string().lowercase().valid('inline', 'attachment'),
4546
- cid: Joi.string().max(256).example('unique-image-id@localhost').description('Content-ID value for embedded images'),
4547
- encoding: Joi.string().valid('base64').default('base64'),
4548
-
4549
- reference: Joi.string()
4550
- .base64({ paddingRequired: false, urlSafe: true })
4551
- .max(256)
4552
- .allow(false, null)
4553
- .example('AAAAAQAACnAcde')
4554
- .description(
4555
- 'References an existing attachment by its ID instead of providing new attachment content. If this field is set, the `content` field must not be included. If not set, the `content` field is required.'
4556
- )
4557
- }).label('UploadAttachment')
4558
- )
4559
- .description('List of attachments')
4560
- .label('UploadAttachmentList'),
4561
-
4562
- messageId: Joi.string().max(996).example('<test123@example.com>').description('Message ID'),
4563
- headers: Joi.object().label('CustomHeaders').description('Custom Headers').unknown().example({
4564
- 'X-My-Custom-Header': 'Custom header value'
4565
- }),
4566
-
4567
- locale: Joi.string().empty('').max(100).example('fr').description('Optional locale'),
4568
- tz: Joi.string().empty('').max(100).example('Europe/Tallinn').description('Optional timezone')
4569
- }).label('MessageUpload')
4570
- },
4571
-
4572
- response: {
4573
- schema: Joi.object({
4574
- id: Joi.string()
4575
- .example('AAAAAgAACrI')
4576
- .description(
4577
- 'Unique identifier for the message. NB! This and other fields might not be present if server did not provide enough information'
4578
- )
4579
- .label('MessageAppendId'),
4580
- path: Joi.string().example('INBOX').description('Folder this message was uploaded to').label('MessageAppendPath'),
4581
- uid: Joi.number().integer().example(12345).description('UID of uploaded message'),
4582
- uidValidity: Joi.string().example('12345').description('UIDVALIDITY of the target folder. Numeric value cast as string.'),
4583
- seq: Joi.number().integer().example(12345).description('Sequence number of uploaded message'),
4584
-
4585
- messageId: Joi.string().max(996).example('<test123@example.com>').description('Message ID'),
4586
-
4587
- reference: Joi.object({
4588
- message: Joi.string()
4589
- .base64({ paddingRequired: false, urlSafe: true })
4590
- .max(256)
4591
- .required()
4592
- .example('AAAAAQAACnA')
4593
- .description('Referenced message ID'),
4594
- success: Joi.boolean().example(true).description('Was the referenced message processed').label('ResponseReferenceSuccess'),
4595
- documentStore: documentStoreSchema.default(false),
4596
- error: Joi.string().example('Referenced message was not found').description('An error message if referenced message processing failed')
4597
- })
4598
- .description('Reference info if referencing was requested')
4599
- .label('ResponseReference')
4600
- }).label('MessageUploadResponse'),
4601
- failAction: 'log'
4602
- }
4603
- }
4604
- });
4605
-
4606
- server.route({
4607
- method: 'PUT',
4608
- path: '/v1/account/{account}/message/{message}',
4609
-
4610
- async handler(request) {
4611
- let accountObject = new Account({
4612
- redis,
4613
- account: request.params.account,
4614
- call,
4615
- secret: await getSecret(),
4616
- timeout: request.headers['x-ee-timeout']
4617
- });
4618
-
4619
- try {
4620
- return await accountObject.updateMessage(request.params.message, request.payload);
4621
- } catch (err) {
4622
- request.logger.error({ msg: 'API request failed', err });
4623
- if (Boom.isBoom(err)) {
4624
- throw err;
4625
- }
4626
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
4627
- if (err.code) {
4628
- error.output.payload.code = err.code;
4629
- }
4630
- throw error;
4631
- }
4632
- },
4633
- options: {
4634
- description: 'Update message',
4635
- notes: 'Update message information. Mainly this means changing message flag values',
4636
- tags: ['api', 'Message'],
4637
-
4638
- plugins: {},
4639
-
4640
- auth: {
4641
- strategy: 'api-token',
4642
- mode: 'required'
4643
- },
4644
- cors: CORS_CONFIG,
4645
-
4646
- validate: {
4647
- options: {
4648
- stripUnknown: false,
4649
- abortEarly: false,
4650
- convert: true
4651
- },
4652
- failAction,
4653
-
4654
- params: Joi.object({
4655
- account: accountIdSchema.required(),
4656
- message: Joi.string().max(256).required().example('AAAAAQAACnA').description('Message ID')
4657
- }),
4658
-
4659
- payload: messageUpdateSchema
4660
- },
4661
- response: {
4662
- schema: Joi.object({
4663
- flags: Joi.object({
4664
- add: Joi.array().items(Joi.string()).example(['\\Seen', '\\Flagged']),
4665
- delete: Joi.array().items(Joi.string()).example(['\\Draft']),
4666
- set: Joi.array().items(Joi.string()).example(['\\Seen'])
4667
- }).label('FlagResponse'),
4668
- labels: Joi.object({
4669
- add: Joi.array().items(Joi.string()).example(['Label1', 'Label2']),
4670
- delete: Joi.array().items(Joi.string()).example(['Label3']),
4671
- set: Joi.array().items(Joi.string()).example(['Label1'])
4672
- }).label('FlagResponse')
4673
- }).label('MessageUpdateResponse'),
4674
- failAction: 'log'
4675
- }
4676
- }
4677
- });
4678
-
4679
- server.route({
4680
- method: 'PUT',
4681
- path: '/v1/account/{account}/messages',
4682
-
4683
- async handler(request) {
4684
- let accountObject = new Account({
4685
- redis,
4686
- account: request.params.account,
4687
- call,
4688
- secret: await getSecret(),
4689
- timeout: request.headers['x-ee-timeout']
4690
- });
4691
-
4692
- try {
4693
- return await accountObject.updateMessages(request.query.path, request.payload.search, request.payload.update);
4694
- } catch (err) {
4695
- request.logger.error({ msg: 'API request failed', err });
4696
- if (Boom.isBoom(err)) {
4697
- throw err;
4698
- }
4699
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
4700
- if (err.code) {
4701
- error.output.payload.code = err.code;
4702
- }
4703
- throw error;
4704
- }
4705
- },
4706
- options: {
4707
- description: 'Update messages',
4708
- notes: 'Update message information for matching emails',
4709
- tags: ['api', 'Multi Message Actions'],
4710
-
4711
- plugins: {},
4712
-
4713
- auth: {
4714
- strategy: 'api-token',
4715
- mode: 'required'
4716
- },
4717
- cors: CORS_CONFIG,
4718
-
4719
- validate: {
4720
- options: {
4721
- stripUnknown: false,
4722
- abortEarly: false,
4723
- convert: true
4724
- },
4725
- failAction,
4726
-
4727
- params: Joi.object({
4728
- account: accountIdSchema.required()
4729
- }),
4730
-
4731
- query: Joi.object({
4732
- path: Joi.string().empty('').required().example('INBOX').description(listMessageFolderPathDescription)
4733
- }).label('MessagesUpdateQuery'),
4734
-
4735
- payload: Joi.object({
4736
- search: searchSchema,
4737
- update: messageUpdateSchema
4738
- }).label('MessagesUpdateRequest')
4739
- },
4740
- response: {
4741
- schema: Joi.object({
4742
- flags: Joi.object({
4743
- add: Joi.array().items(Joi.string()).example(['\\Seen', '\\Flagged']),
4744
- delete: Joi.array().items(Joi.string()).example(['\\Draft']),
4745
- set: Joi.array().items(Joi.string()).example(['\\Seen'])
4746
- }).label('FlagResponse'),
4747
- labels: Joi.object({
4748
- add: Joi.array().items(Joi.string()).example(['Label1', 'Label2']),
4749
- delete: Joi.array().items(Joi.string()).example(['Label3']),
4750
- set: Joi.array().items(Joi.string()).example(['Label1'])
4751
- }).label('FlagResponse')
4752
- }).label('MessageUpdateResponse'),
4753
- failAction: 'log'
4754
- }
4755
- }
4756
- });
4757
-
4758
- server.route({
4759
- method: 'PUT',
4760
- path: '/v1/account/{account}/message/{message}/move',
4761
-
4762
- async handler(request) {
4763
- let accountObject = new Account({
4764
- redis,
4765
- account: request.params.account,
4766
- call,
4767
- secret: await getSecret(),
4768
- timeout: request.headers['x-ee-timeout']
4769
- });
4770
-
4771
- try {
4772
- return await accountObject.moveMessage(
4773
- request.params.message,
4774
- { path: request.payload.path },
4775
- {
4776
- source: request.payload.source
4777
- ? {
4778
- path: request.payload.source
4779
- }
4780
- : null
4781
- }
4782
- );
4783
- } catch (err) {
4784
- request.logger.error({ msg: 'API request failed', err });
4785
- if (Boom.isBoom(err)) {
4786
- throw err;
4787
- }
4788
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
4789
- if (err.code) {
4790
- error.output.payload.code = err.code;
4791
- }
4792
- throw error;
4793
- }
4794
- },
4795
- options: {
4796
- description: 'Move a message to a specified folder',
4797
- notes: 'Moves a message to a target folder',
4798
- tags: ['api', 'Message'],
4799
-
4800
- plugins: {},
4801
-
4802
- auth: {
4803
- strategy: 'api-token',
4804
- mode: 'required'
4805
- },
4806
- cors: CORS_CONFIG,
4807
-
4808
- validate: {
4809
- options: {
4810
- stripUnknown: false,
4811
- abortEarly: false,
4812
- convert: true
4813
- },
4814
- failAction,
4815
-
4816
- params: Joi.object({
4817
- account: accountIdSchema.required(),
4818
- message: Joi.string().max(256).required().example('AAAAAQAACnA').description('Message ID')
4819
- }),
4820
-
4821
- payload: Joi.object({
4822
- path: Joi.string().required().example('INBOX').description('Destination mailbox folder path'),
4823
- source: Joi.string()
4824
- .example('INBOX')
4825
- .description('Source mailbox folder path (Gmail API only). Needed to remove the label from the message.')
4826
- })
4827
- .example({ path: 'Target/Folder' })
4828
- .label('MessageMove')
4829
- },
4830
-
4831
- response: {
4832
- schema: Joi.object({
4833
- path: Joi.string().required().example('INBOX').description('Destination mailbox folder path'),
4834
- id: Joi.string().max(256).example('AAAAAQAACnA').description('ID of the moved message. Only included if the server provides it.'),
4835
- uid: Joi.number()
4836
- .integer()
4837
- .example(12345)
4838
- .description('UID of the moved message, applies only to IMAP accounts. Only included if the server provides it.')
4839
- }).label('MessageMoveResponse'),
4840
- failAction: 'log'
4841
- }
4842
- }
4843
- });
4844
-
4845
- server.route({
4846
- method: 'PUT',
4847
- path: '/v1/account/{account}/messages/move',
4848
-
4849
- async handler(request) {
4850
- let accountObject = new Account({
4851
- redis,
4852
- account: request.params.account,
4853
- call,
4854
- secret: await getSecret(),
4855
- timeout: request.headers['x-ee-timeout']
4856
- });
4857
-
4858
- try {
4859
- return await accountObject.moveMessages(request.query.path, request.payload.search, { path: request.payload.path });
4860
- } catch (err) {
4861
- request.logger.error({ msg: 'API request failed', err });
4862
- if (Boom.isBoom(err)) {
4863
- throw err;
4864
- }
4865
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
4866
- if (err.code) {
4867
- error.output.payload.code = err.code;
4868
- }
4869
- throw error;
4870
- }
4871
- },
4872
- options: {
4873
- description: 'Move messages',
4874
- notes: 'Move messages matching to a search query to another folder',
4875
- tags: ['api', 'Multi Message Actions'],
4876
-
4877
- plugins: {},
4878
-
4879
- auth: {
4880
- strategy: 'api-token',
4881
- mode: 'required'
4882
- },
4883
- cors: CORS_CONFIG,
4884
-
4885
- validate: {
4886
- options: {
4887
- stripUnknown: false,
4888
- abortEarly: false,
4889
- convert: true
4890
- },
4891
- failAction,
4892
-
4893
- params: Joi.object({
4894
- account: accountIdSchema.required()
4895
- }),
4896
-
4897
- query: Joi.object({
4898
- path: Joi.string().empty('').required().example('INBOX').description(listMessageFolderPathDescription)
4899
- }).label('MessagesMoveQuery'),
4900
-
4901
- payload: Joi.object({
4902
- search: searchSchema,
4903
- path: Joi.string().required().example('INBOX').description('Target mailbox folder path')
4904
- }).label('MessagesMoveRequest')
4905
- },
4906
-
4907
- response: {
4908
- schema: Joi.object({
4909
- path: Joi.string().required().example('INBOX').description('Target mailbox folder path'),
4910
-
4911
- idMap: Joi.array()
4912
- .items(Joi.array().length(2).items(Joi.string().max(256).required().description('Message ID')).label('IdMapTuple'))
4913
- .example([['AAAAAQAACnA', 'AAAAAwAAAD4']])
4914
- .description('An optional map of source and target ID values, if the server provided this info')
4915
- .label('IdMapArray'),
4916
-
4917
- emailIds: Joi.array()
4918
- .items(Joi.string().example('1278455344230334865'))
4919
- .description('An optional list of emailId values, if the server supports unique email IDs')
4920
- .label('EmailIdsArray')
4921
- }).label('MessagesMoveResponse'),
4922
- failAction: 'log'
4923
- }
4924
- }
4925
- });
4926
-
4927
- server.route({
4928
- method: 'DELETE',
4929
- path: '/v1/account/{account}/message/{message}',
4930
-
4931
- async handler(request) {
4932
- let accountObject = new Account({
4933
- redis,
4934
- account: request.params.account,
4935
- call,
4936
- secret: await getSecret(),
4937
- timeout: request.headers['x-ee-timeout']
4938
- });
4939
-
4940
- try {
4941
- return await accountObject.deleteMessage(request.params.message, request.query.force);
4942
- } catch (err) {
4943
- request.logger.error({ msg: 'API request failed', err });
4944
- if (Boom.isBoom(err)) {
4945
- throw err;
4946
- }
4947
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
4948
- if (err.code) {
4949
- error.output.payload.code = err.code;
4950
- }
4951
- throw error;
4952
- }
4953
- },
4954
- options: {
4955
- description: 'Delete message',
4956
- notes: 'Move message to Trash or delete it if already in Trash',
4957
- tags: ['api', 'Message'],
4958
-
4959
- plugins: {},
4960
-
4961
- auth: {
4962
- strategy: 'api-token',
4963
- mode: 'required'
4964
- },
4965
- cors: CORS_CONFIG,
4966
-
4967
- validate: {
4968
- options: {
4969
- stripUnknown: false,
4970
- abortEarly: false,
4971
- convert: true
4972
- },
4973
- failAction,
4974
-
4975
- query: Joi.object({
4976
- force: Joi.boolean()
4977
- .truthy('Y', 'true', '1')
4978
- .falsy('N', 'false', 0)
4979
- .default(false)
4980
- .description('Delete message even if not in Trash. Not supported for Gmail API accounts.')
4981
- .label('ForceDelete')
4982
- }).label('MessageDeleteQuery'),
4983
-
4984
- params: Joi.object({
4985
- account: accountIdSchema.required(),
4986
- message: Joi.string().max(256).required().example('AAAAAQAACnA').description('Message ID')
4987
- }).label('MessageDelete')
4988
- },
4989
- response: {
4990
- schema: Joi.object({
4991
- deleted: Joi.boolean().example(false).description('Was the delete action executed'),
4992
- moved: Joi.object({
4993
- destination: Joi.string().required().example('Trash').description('Trash folder path').label('TrashPath'),
4994
- message: Joi.string().required().example('AAAAAwAAAWg').description('Message ID in Trash').label('TrashMessageId')
4995
- }).description('Present if message was moved to Trash')
4996
- }).label('MessageDeleteResponse'),
4997
- failAction: 'log'
4998
- }
4999
- }
5000
- });
5001
-
5002
- server.route({
5003
- method: 'PUT',
5004
- path: '/v1/account/{account}/messages/delete',
5005
-
5006
- async handler(request) {
5007
- let accountObject = new Account({
5008
- redis,
5009
- account: request.params.account,
5010
- call,
5011
- secret: await getSecret(),
5012
- timeout: request.headers['x-ee-timeout']
5013
- });
5014
-
5015
- try {
5016
- return await accountObject.deleteMessages(request.query.path, request.payload.search, request.query.force);
5017
- } catch (err) {
5018
- request.logger.error({ msg: 'API request failed', err });
5019
- if (Boom.isBoom(err)) {
5020
- throw err;
5021
- }
5022
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
5023
- if (err.code) {
5024
- error.output.payload.code = err.code;
5025
- }
5026
- throw error;
5027
- }
5028
- },
5029
- options: {
5030
- description: 'Delete messages',
5031
- notes: 'Move messages to Trash or delete these if already in Trash',
5032
- tags: ['api', 'Multi Message Actions'],
5033
-
5034
- plugins: {},
5035
-
5036
- auth: {
5037
- strategy: 'api-token',
5038
- mode: 'required'
5039
- },
5040
- cors: CORS_CONFIG,
5041
-
5042
- validate: {
5043
- options: {
5044
- stripUnknown: false,
5045
- abortEarly: false,
5046
- convert: true
5047
- },
5048
- failAction,
5049
-
5050
- params: Joi.object({
5051
- account: accountIdSchema.required()
5052
- }),
5053
-
5054
- query: Joi.object({
5055
- path: Joi.string().empty('').required().example('INBOX').description(listMessageFolderPathDescription),
5056
- force: Joi.boolean()
5057
- .truthy('Y', 'true', '1')
5058
- .falsy('N', 'false', 0)
5059
- .default(false)
5060
- .description('Delete messages even if not in Trash')
5061
- .label('ForceDelete')
5062
- }).label('MessagesDeleteQuery'),
5063
-
5064
- payload: Joi.object({
5065
- search: searchSchema
5066
- }).label('MessagesDeleteRequest')
5067
- },
5068
-
5069
- response: {
5070
- schema: Joi.object({
5071
- deleted: Joi.boolean().example(false).description('Was the delete action executed'),
5072
- moved: Joi.object({
5073
- destination: Joi.string().required().example('Trash').description('Trash folder path').label('TrashPath'),
5074
-
5075
- idMap: Joi.array()
5076
- .items(Joi.array().length(2).items(Joi.string().max(256).required().description('Message ID')).label('IdMapTuple'))
5077
- .example([['AAAAAQAACnA', 'AAAAAwAAAD4']])
5078
- .description('An optional map of source and target ID values, if the server provided this info')
5079
- .label('IdMapArray'),
5080
-
5081
- emailIds: Joi.array()
5082
- .items(Joi.string().example('1278455344230334865'))
5083
- .description('An optional list of emailId values, if the server supports unique email IDs')
5084
- .label('EmailIdsArray')
5085
- })
5086
- .label('MessagesMovedToTrash')
5087
- .description('Value is present if messages were moved to Trash')
5088
- }).label('MessagesDeleteResponse'),
5089
- failAction: 'log'
5090
- }
5091
- }
5092
- });
5093
-
5094
- server.route({
5095
- method: 'GET',
5096
- path: '/v1/account/{account}/text/{text}',
5097
-
5098
- async handler(request, h) {
5099
- let accountObject = new Account({
5100
- redis,
5101
- account: request.params.account,
5102
- call,
5103
- secret: await getSecret(),
5104
- esClient: await h.getESClient(request.logger),
5105
- timeout: request.headers['x-ee-timeout']
5106
- });
5107
-
5108
- try {
5109
- return await accountObject.getText(request.params.text, request.query);
5110
- } catch (err) {
5111
- request.logger.error({ msg: 'API request failed', err });
5112
- if (Boom.isBoom(err)) {
5113
- throw err;
5114
- }
5115
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
5116
- if (err.code) {
5117
- error.output.payload.code = err.code;
5118
- }
5119
- throw error;
5120
- }
5121
- },
5122
- options: {
5123
- description: 'Retrieve message text',
5124
- notes: 'Retrieves message text',
5125
- tags: ['api', 'Message'],
5126
-
5127
- auth: {
5128
- strategy: 'api-token',
5129
- mode: 'required'
5130
- },
5131
- cors: CORS_CONFIG,
5132
-
5133
- validate: {
5134
- options: {
5135
- stripUnknown: false,
5136
- abortEarly: false,
5137
- convert: true
5138
- },
5139
- failAction,
5140
-
5141
- query: Joi.object({
5142
- maxBytes: Joi.number()
5143
- .integer()
5144
- .min(0)
5145
- .max(1024 * 1024 * 1024)
5146
- .example(MAX_ATTACHMENT_SIZE)
5147
- .description('Max length of text content'),
5148
- textType: Joi.string()
5149
- .lowercase()
5150
- .valid('html', 'plain', '*')
5151
- .default('*')
5152
- .example('*')
5153
- .description('Which text content to return, use * for all. By default all contents are returned.'),
5154
- documentStore: documentStoreSchema.default(false)
5155
- }),
5156
-
5157
- params: Joi.object({
5158
- account: accountIdSchema.required(),
5159
- text: Joi.string()
5160
- .base64({ paddingRequired: false, urlSafe: true })
5161
- .max(10 * 1024)
5162
- .required()
5163
- .example('AAAAAQAACnAcdfaaN')
5164
- .description('Message text ID')
5165
- }).label('Text')
3132
+ path: Joi.string().required().example('Folder Name').description('Mailbox folder path to modify').label('ExistingMailboxPath'),
3133
+ newPath: Joi.array()
3134
+ .items(Joi.string().max(256))
3135
+ .single()
3136
+ .example(['Parent folder', 'Subfolder'])
3137
+ .description('New mailbox path as an array or a string. If account is namespaced then namespace prefix is added by default. Optional.')
3138
+ .label('TargetMailboxPath'),
3139
+ subscribed: Joi.boolean()
3140
+ .example(true)
3141
+ .description('Change mailbox subscription status. Only applies to IMAP accounts, ignored for Gmail and Outlook.')
3142
+ .label('SubscriptionStatus')
3143
+ })
3144
+ .or('newPath', 'subscribed')
3145
+ .label('ModifyMailbox')
5166
3146
  },
5167
3147
 
5168
3148
  response: {
5169
3149
  schema: Joi.object({
5170
- plain: Joi.string().example('Hello world').description('Plaintext content'),
5171
- html: Joi.string().example('<p>Hello world</p>').description('HTML content'),
5172
- hasMore: Joi.boolean().example(false).description('Is the current text output capped or not')
5173
- }).label('TextResponse'),
3150
+ path: Joi.string().required().example('Mail').description('Mailbox folder path').label('ExistingMailboxPath'),
3151
+ newPath: Joi.string().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to mailbox if renamed').label('NewMailboxPath'),
3152
+ renamed: Joi.boolean().example(true).description('Was the mailbox renamed'),
3153
+ subscribed: Joi.boolean().example(true).description('Subscription status after modification')
3154
+ }).label('ModifyMailboxResponse'),
5174
3155
  failAction: 'log'
5175
3156
  }
5176
3157
  }
5177
3158
  });
5178
3159
 
5179
3160
  server.route({
5180
- method: 'GET',
5181
- path: '/v1/account/{account}/messages',
3161
+ method: 'DELETE',
3162
+ path: '/v1/account/{account}/mailbox',
5182
3163
 
5183
- async handler(request, h) {
3164
+ async handler(request) {
5184
3165
  let accountObject = new Account({
5185
3166
  redis,
5186
3167
  account: request.params.account,
5187
3168
  call,
5188
3169
  secret: await getSecret(),
5189
- esClient: await h.getESClient(request.logger),
5190
3170
  timeout: request.headers['x-ee-timeout']
5191
3171
  });
5192
3172
 
5193
3173
  try {
5194
- return await accountObject.listMessages(request.query);
3174
+ return await accountObject.deleteMailbox(request.query.path);
5195
3175
  } catch (err) {
5196
3176
  request.logger.error({ msg: 'API request failed', err });
5197
3177
  if (Boom.isBoom(err)) {
@@ -5204,116 +3184,11 @@ Include your token in requests using one of these methods:
5204
3184
  throw error;
5205
3185
  }
5206
3186
  },
5207
- options: {
5208
- description: 'List messages in a folder',
5209
- notes: 'Lists messages in a mailbox folder',
5210
- tags: ['api', 'Message'],
5211
-
5212
- auth: {
5213
- strategy: 'api-token',
5214
- mode: 'required'
5215
- },
5216
- cors: CORS_CONFIG,
5217
-
5218
- validate: {
5219
- options: {
5220
- stripUnknown: false,
5221
- abortEarly: false,
5222
- convert: true
5223
- },
5224
- failAction,
5225
-
5226
- params: Joi.object({
5227
- account: accountIdSchema.required().label('AccountId')
5228
- }),
5229
-
5230
- query: Joi.object({
5231
- path: Joi.string().required().example('INBOX').description(listMessageFolderPathDescription).label('SpecialPath'),
5232
-
5233
- cursor: Joi.string()
5234
- .trim()
5235
- .empty('')
5236
- .max(1024 * 1024)
5237
- .example('imap_kcQIji3UobDDTxc')
5238
- .description('Paging cursor from `nextPageCursor` or `prevPageCursor` value')
5239
- .label('PageCursor'),
5240
- page: Joi.number()
5241
- .integer()
5242
- .min(0)
5243
- .max(1024 * 1024)
5244
- .default(0)
5245
- .example(0)
5246
- .description(
5247
- 'Page number (zero-indexed, so use 0 for the first page). Only supported for IMAP accounts. Deprecated; use the paging cursor instead. If the page cursor value is provided, then the page number value is ignored.'
5248
- )
5249
- .label('PageNumber'),
5250
-
5251
- pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page').label('PageSize'),
5252
- documentStore: documentStoreSchema.default(false)
5253
- }).label('MessageQuery')
5254
- },
5255
-
5256
- response: {
5257
- schema: messageListSchema,
5258
- failAction: 'log'
5259
- }
5260
- }
5261
- });
5262
-
5263
- server.route({
5264
- method: 'POST',
5265
- path: '/v1/account/{account}/search',
5266
-
5267
- async handler(request, h) {
5268
- let accountObject = new Account({
5269
- redis,
5270
- account: request.params.account,
5271
- call,
5272
- secret: await getSecret(),
5273
- esClient: await h.getESClient(request.logger),
5274
- timeout: request.headers['x-ee-timeout']
5275
- });
5276
-
5277
- let extraValidationErrors = [];
5278
3187
 
5279
- if (request.query.documentStore) {
5280
- for (let key of ['seq', 'modseq']) {
5281
- if (request.payload.search && key in request.payload.search) {
5282
- extraValidationErrors.push({ message: 'Not allowed with documentStore', context: { key } });
5283
- }
5284
- }
5285
- } else {
5286
- for (let key of ['documentQuery']) {
5287
- if (key in request.payload) {
5288
- extraValidationErrors.push({ message: 'Not allowed without documentStore', context: { key } });
5289
- }
5290
- }
5291
- }
5292
-
5293
- if (extraValidationErrors.length) {
5294
- let error = new Error('Input validation failed');
5295
- error.details = extraValidationErrors;
5296
- return failAction(request, h, error);
5297
- }
5298
-
5299
- try {
5300
- return await accountObject.searchMessages(Object.assign(request.query, request.payload));
5301
- } catch (err) {
5302
- request.logger.error({ msg: 'API request failed', err });
5303
- if (Boom.isBoom(err)) {
5304
- throw err;
5305
- }
5306
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
5307
- if (err.code) {
5308
- error.output.payload.code = err.code;
5309
- }
5310
- throw error;
5311
- }
5312
- },
5313
3188
  options: {
5314
- description: 'Search for messages',
5315
- notes: 'Filter messages from a mailbox folder by search options. Search is performed against a specific folder and not for the entire account.',
5316
- tags: ['api', 'Message'],
3189
+ description: 'Delete mailbox',
3190
+ notes: 'Delete existing mailbox folder',
3191
+ tags: ['api', 'Mailbox'],
5317
3192
 
5318
3193
  plugins: {},
5319
3194
 
@@ -5336,200 +3211,15 @@ Include your token in requests using one of these methods:
5336
3211
  }),
5337
3212
 
5338
3213
  query: Joi.object({
5339
- path: Joi.string()
5340
- .when('documentStore', {
5341
- is: true,
5342
- then: Joi.optional(),
5343
- otherwise: Joi.required()
5344
- })
5345
- .example('INBOX')
5346
- .description(listMessageFolderPathDescription)
5347
- .label('Path'),
5348
-
5349
- cursor: Joi.string()
5350
- .trim()
5351
- .empty('')
5352
- .max(1024 * 1024)
5353
- .example('imap_kcQIji3UobDDTxc')
5354
- .description('Paging cursor from `nextPageCursor` or `prevPageCursor` value')
5355
- .label('PageCursor'),
5356
- page: Joi.number()
5357
- .integer()
5358
- .min(0)
5359
- .max(1024 * 1024)
5360
- .default(0)
5361
- .example(0)
5362
- .description(
5363
- 'Page number (zero-indexed, so use 0 for the first page). Only supported for IMAP accounts. Deprecated; use the paging cursor instead. If the page cursor value is provided, then the page number value is ignored.'
5364
- )
5365
- .label('PageNumber'),
5366
-
5367
- pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page'),
5368
-
5369
- useOutlookSearch: Joi.boolean()
5370
- .truthy('Y', 'true', '1')
5371
- .falsy('N', 'false', 0)
5372
- .description(
5373
- 'MS Graph only. If enabled, uses the $search parameter for MS Graph search queries instead of $filter. This allows searching the "to", "cc", "bcc", "larger", "smaller", "body", "before", "sentBefore", "since", and the "sentSince" fields. Note that $search returns up to 1,000 results, does not indicate the total number of matching results or pages, and returns results sorted by relevance rather than date.'
5374
- )
5375
- .label('useOutlookSearch')
5376
- .optional(),
5377
-
5378
- documentStore: documentStoreSchema.default(false).meta({ swaggerHidden: true }),
5379
- exposeQuery: Joi.boolean()
5380
- .truthy('Y', 'true', '1')
5381
- .falsy('N', 'false', 0)
5382
- .description('If enabled then returns the ElasticSearch query for debugging as part of the response')
5383
- .label('exposeQuery')
5384
- .when('documentStore', {
5385
- is: true,
5386
- then: Joi.optional(),
5387
- otherwise: Joi.forbidden()
5388
- })
5389
- .meta({ swaggerHidden: true })
5390
- }),
5391
-
5392
- payload: Joi.object({
5393
- search: searchSchema,
5394
- documentQuery: Joi.object()
5395
- .min(1)
5396
- .description('Document Store query. Only allowed with `documentStore`.')
5397
- .label('DocumentQuery')
5398
- .unknown()
5399
- .meta({ swaggerHidden: true })
5400
- })
5401
- .label('SearchQuery')
5402
- .example({
5403
- search: {
5404
- unseen: true,
5405
- flagged: true,
5406
- from: 'nyan.cat@example.com',
5407
- body: 'Hello world',
5408
- subject: 'Hello world',
5409
- sentBefore: '2024-08-09',
5410
- sentSince: '2022-08-09',
5411
- emailId: '1278455344230334865',
5412
- threadId: '1266894439832287888',
5413
- header: {
5414
- 'Message-ID': '<12345@example.com>'
5415
- },
5416
- gmailRaw: 'has:attachment in:unread'
5417
- }
5418
- })
5419
- },
5420
-
5421
- response: {
5422
- schema: messageListSchema,
5423
- failAction: 'log'
5424
- }
5425
- }
5426
- });
5427
-
5428
- server.route({
5429
- method: 'POST',
5430
- path: '/v1/unified/search',
5431
-
5432
- async handler(request, h) {
5433
- let accountObject = new Account({
5434
- redis,
5435
- call,
5436
- secret: await getSecret(),
5437
- esClient: await h.getESClient(request.logger),
5438
- timeout: request.headers['x-ee-timeout']
5439
- });
5440
-
5441
- let extraValidationErrors = [];
5442
-
5443
- for (let key of ['seq', 'modseq']) {
5444
- if (request.payload.search && key in request.payload.search) {
5445
- extraValidationErrors.push({ message: 'Not allowed with documentStore', context: { key } });
5446
- }
5447
- }
5448
-
5449
- if (extraValidationErrors.length) {
5450
- let error = new Error('Input validation failed');
5451
- error.details = extraValidationErrors;
5452
- return failAction(request, h, error);
5453
- }
5454
-
5455
- let documentStoreEnabled = await settings.get('documentStoreEnabled');
5456
- if (!documentStoreEnabled) {
5457
- let error = new Error('Document store not enabled');
5458
- error.details = extraValidationErrors;
5459
- return failAction(request, h, error);
5460
- }
5461
-
5462
- try {
5463
- return await accountObject.searchMessages(Object.assign({ documentStore: true }, request.query, request.payload), { unified: true });
5464
- } catch (err) {
5465
- request.logger.error({ msg: 'API request failed', err });
5466
- if (Boom.isBoom(err)) {
5467
- throw err;
5468
- }
5469
- let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
5470
- if (err.code) {
5471
- error.output.payload.code = err.code;
5472
- }
5473
- throw error;
5474
- }
5475
- },
5476
- options: {
5477
- description: 'Unified search for messages',
5478
- notes: 'Filter messages from the Document Store for multiple accounts or paths. Document Store must be enabled for the unified search to work.',
5479
- tags: ['Deprecated endpoints (Document Store)'],
5480
-
5481
- plugins: {},
5482
-
5483
- auth: {
5484
- strategy: 'api-token',
5485
- mode: 'required'
5486
- },
5487
- cors: CORS_CONFIG,
5488
-
5489
- validate: {
5490
- options: {
5491
- stripUnknown: false,
5492
- abortEarly: false,
5493
- convert: true
5494
- },
5495
- failAction,
5496
-
5497
- query: Joi.object({
5498
- page: Joi.number()
5499
- .integer()
5500
- .min(0)
5501
- .max(1024 * 1024)
5502
- .default(0)
5503
- .example(0)
5504
- .description('Page number (zero indexed, so use 0 for first page)'),
5505
- pageSize: Joi.number().integer().min(1).max(1000).default(20).example(20).description('How many entries per page'),
5506
- exposeQuery: Joi.boolean()
5507
- .truthy('Y', 'true', '1')
5508
- .falsy('N', 'false', 0)
5509
- .description('If enabled then returns the ElasticSearch query for debugging as part of the response')
5510
- .label('exposeQuery')
5511
- .optional()
5512
- .meta({ swaggerHidden: true })
5513
- }),
5514
-
5515
- payload: Joi.object({
5516
- accounts: Joi.array()
5517
- .items(Joi.string().empty('').trim().max(256).example('example'))
5518
- .single()
5519
- .description('Optional list of account ID values')
5520
- .label('UnifiedSearchAccounts'),
5521
- paths: Joi.array()
5522
- .items(Joi.string().optional().example('INBOX'))
5523
- .single()
5524
- .description('Optional list of mailbox folder paths or specialUse flags')
5525
- .label('UnifiedSearchPaths'),
5526
- search: searchSchema,
5527
- documentQuery: Joi.object().min(1).description('Document Store query').label('DocumentQuery').unknown().meta({ swaggerHidden: true })
5528
- }).label('UnifiedSearchQuery')
3214
+ path: Joi.string().required().example('My Outdated Mail').description('Mailbox folder path to delete').label('MailboxPath')
3215
+ }).label('DeleteMailbox')
5529
3216
  },
5530
3217
 
5531
3218
  response: {
5532
- schema: messageListSchema,
3219
+ schema: Joi.object({
3220
+ path: Joi.string().required().example('Kalender/S&APw-nnip&AOQ-evad').description('Full path to mailbox').label('MailboxPath'),
3221
+ deleted: Joi.boolean().example(true).description('Was the mailbox deleted')
3222
+ }).label('DeleteMailboxResponse'),
5533
3223
  failAction: 'log'
5534
3224
  }
5535
3225
  }
@@ -6885,6 +4575,28 @@ Include your token in requests using one of these methods:
6885
4575
  // setup "chat with email" routes
6886
4576
  await chatRoutes({ server, call, CORS_CONFIG });
6887
4577
 
4578
+ // setup account CRUD routes
4579
+ await accountRoutes({
4580
+ server,
4581
+ call,
4582
+ documentsQueue,
4583
+ oauth2Schema,
4584
+ imapSchema,
4585
+ smtpSchema,
4586
+ CORS_CONFIG,
4587
+ AccountTypeSchema
4588
+ });
4589
+
4590
+ // setup message routes
4591
+ await messageRoutes({
4592
+ server,
4593
+ call,
4594
+ CORS_CONFIG,
4595
+ MAX_ATTACHMENT_SIZE,
4596
+ MAX_BODY_SIZE,
4597
+ MAX_PAYLOAD_TIMEOUT
4598
+ });
4599
+
6888
4600
  server.route({
6889
4601
  method: 'GET',
6890
4602
  path: '/v1/webhookRoutes',
@@ -8996,7 +6708,7 @@ ${now}`,
8996
6708
  url: '/admin/config/webhooks',
8997
6709
  level: 'danger',
8998
6710
  icon: 'link',
8999
- message: 'Webhooks are failing, please review'
6711
+ message: 'Webhook delivery is failing'
9000
6712
  });
9001
6713
  }
9002
6714
 
@@ -9005,7 +6717,7 @@ ${now}`,
9005
6717
  url: '/admin/config/license',
9006
6718
  level: 'warning',
9007
6719
  icon: 'key',
9008
- message: 'License key is not registered'
6720
+ message: 'No license key registered'
9009
6721
  });
9010
6722
  }
9011
6723