emailengine-app 2.64.0 → 2.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,77 @@
1
+ {{#if activeOutlook}}
2
+ <div class="card border-left-info shadow mb-4">
3
+ <div class="card-body">
4
+ <div class="row no-gutters align-items-center">
5
+ <div class="col mr-2">
6
+ <strong>Delegated</strong> access means EmailEngine acts on behalf of a signed-in user. Each
7
+ mailbox owner must complete an interactive OAuth2 login. Use this when users manage their own
8
+ accounts or when you have access to the mailbox credentials.
9
+ {{#if providerData.tutorialUrl}}
10
+ Read about setting up {{providerData.comment}} OAuth2 project from <a
11
+ href="{{providerData.tutorialUrl}}" target="_blank" rel="noopener noreferrer"
12
+ referrerpolicy="no-referrer">here</a>.
13
+ {{/if}}
14
+ </div>
15
+ <div class="col-auto">
16
+ <i class="fas fa-info-circle fa-2x text-gray-300"></i>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ {{/if}}
22
+
23
+ {{#if activeGmailService}}
24
+ <div class="card border-left-info shadow mb-4">
25
+ <div class="card-body">
26
+ <div class="row no-gutters align-items-center">
27
+ <div class="col mr-2">
28
+ <strong>Service accounts</strong> allow EmailEngine to access Gmail mailboxes using a Google Cloud
29
+ service key, with no interactive user login required. The service account must have domain-wide
30
+ delegation enabled in Google Workspace. Use this for automated integrations where you cannot
31
+ perform interactive logins.
32
+ Accounts using service access can only be added via the
33
+ <a href="/admin/swagger#/Account/postV1Account">REST API</a>,
34
+ not through the hosted authentication form.
35
+ {{#if providerData.tutorialUrl}}
36
+ Read about setting up {{providerData.comment}} from <a
37
+ href="{{providerData.tutorialUrl}}" target="_blank" rel="noopener noreferrer"
38
+ referrerpolicy="no-referrer">here</a>.
39
+ {{/if}}
40
+ </div>
41
+ <div class="col-auto">
42
+ <i class="fas fa-info-circle fa-2x text-gray-300"></i>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ {{/if}}
48
+
49
+ {{#if activeOutlookService}}
50
+ <div class="card border-left-info shadow mb-4">
51
+ <div class="card-body">
52
+ <div class="row no-gutters align-items-center">
53
+ <div class="col mr-2">
54
+ <strong>Application</strong> access means EmailEngine authenticates as the app itself using
55
+ client credentials, with no interactive user login required. The Entra app registration must have
56
+ <strong>application permissions</strong> (not delegated) with admin consent. Use this for
57
+ service integrations, shared mailboxes, or when you cannot perform interactive logins.
58
+ Accounts using application access can only be added via the
59
+ <a href="/admin/swagger#/Account/postV1Account">REST API</a>,
60
+ not through the hosted authentication form.
61
+ {{#if providerData.tutorialUrl}}
62
+ Read about setting up {{providerData.comment}} from <a
63
+ href="{{providerData.tutorialUrl}}" target="_blank" rel="noopener noreferrer"
64
+ referrerpolicy="no-referrer">here</a>.
65
+ {{/if}}
66
+ </div>
67
+ <div class="col-auto">
68
+ <i class="fas fa-info-circle fa-2x text-gray-300"></i>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ {{/if}}
74
+
1
75
  <div class="card mb-4">
2
76
 
3
77
  <div class="card-body">
@@ -42,7 +116,7 @@
42
116
  <small class="form-text text-muted">Optional application description or a comment.</small>
43
117
  </div>
44
118
 
45
- {{#unless activeGmailService}}
119
+ {{#unless activeGmailService}}{{#unless activeOutlookService}}
46
120
  <div class="form-group">
47
121
 
48
122
  <label for="title">
@@ -63,7 +137,7 @@
63
137
  <small class="form-text text-muted">Optional display title next to the application button on the account
64
138
  type selection page.</small>
65
139
  </div>
66
- {{/unless}}
140
+ {{/unless}}{{/unless}}
67
141
 
68
142
 
69
143
  {{#if activeGmail}}
@@ -82,7 +156,7 @@
82
156
  </div>
83
157
  {{/if}}
84
158
 
85
- {{#unless activeGmailService}}
159
+ {{#unless activeGmailService}}{{#unless activeOutlookService}}
86
160
  <div class="form-group form-check">
87
161
 
88
162
  <input type="checkbox" class="form-check-input {{#if errors.enabled}}is-invalid{{/if}}" id="enabled"
@@ -94,7 +168,7 @@
94
168
  <small class="form-text text-muted">If enabled, then this OAuth2 app is shown as an
95
169
  account type option in the hosted authentication form.</small>
96
170
  </div>
97
- {{/unless}}
171
+ {{/unless}}{{/unless}}
98
172
 
99
173
  </div>
100
174
  </div>
@@ -175,6 +249,66 @@
175
249
  {{/if}}
176
250
  </div>
177
251
 
252
+ {{else if activeOutlookService}}
253
+
254
+ <div class="form-group">
255
+
256
+ <label for="clientId">
257
+ Azure Application Id
258
+ </label>
259
+ <input type="text" class="form-control {{#if errors.clientId}}is-invalid{{/if}}" id="clientId"
260
+ name="clientId" value="{{values.clientId}}" placeholder="Enter application (client) ID&mldr;" required />
261
+ {{#if errors.clientId}}
262
+ <span class="invalid-feedback">{{errors.clientId}}</span>
263
+ {{/if}}
264
+ <small class="form-text text-muted">The Application (client) ID from your Entra app registration.</small>
265
+ </div>
266
+
267
+ <div class="form-group">
268
+
269
+ <label for="clientSecret">Client Secret</label>
270
+
271
+ <input type="text" class="form-control {{#if errors.clientSecret}}is-invalid{{/if}}" id="clientSecret"
272
+ name="clientSecret" value="{{values.clientSecret}}" {{#if hasClientSecret}}
273
+ placeholder="Client secret is set but not shown&mldr;" {{else}} placeholder="Enter client secret&mldr;"
274
+ {{/if}} {{#unless hasClientSecret}}required{{/unless}} />
275
+ {{#if errors.clientSecret}}
276
+ <span class="invalid-feedback">{{errors.clientSecret}}</span>
277
+ {{/if}}
278
+ <small class="form-text text-muted">Client secret value from Certificates &amp; secrets.</small>
279
+ </div>
280
+
281
+ <div class="form-group">
282
+ <label for="cloud">Azure cloud environment</label>
283
+ <select class="custom-select custom-select-sm {{#if errors.cloud}}is-invalid{{/if}}" id="cloud" name="cloud"
284
+ required>
285
+
286
+ {{#each azureClouds}}
287
+ <option value="{{id}}" {{#if selected}}selected{{/if}}>{{name}}{{#if
288
+ description}} &mdash;
289
+ {{description}}{{/if}}</option>
290
+ {{/each}}
291
+ </select>
292
+ {{#if errors.cloud}}
293
+ <span class="invalid-feedback">{{errors.cloud}}</span>
294
+ {{/if}}
295
+ <small class="form-text text-muted">Accounts hosted in different cloud environments use different OAuth2
296
+ endpoints.</small>
297
+ </div>
298
+
299
+ <div class="form-group">
300
+ <label for="authority">Directory (tenant) ID</label>
301
+
302
+ <input type="text" class="form-control {{#if errors.authority}}is-invalid{{/if}}" id="authority"
303
+ name="authority" value="{{values.authority}}"
304
+ placeholder="Enter tenant ID, eg. &quot;f8cdef31-a31e-4b4a-93e4-5f571e91255a&quot;" required />
305
+ {{#if errors.authority}}
306
+ <span class="invalid-feedback">{{errors.authority}}</span>
307
+ {{/if}}
308
+ <small class="form-text text-muted">The Directory (tenant) ID from your Entra app registration. Client
309
+ credentials flow requires a specific tenant ID.</small>
310
+ </div>
311
+
178
312
  {{else}}
179
313
 
180
314
  {{#if activeGmail}}
@@ -702,6 +836,47 @@
702
836
 
703
837
  {{/if}}
704
838
 
839
+ {{#if activeOutlookService}}
840
+
841
+ <div class="card mb-4">
842
+
843
+ <div class="card-header py-3">
844
+ <h6 class="m-0 font-weight-bold text-primary">Connection type</h6>
845
+ </div>
846
+
847
+ <div class="card-body">
848
+ <input type="hidden" name="baseScopes" value="api" />
849
+ <p><strong>MS Graph API</strong> &mdash; This app uses Microsoft Graph API with application permissions
850
+ (client credentials). Requires <code>Mail.ReadWrite</code>, <code>Mail.Send</code>, and
851
+ <code>User.Read.All</code> application permissions with admin consent in Entra.</p>
852
+ </div>
853
+
854
+ <div class="card-footer">
855
+
856
+ <p>Microsoft Graph delivers change notifications to EmailEngine at these two endpoints:</p>
857
+
858
+ <pre><code>{{mainServiceUrl}}/oauth/msg/lifecycle
859
+ {{mainServiceUrl}}/oauth/msg/notification</code></pre>
860
+
861
+ <p>
862
+ Both endpoints must be publicly reachable for anonymous HTTPS
863
+ <strong>POST</strong> requests. When a notification arrives, EmailEngine
864
+ immediately forwards the event to your application through its own webhooks.
865
+ </p>
866
+
867
+ <p>
868
+ If your EmailEngine instance cannot be exposed directly, configure a public
869
+ proxy domain in <span class="text-muted code-link"><a href="/admin/swagger#/Settings/postV1Settings"
870
+ target="_blank" rel="noopener noreferrer">notificationBaseUrl</a></span>. Microsoft Graph will then
871
+ post to <code>https://&lt;proxy-domain&gt;/oauth/msg/...</code> instead of
872
+ <code>{{mainServiceUrl}}</code>.
873
+ </p>
874
+
875
+ </div>
876
+ </div>
877
+
878
+ {{/if}}
879
+
705
880
  {{#if activeGmail}}
706
881
  <div class="card mb-4 {{#unless baseScopesApi}}d-none{{/unless}}" id="account-type-card-gmail">
707
882
  <div class="card-header py-3">
@@ -119,6 +119,16 @@
119
119
  </div>
120
120
  {{/if}}
121
121
 
122
+ {{#if activeOutlookService}}
123
+ <div>Your Entra app registration <strong>must</strong> have the following <strong>application permissions</strong> (not
124
+ delegated) with admin consent:</div>
125
+ <ul>
126
+ <li><code>"Mail.ReadWrite"</code></li>
127
+ <li><code>"Mail.Send"</code></li>
128
+ <li><code>"User.Read.All"</code></li>
129
+ </ul>
130
+ {{/if}}
131
+
122
132
  {{#if activeMailRu}}
123
133
  <div>Your OAuth2 project <strong>must</strong> have the following scopes enabled:</div>
124
134
  <ul>
package/workers/export.js CHANGED
@@ -755,7 +755,7 @@ const exportWorker = new Worker(
755
755
  throw new Error('Export not found');
756
756
  }
757
757
 
758
- await Export.update(account, exportId, { status: 'processing', phase: 'indexing' });
758
+ await Export.startProcessing(account, exportId);
759
759
  await indexMessages(job, exportData);
760
760
  await Export.update(account, exportId, { phase: 'exporting' });
761
761
 
@@ -788,7 +788,11 @@ const exportWorker = new Worker(
788
788
  await fs.promises.unlink(exportData.filePath).catch(() => {});
789
789
  }
790
790
 
791
- await Export.fail(account, exportId, err.message);
791
+ if (err.code === 'ExportCancelled') {
792
+ await Export.deleteFully(account, exportId);
793
+ } else {
794
+ await Export.fail(account, exportId, err.message);
795
+ }
792
796
 
793
797
  if (err.code !== 'AccountDeleted' && err.code !== 'AccountNotFound' && err.code !== 'ExportCancelled') {
794
798
  await notify(account, EXPORT_FAILED_NOTIFY, {
package/workers/imap.js CHANGED
@@ -37,7 +37,7 @@ const { GmailClient } = require('../lib/email-client/gmail-client');
37
37
  const { OutlookClient } = require('../lib/email-client/outlook-client');
38
38
  const { BaseClient } = require('../lib/email-client/base-client');
39
39
  const { Account } = require('../lib/account');
40
- const { oauth2Apps } = require('../lib/oauth2-apps');
40
+ const { oauth2Apps, isApiBasedApp } = require('../lib/oauth2-apps');
41
41
  const { redis, notifyQueue, submitQueue, documentsQueue, getFlowProducer } = require('../lib/db');
42
42
  const { MessagePortWritable } = require('../lib/message-port-stream');
43
43
  const { getESClient } = require('../lib/document-store');
@@ -209,7 +209,7 @@ class ConnectionHandler {
209
209
  oauth2App = await oauth2Apps.get(accountData.oauth2.provider);
210
210
  }
211
211
 
212
- if (oauth2App && oauth2App.baseScopes === 'api') {
212
+ if (isApiBasedApp(oauth2App)) {
213
213
  // Use API instead of IMAP
214
214
 
215
215
  switch (oauth2App.provider) {
@@ -232,6 +232,7 @@ class ConnectionHandler {
232
232
  accountObject.logger = accountObject.connection.logger;
233
233
  break;
234
234
 
235
+ case 'outlookService':
235
236
  case 'outlook':
236
237
  accountObject.connection = new OutlookClient(account, {
237
238
  runIndex,