emailengine-app 2.68.1 → 2.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/deploy.yml +8 -3
- package/.github/workflows/release.yaml +6 -0
- package/CHANGELOG.md +59 -0
- package/Gruntfile.js +3 -1
- package/config/default.toml +2 -0
- package/data/google-crawlers.json +7 -1
- package/getswagger.sh +40 -4
- package/gettext-extract.js +163 -0
- package/lib/account.js +135 -72
- package/lib/api-routes/account-routes.js +684 -106
- package/lib/api-routes/blocklist-routes.js +344 -0
- package/lib/api-routes/chat-routes.js +32 -14
- package/lib/api-routes/delivery-test-routes.js +346 -0
- package/lib/api-routes/export-routes.js +28 -14
- package/lib/api-routes/gateway-routes.js +427 -0
- package/lib/api-routes/license-routes.js +156 -0
- package/lib/api-routes/mailbox-routes.js +344 -0
- package/lib/api-routes/message-routes.js +221 -187
- package/lib/api-routes/oauth2-app-routes.js +697 -0
- package/lib/api-routes/outbox-routes.js +185 -0
- package/lib/api-routes/pubsub-routes.js +102 -0
- package/lib/api-routes/route-helpers.js +58 -0
- package/lib/api-routes/settings-routes.js +357 -0
- package/lib/api-routes/stats-routes.js +111 -0
- package/lib/api-routes/submit-routes.js +461 -0
- package/lib/api-routes/template-routes.js +60 -75
- package/lib/api-routes/token-routes.js +297 -0
- package/lib/api-routes/webhook-route-routes.js +181 -0
- package/lib/autodetect-imap-settings.js +0 -2
- package/lib/consts.js +5 -0
- package/lib/email-client/base-client.js +28 -6
- package/lib/email-client/gmail-client.js +133 -112
- package/lib/email-client/imap/mailbox.js +34 -11
- package/lib/email-client/imap/subconnection.js +20 -13
- package/lib/email-client/imap/sync-operations.js +131 -3
- package/lib/email-client/imap-client.js +152 -75
- package/lib/email-client/notification-handler.js +1 -4
- package/lib/email-client/outlook-client.js +134 -75
- package/lib/export.js +97 -20
- package/lib/feature-flags.js +2 -2
- package/lib/gateway.js +4 -9
- package/lib/get-raw-email.js +5 -5
- package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
- package/lib/imapproxy/imap-core/lib/imap-command.js +6 -1
- package/lib/imapproxy/imap-core/lib/imap-connection.js +106 -24
- package/lib/imapproxy/imap-core/lib/imap-server.js +24 -0
- package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
- package/lib/logger.js +24 -21
- package/lib/message-port-stream.js +113 -16
- package/lib/metrics-collector.js +0 -2
- package/lib/oauth2-apps.js +13 -4
- package/lib/outbox.js +24 -40
- package/lib/redis-operations.js +1 -1
- package/lib/reject-worker-calls.js +42 -0
- package/lib/routes-ui.js +37 -8778
- package/lib/schemas.js +429 -84
- package/lib/sentry.js +139 -0
- package/lib/settings.js +9 -3
- package/lib/stream-encrypt.js +1 -1
- package/lib/templates.js +1 -1
- package/lib/tokens.js +5 -3
- package/lib/tools.js +70 -4
- package/lib/ui-routes/account-routes.js +45 -212
- package/lib/ui-routes/admin-config-routes.js +928 -489
- package/lib/ui-routes/admin-entities-routes.js +1 -0
- package/lib/ui-routes/auth-routes.js +1339 -0
- package/lib/ui-routes/dashboard-routes.js +188 -0
- package/lib/ui-routes/document-store-routes.js +800 -0
- package/lib/ui-routes/export-routes.js +217 -0
- package/lib/ui-routes/internals-routes.js +354 -0
- package/lib/ui-routes/network-config-routes.js +759 -0
- package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +369 -91
- package/lib/ui-routes/route-helpers.js +314 -0
- package/lib/ui-routes/smtp-test-routes.js +236 -0
- package/lib/ui-routes/unsubscribe-routes.js +232 -0
- package/lib/webhook-request.js +36 -0
- package/lib/webhooks.js +8 -4
- package/package.json +13 -12
- package/sbom.json +1 -1
- package/server.js +222 -39
- package/static/licenses.html +160 -300
- package/translations/messages.pot +112 -132
- package/update-info.sh +19 -1
- package/views/config/logging.hbs +48 -0
- package/views/dashboard.hbs +7 -26
- package/views/internals/index.hbs +15 -0
- package/views/tokens/index.hbs +9 -0
- package/workers/api.js +200 -4424
- package/workers/documents.js +2 -22
- package/workers/export.js +103 -104
- package/workers/imap-proxy.js +3 -23
- package/workers/imap.js +32 -36
- package/workers/smtp.js +2 -22
- package/workers/submit.js +26 -35
- package/workers/webhooks.js +9 -43
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Joi = require('joi');
|
|
4
|
+
const { redis } = require('../db');
|
|
5
|
+
const { failAction, getStats } = require('../tools');
|
|
6
|
+
const { MAX_DAYS_STATS } = require('../consts');
|
|
7
|
+
const { errorResponses } = require('../schemas');
|
|
8
|
+
const packageData = require('../../package.json');
|
|
9
|
+
|
|
10
|
+
async function init(args) {
|
|
11
|
+
const { server, call, CORS_CONFIG } = args;
|
|
12
|
+
|
|
13
|
+
server.route({
|
|
14
|
+
method: 'GET',
|
|
15
|
+
path: '/v1/stats',
|
|
16
|
+
|
|
17
|
+
async handler(request) {
|
|
18
|
+
return await getStats(redis, call, request.query.seconds);
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
options: {
|
|
22
|
+
description: 'Return server stats',
|
|
23
|
+
tags: ['api', 'Stats'],
|
|
24
|
+
|
|
25
|
+
plugins: {
|
|
26
|
+
'hapi-swagger': {
|
|
27
|
+
responses: errorResponses(400, 401, 403, 429, 500)
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
auth: {
|
|
32
|
+
strategy: 'api-token',
|
|
33
|
+
mode: 'required'
|
|
34
|
+
},
|
|
35
|
+
cors: CORS_CONFIG,
|
|
36
|
+
|
|
37
|
+
validate: {
|
|
38
|
+
options: {
|
|
39
|
+
stripUnknown: false,
|
|
40
|
+
abortEarly: false,
|
|
41
|
+
convert: true
|
|
42
|
+
},
|
|
43
|
+
failAction,
|
|
44
|
+
|
|
45
|
+
query: Joi.object({
|
|
46
|
+
seconds: Joi.number()
|
|
47
|
+
.integer()
|
|
48
|
+
.empty('')
|
|
49
|
+
.min(0)
|
|
50
|
+
.max(MAX_DAYS_STATS * 24 * 3600)
|
|
51
|
+
.default(3600)
|
|
52
|
+
.example(3600)
|
|
53
|
+
.description('Duration for counters')
|
|
54
|
+
.label('CounterSeconds')
|
|
55
|
+
}).label('ServerStats')
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
response: {
|
|
59
|
+
schema: Joi.object({
|
|
60
|
+
version: Joi.string().example(packageData.version).description('EmailEngine version number'),
|
|
61
|
+
license: Joi.string().example(packageData.license).description('EmailEngine license'),
|
|
62
|
+
accounts: Joi.number().integer().example(26).description('Number of registered accounts'),
|
|
63
|
+
node: Joi.string().example('16.10.0').description('Node.js Version'),
|
|
64
|
+
redis: Joi.string()
|
|
65
|
+
.example('6.2.4')
|
|
66
|
+
.description('Redis version. Can include the server software in parentheses, or an error message if the version lookup failed'),
|
|
67
|
+
redisSoftware: Joi.string().example('redis').description('Redis-compatible server software name'),
|
|
68
|
+
redisCluster: Joi.boolean().example(false).description('Whether Redis is running in cluster mode'),
|
|
69
|
+
redisWarnings: Joi.array()
|
|
70
|
+
.items(
|
|
71
|
+
Joi.object({
|
|
72
|
+
key: Joi.string().example('maxmemory-policy').description('Warning identifier'),
|
|
73
|
+
color: Joi.string().example('warning').description('Severity indicator'),
|
|
74
|
+
title: Joi.string().example('Unsafe Redis eviction policy').description('Warning title'),
|
|
75
|
+
details: Joi.array().items(Joi.string()).description('Warning details')
|
|
76
|
+
})
|
|
77
|
+
.unknown()
|
|
78
|
+
.label('RedisWarningEntry')
|
|
79
|
+
)
|
|
80
|
+
.description('Warnings about the Redis configuration')
|
|
81
|
+
.label('RedisWarnings'),
|
|
82
|
+
redisPing: Joi.number().description('Redis latency in milliseconds'),
|
|
83
|
+
imapflow: Joi.string().example('1.0.188').description('ImapFlow version'),
|
|
84
|
+
bullmq: Joi.string().example('5.0.0').description('BullMQ version'),
|
|
85
|
+
arch: Joi.string().example('arm64').description('CPU architecture of the host'),
|
|
86
|
+
build: Joi.object().unknown().description('Build information for the running EmailEngine instance').label('BuildInfo'),
|
|
87
|
+
queues: Joi.object().unknown().description('Job counters for the notify, submit, and documents queues').label('QueueStats'),
|
|
88
|
+
connections: Joi.object({
|
|
89
|
+
init: Joi.number().integer().example(2).description('Accounts not yet initialized'),
|
|
90
|
+
connected: Joi.number().integer().example(8).description('Successfully connected accounts'),
|
|
91
|
+
connecting: Joi.number().integer().example(7).description('Connection is being established'),
|
|
92
|
+
syncing: Joi.number().integer().example(1).description('Accounts that are currently syncing'),
|
|
93
|
+
authenticationError: Joi.number().integer().example(3).description('Authentication failed'),
|
|
94
|
+
connectError: Joi.number().integer().example(5).description('Connection failed due to technical error'),
|
|
95
|
+
unset: Joi.number().integer().example(0).description('Accounts without valid IMAP settings'),
|
|
96
|
+
disconnected: Joi.number().integer().example(1).description('IMAP connection was closed'),
|
|
97
|
+
paused: Joi.number().integer().example(0).description('Accounts that are paused'),
|
|
98
|
+
unassigned: Joi.number().integer().example(0).description('Accounts not assigned to any worker')
|
|
99
|
+
})
|
|
100
|
+
.unknown()
|
|
101
|
+
.description('Counts of accounts in different connection states')
|
|
102
|
+
.label('ConnectionsStats'),
|
|
103
|
+
counters: Joi.object().label('CounterStats').unknown()
|
|
104
|
+
}).label('StatsResponse'),
|
|
105
|
+
failAction: 'log'
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = init;
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Boom = require('@hapi/boom');
|
|
4
|
+
const Joi = require('joi');
|
|
5
|
+
const { redis } = require('../db');
|
|
6
|
+
const { Account } = require('../account');
|
|
7
|
+
const getSecret = require('../get-secret');
|
|
8
|
+
const { failAction } = require('../tools');
|
|
9
|
+
const {
|
|
10
|
+
messageReferenceSchema,
|
|
11
|
+
fromAddressSchema,
|
|
12
|
+
addressSchema,
|
|
13
|
+
idempotencyKeySchema,
|
|
14
|
+
headerTimeoutSchema,
|
|
15
|
+
accountIdSchema,
|
|
16
|
+
templateSchemas,
|
|
17
|
+
settingsSchema,
|
|
18
|
+
ipSchema,
|
|
19
|
+
threadIdSchema,
|
|
20
|
+
errorResponses
|
|
21
|
+
} = require('../schemas');
|
|
22
|
+
|
|
23
|
+
async function init(args) {
|
|
24
|
+
const { server, call, CORS_CONFIG, MAX_ATTACHMENT_SIZE, MAX_BODY_SIZE, MAX_PAYLOAD_TIMEOUT } = args;
|
|
25
|
+
|
|
26
|
+
server.route({
|
|
27
|
+
method: 'POST',
|
|
28
|
+
path: '/v1/account/{account}/submit',
|
|
29
|
+
|
|
30
|
+
async handler(request) {
|
|
31
|
+
let accountObject = new Account({
|
|
32
|
+
redis,
|
|
33
|
+
account: request.params.account,
|
|
34
|
+
call,
|
|
35
|
+
secret: await getSecret(),
|
|
36
|
+
timeout: request.headers['x-ee-timeout']
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
return await accountObject.queueMessage(request.payload, {
|
|
41
|
+
source: 'api',
|
|
42
|
+
idempotencyKey: request.headers['idempotency-key'],
|
|
43
|
+
useStructuredFormat: request.query.useStructuredFormat
|
|
44
|
+
});
|
|
45
|
+
} catch (err) {
|
|
46
|
+
request.logger.error({ msg: 'API request failed', err });
|
|
47
|
+
if (Boom.isBoom(err)) {
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
let error = Boom.boomify(err, { statusCode: err.statusCode || 500 });
|
|
51
|
+
if (err.code) {
|
|
52
|
+
error.output.payload.code = err.code;
|
|
53
|
+
}
|
|
54
|
+
if (err.info) {
|
|
55
|
+
error.output.payload.info = err.info;
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
options: {
|
|
61
|
+
payload: {
|
|
62
|
+
maxBytes: MAX_BODY_SIZE,
|
|
63
|
+
timeout: MAX_PAYLOAD_TIMEOUT
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
description: 'Submit message for delivery',
|
|
67
|
+
notes: 'Submit message for delivery. If reference message ID is provided then EmailEngine adds all headers and flags required for a reply/forward automatically.',
|
|
68
|
+
tags: ['api', 'Submit'],
|
|
69
|
+
|
|
70
|
+
plugins: {
|
|
71
|
+
'hapi-swagger': {
|
|
72
|
+
responses: errorResponses(400, 401, 403, 404, 429, 500, 503)
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
auth: {
|
|
77
|
+
strategy: 'api-token',
|
|
78
|
+
mode: 'required'
|
|
79
|
+
},
|
|
80
|
+
cors: CORS_CONFIG,
|
|
81
|
+
|
|
82
|
+
validate: {
|
|
83
|
+
options: {
|
|
84
|
+
stripUnknown: false,
|
|
85
|
+
abortEarly: false,
|
|
86
|
+
convert: true
|
|
87
|
+
},
|
|
88
|
+
failAction,
|
|
89
|
+
|
|
90
|
+
params: Joi.object({
|
|
91
|
+
account: accountIdSchema.required()
|
|
92
|
+
}),
|
|
93
|
+
|
|
94
|
+
query: Joi.object({
|
|
95
|
+
documentStore: Joi.boolean()
|
|
96
|
+
.truthy('Y', 'true', '1')
|
|
97
|
+
.falsy('N', 'false', 0)
|
|
98
|
+
.default(false)
|
|
99
|
+
.description('If enabled then fetch email used as a reference template from the Document Store'),
|
|
100
|
+
useStructuredFormat: Joi.boolean()
|
|
101
|
+
.truthy('Y', 'true', '1')
|
|
102
|
+
.falsy('N', 'false', 0)
|
|
103
|
+
.default(false)
|
|
104
|
+
.description(
|
|
105
|
+
'For MS Graph accounts: If true, uses structured JSON format (respects from field for shared mailboxes, breaks calendar invites and special MIME types). If false, sends as raw MIME (preserves calendar invites, ignores from field). Default is false (raw MIME).'
|
|
106
|
+
)
|
|
107
|
+
}).label('SubmitQuery'),
|
|
108
|
+
|
|
109
|
+
headers: Joi.object({
|
|
110
|
+
'x-ee-timeout': headerTimeoutSchema,
|
|
111
|
+
'idempotency-key': idempotencyKeySchema
|
|
112
|
+
}).unknown(),
|
|
113
|
+
|
|
114
|
+
payload: Joi.object({
|
|
115
|
+
reference: messageReferenceSchema,
|
|
116
|
+
|
|
117
|
+
envelope: Joi.object({
|
|
118
|
+
from: Joi.string().email().allow('').example('sender@example.com'),
|
|
119
|
+
to: Joi.array().items(Joi.string().email().required().example('recipient@example.com')).single().label('SmtpEnvelopeTo')
|
|
120
|
+
})
|
|
121
|
+
.description(
|
|
122
|
+
"An optional object specifying the SMTP envelope used during email transmission. If not provided, the envelope is automatically derived from the email's message headers. This is useful when you need the envelope addresses to differ from those in the email headers."
|
|
123
|
+
)
|
|
124
|
+
.label('SMTPEnvelope')
|
|
125
|
+
.when('mailMerge', {
|
|
126
|
+
is: Joi.exist().not(false, null),
|
|
127
|
+
then: Joi.forbidden('y')
|
|
128
|
+
}),
|
|
129
|
+
|
|
130
|
+
raw: Joi.string()
|
|
131
|
+
.base64()
|
|
132
|
+
.max(MAX_ATTACHMENT_SIZE)
|
|
133
|
+
.example('TUlNRS1WZXJzaW9uOiAxLjANClN1YmplY3Q6IGhlbGxvIHdvcmxkDQoNCkhlbGxvIQ0K')
|
|
134
|
+
.description(
|
|
135
|
+
'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.'
|
|
136
|
+
)
|
|
137
|
+
.label('RFC822Raw')
|
|
138
|
+
.when('mailMerge', {
|
|
139
|
+
is: Joi.exist().not(false, null),
|
|
140
|
+
then: Joi.forbidden('y')
|
|
141
|
+
}),
|
|
142
|
+
|
|
143
|
+
from: fromAddressSchema,
|
|
144
|
+
|
|
145
|
+
replyTo: Joi.array()
|
|
146
|
+
.items(addressSchema.label('ReplyToAddress'))
|
|
147
|
+
.single()
|
|
148
|
+
.example([{ name: 'From Me', address: 'sender@example.com' }])
|
|
149
|
+
.description('List of Reply-To addresses')
|
|
150
|
+
.label('ReplyTo'),
|
|
151
|
+
|
|
152
|
+
to: Joi.array()
|
|
153
|
+
.items(addressSchema.label('ToAddress'))
|
|
154
|
+
.single()
|
|
155
|
+
.example([{ address: 'recipient@example.com' }])
|
|
156
|
+
.description('List of recipient addresses')
|
|
157
|
+
.label('ToAddressList')
|
|
158
|
+
.when('mailMerge', {
|
|
159
|
+
is: Joi.exist().not(false, null),
|
|
160
|
+
then: Joi.forbidden('y')
|
|
161
|
+
}),
|
|
162
|
+
|
|
163
|
+
cc: Joi.array()
|
|
164
|
+
.items(addressSchema.label('CcAddress'))
|
|
165
|
+
.single()
|
|
166
|
+
.description('List of CC addresses')
|
|
167
|
+
.label('CcAddressList')
|
|
168
|
+
.when('mailMerge', {
|
|
169
|
+
is: Joi.exist().not(false, null),
|
|
170
|
+
then: Joi.forbidden('y')
|
|
171
|
+
}),
|
|
172
|
+
|
|
173
|
+
bcc: Joi.array()
|
|
174
|
+
.items(addressSchema.label('BccAddress'))
|
|
175
|
+
.single()
|
|
176
|
+
.description('List of BCC addresses')
|
|
177
|
+
.label('BccAddressList')
|
|
178
|
+
.when('mailMerge', {
|
|
179
|
+
is: Joi.exist().not(false, null),
|
|
180
|
+
then: Joi.forbidden('y')
|
|
181
|
+
}),
|
|
182
|
+
|
|
183
|
+
subject: templateSchemas.subject,
|
|
184
|
+
text: templateSchemas.text,
|
|
185
|
+
html: templateSchemas.html,
|
|
186
|
+
previewText: templateSchemas.previewText,
|
|
187
|
+
|
|
188
|
+
template: Joi.string().max(256).example('example').description('Stored template ID to load the email content from'),
|
|
189
|
+
|
|
190
|
+
render: Joi.object({
|
|
191
|
+
format: Joi.string()
|
|
192
|
+
.valid('html', 'markdown')
|
|
193
|
+
.default('html')
|
|
194
|
+
.description('Markup language for HTML ("html" or "markdown")')
|
|
195
|
+
.label('RenderFormat'),
|
|
196
|
+
params: Joi.object().label('RenderValues').description('An object of variables for the template renderer')
|
|
197
|
+
})
|
|
198
|
+
.allow(false)
|
|
199
|
+
.description('Template rendering options')
|
|
200
|
+
.when('mailMerge', {
|
|
201
|
+
is: Joi.exist().not(false, null),
|
|
202
|
+
then: Joi.forbidden('y')
|
|
203
|
+
})
|
|
204
|
+
.label('TemplateRender'),
|
|
205
|
+
|
|
206
|
+
mailMerge: Joi.array()
|
|
207
|
+
.items(
|
|
208
|
+
Joi.object({
|
|
209
|
+
to: addressSchema.label('ToAddress').required(),
|
|
210
|
+
messageId: Joi.string().max(996).example('<test123@example.com>').description('Message ID'),
|
|
211
|
+
params: Joi.object().label('RenderValues').description('An object of variables for the template renderer'),
|
|
212
|
+
sendAt: Joi.date()
|
|
213
|
+
.iso()
|
|
214
|
+
.example('2021-07-08T07:06:34.336Z')
|
|
215
|
+
.description('Send message at specified time. Overrides message level `sendAt` value.')
|
|
216
|
+
}).label('MailMergeListEntry')
|
|
217
|
+
)
|
|
218
|
+
.min(1)
|
|
219
|
+
.description(
|
|
220
|
+
'Mail merge options. A separate email is generated for each recipient. Using mail merge disables `messageId`, `envelope`, `to`, `cc`, `bcc`, `render` keys for the message root.'
|
|
221
|
+
)
|
|
222
|
+
.label('MailMergeList'),
|
|
223
|
+
|
|
224
|
+
attachments: Joi.array()
|
|
225
|
+
.items(
|
|
226
|
+
Joi.object({
|
|
227
|
+
filename: Joi.string().max(256).example('transparent.gif'),
|
|
228
|
+
content: Joi.string()
|
|
229
|
+
.base64()
|
|
230
|
+
.max(MAX_ATTACHMENT_SIZE)
|
|
231
|
+
.required()
|
|
232
|
+
.example('R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=')
|
|
233
|
+
.description('Base64 formatted attachment file')
|
|
234
|
+
.when('reference', {
|
|
235
|
+
is: Joi.exist().not(false, null),
|
|
236
|
+
then: Joi.forbidden(),
|
|
237
|
+
otherwise: Joi.required()
|
|
238
|
+
}),
|
|
239
|
+
|
|
240
|
+
contentType: Joi.string().lowercase().max(256).example('image/gif'),
|
|
241
|
+
contentDisposition: Joi.string().lowercase().valid('inline', 'attachment').label('AttachmentContentDisposition'),
|
|
242
|
+
cid: Joi.string().max(256).example('unique-image-id@localhost').description('Content-ID value for embedded images'),
|
|
243
|
+
encoding: Joi.string().valid('base64').default('base64').label('AttachmentEncoding'),
|
|
244
|
+
|
|
245
|
+
reference: Joi.string()
|
|
246
|
+
.base64({ paddingRequired: false, urlSafe: true })
|
|
247
|
+
.max(256)
|
|
248
|
+
.allow(false, null)
|
|
249
|
+
.example('AAAAAQAACnAcde')
|
|
250
|
+
.description(
|
|
251
|
+
'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.'
|
|
252
|
+
)
|
|
253
|
+
.label('AttachmentReference')
|
|
254
|
+
}).label('UploadAttachment')
|
|
255
|
+
)
|
|
256
|
+
.description('List of attachments')
|
|
257
|
+
.label('UploadAttachmentList'),
|
|
258
|
+
|
|
259
|
+
messageId: Joi.string().max(996).example('<test123@example.com>').description('Message ID'),
|
|
260
|
+
headers: Joi.object().label('CustomHeaders').description('Custom Headers').unknown().example({
|
|
261
|
+
'X-My-Custom-Header': 'Custom header value'
|
|
262
|
+
}),
|
|
263
|
+
|
|
264
|
+
trackingEnabled: Joi.boolean()
|
|
265
|
+
.example(false)
|
|
266
|
+
.description('Should EmailEngine track clicks and opens for this message')
|
|
267
|
+
.meta({ swaggerHidden: true }),
|
|
268
|
+
|
|
269
|
+
trackOpens: Joi.boolean().example(false).description('Should EmailEngine track opens for this message'),
|
|
270
|
+
trackClicks: Joi.boolean().example(false).description('Should EmailEngine track clicks for this message'),
|
|
271
|
+
|
|
272
|
+
copy: Joi.boolean()
|
|
273
|
+
.allow(null)
|
|
274
|
+
.example(null)
|
|
275
|
+
.description(
|
|
276
|
+
"If set then either copies the message to the Sent Mail folder or not. If not set then uses the account's default setting."
|
|
277
|
+
),
|
|
278
|
+
|
|
279
|
+
sentMailPath: Joi.string()
|
|
280
|
+
.empty('')
|
|
281
|
+
.max(1024)
|
|
282
|
+
.example('Sent Mail')
|
|
283
|
+
.description("Upload sent message to this folder. By default the account's Sent Mail folder is used."),
|
|
284
|
+
|
|
285
|
+
locale: Joi.string().empty('').max(100).example('fr').description('Optional locale').label('MessageLocale'),
|
|
286
|
+
tz: Joi.string().empty('').max(100).example('Europe/Tallinn').description('Optional timezone'),
|
|
287
|
+
|
|
288
|
+
sendAt: Joi.date().iso().example('2021-07-08T07:06:34.336Z').description('Send message at specified time'),
|
|
289
|
+
deliveryAttempts: Joi.number()
|
|
290
|
+
.integer()
|
|
291
|
+
.example(10)
|
|
292
|
+
.description('How many delivery attempts to make until message is considered as failed'),
|
|
293
|
+
gateway: Joi.string().max(256).example('example').description('Optional SMTP gateway ID for message routing').label('MessageGateway'),
|
|
294
|
+
|
|
295
|
+
listId: Joi.string()
|
|
296
|
+
.hostname()
|
|
297
|
+
.example('test-list')
|
|
298
|
+
.description(
|
|
299
|
+
'List ID for Mail Merge. Must use a subdomain name format. Lists are registered ad-hoc, so a new identifier defines a new list.'
|
|
300
|
+
)
|
|
301
|
+
.label('ListID')
|
|
302
|
+
.when('mailMerge', {
|
|
303
|
+
is: Joi.exist().not(false, null),
|
|
304
|
+
then: Joi.optional(),
|
|
305
|
+
otherwise: Joi.forbidden()
|
|
306
|
+
}),
|
|
307
|
+
|
|
308
|
+
dsn: Joi.object({
|
|
309
|
+
id: Joi.string().trim().empty('').max(256).description('The envelope identifier that would be included in the response (ENVID)'),
|
|
310
|
+
return: Joi.string()
|
|
311
|
+
.trim()
|
|
312
|
+
.empty('')
|
|
313
|
+
.valid('headers', 'full')
|
|
314
|
+
.required()
|
|
315
|
+
.description('Specifies if only headers or the entire body of the message should be included in the response (RET)')
|
|
316
|
+
.label('DsnReturn'),
|
|
317
|
+
notify: Joi.array()
|
|
318
|
+
.single()
|
|
319
|
+
.items(Joi.string().valid('never', 'success', 'failure', 'delay').label('NotifyEntry'))
|
|
320
|
+
.description('Defines the conditions under which a DSN response should be sent')
|
|
321
|
+
.label('DsnNotify'),
|
|
322
|
+
recipient: Joi.string().trim().empty('').email().description('The email address the DSN should be sent (ORCPT)')
|
|
323
|
+
})
|
|
324
|
+
.description('Request DSN notifications')
|
|
325
|
+
.label('DSN'),
|
|
326
|
+
|
|
327
|
+
baseUrl: Joi.string()
|
|
328
|
+
.trim()
|
|
329
|
+
.empty('')
|
|
330
|
+
.uri({
|
|
331
|
+
scheme: ['http', 'https'],
|
|
332
|
+
allowRelative: false
|
|
333
|
+
})
|
|
334
|
+
.example('https://customer123.myservice.com')
|
|
335
|
+
.description('Optional base URL for trackers. This URL must point to your EmailEngine instance.'),
|
|
336
|
+
|
|
337
|
+
proxy: settingsSchema.proxyUrl.description('Optional proxy URL to use when connecting to the SMTP server'),
|
|
338
|
+
localAddress: ipSchema.description('Optional local IP address to bind to when connecting to the SMTP server'),
|
|
339
|
+
|
|
340
|
+
dryRun: Joi.boolean()
|
|
341
|
+
.truthy('Y', 'true', '1')
|
|
342
|
+
.falsy('N', 'false', 0)
|
|
343
|
+
.default(false)
|
|
344
|
+
.description(
|
|
345
|
+
'If true, then EmailEngine does not send the email and returns an RFC822 formatted email file. Tracking information is not added to the email.'
|
|
346
|
+
)
|
|
347
|
+
.label('Preview')
|
|
348
|
+
})
|
|
349
|
+
.oxor('raw', 'html')
|
|
350
|
+
.oxor('raw', 'text')
|
|
351
|
+
.oxor('raw', 'text')
|
|
352
|
+
.oxor('raw', 'attachments')
|
|
353
|
+
.label('SubmitMessage')
|
|
354
|
+
.example({
|
|
355
|
+
to: [
|
|
356
|
+
{
|
|
357
|
+
name: 'Nyan Cat',
|
|
358
|
+
address: 'nyan.cat@example.com'
|
|
359
|
+
}
|
|
360
|
+
],
|
|
361
|
+
subject: 'What a wonderful message!',
|
|
362
|
+
text: 'Hello from myself!',
|
|
363
|
+
html: '<p>Hello from myself!</p>',
|
|
364
|
+
attachments: [
|
|
365
|
+
{
|
|
366
|
+
filename: 'transparent.gif',
|
|
367
|
+
content: 'R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=',
|
|
368
|
+
contentType: 'image/gif'
|
|
369
|
+
}
|
|
370
|
+
]
|
|
371
|
+
})
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
response: {
|
|
375
|
+
schema: Joi.object({
|
|
376
|
+
response: Joi.string().example('Queued for delivery'),
|
|
377
|
+
messageId: Joi.string()
|
|
378
|
+
.example('<a2184d08-a470-fec6-a493-fa211a3756e9@example.com>')
|
|
379
|
+
.description('Message-ID header value. Not present for bulk messages.'),
|
|
380
|
+
queueId: Joi.string().example('d41f0423195f271f').description('Queue identifier for scheduled email. Not present for bulk messages.'),
|
|
381
|
+
sendAt: Joi.date().example('2021-07-08T07:06:34.336Z').description('Scheduled send time'),
|
|
382
|
+
|
|
383
|
+
reference: Joi.object({
|
|
384
|
+
message: Joi.string()
|
|
385
|
+
.base64({ paddingRequired: false, urlSafe: true })
|
|
386
|
+
.max(256)
|
|
387
|
+
.example('AAAAAQAACnA')
|
|
388
|
+
.description('Referenced message ID. Not present when only a thread ID was referenced'),
|
|
389
|
+
threadId: threadIdSchema.description('Referenced thread ID (Gmail API accounts only)').label('SubmitResponseReferenceThreadId'),
|
|
390
|
+
documentStore: Joi.boolean()
|
|
391
|
+
.example(true)
|
|
392
|
+
.description('Was the message data loaded from the Document Store')
|
|
393
|
+
.label('ResponseDocumentStore')
|
|
394
|
+
.meta({ swaggerHidden: true }),
|
|
395
|
+
success: Joi.boolean().example(true).description('Was the referenced message processed successfully').label('ResponseReferenceSuccess'),
|
|
396
|
+
error: Joi.string().example('Referenced message was not found').description('An error message if referenced message processing failed')
|
|
397
|
+
})
|
|
398
|
+
.description('Reference info if referencing was requested')
|
|
399
|
+
.label('ResponseReference'),
|
|
400
|
+
|
|
401
|
+
preview: Joi.string()
|
|
402
|
+
.base64()
|
|
403
|
+
.example('Q29udGVudC1UeXBlOiBtdWx0aX...')
|
|
404
|
+
.description('Base64 encoded RFC822 email if a preview was requested. Not returned for mail-merge submissions.')
|
|
405
|
+
.label('ResponsePreview'),
|
|
406
|
+
|
|
407
|
+
idempotency: Joi.object({
|
|
408
|
+
key: idempotencyKeySchema.example('submit-12345').description('Idempotency key from the request').label('ResponseIdempotencyKey'),
|
|
409
|
+
status: Joi.string()
|
|
410
|
+
.valid('MISS', 'HIT')
|
|
411
|
+
.example('MISS')
|
|
412
|
+
.description('Whether the response was generated now (MISS) or returned from the idempotency cache (HIT)')
|
|
413
|
+
})
|
|
414
|
+
.description('Idempotency info, present when an Idempotency-Key header was used')
|
|
415
|
+
.label('ResponseIdempotency'),
|
|
416
|
+
|
|
417
|
+
mailMerge: Joi.array()
|
|
418
|
+
.items(
|
|
419
|
+
Joi.object({
|
|
420
|
+
success: Joi.boolean().example(true).description('Was the message queued successfully').label('ResponseReferenceSuccess'),
|
|
421
|
+
to: addressSchema.label('ToAddressSingle'),
|
|
422
|
+
messageId: Joi.string().max(996).example('<test123@example.com>').description('Message ID'),
|
|
423
|
+
queueId: Joi.string().example('d41f0423195f271f').description('Queue identifier for the scheduled email'),
|
|
424
|
+
sendAt: Joi.date().iso().example('2021-07-08T07:06:34.336Z').description('Scheduled send time for this recipient'),
|
|
425
|
+
error: Joi.string().example('Failed to queue message').description('Error message if queueing failed for this recipient'),
|
|
426
|
+
code: Joi.string().example('EENVELOPE').description('Error code if queueing failed for this recipient'),
|
|
427
|
+
statusCode: Joi.number()
|
|
428
|
+
.integer()
|
|
429
|
+
.allow(null)
|
|
430
|
+
.example(500)
|
|
431
|
+
.description('Error status code if queueing failed for this recipient'),
|
|
432
|
+
skipped: Joi.object({
|
|
433
|
+
reason: Joi.string().example('unsubscribe').description('Why this message was skipped'),
|
|
434
|
+
listId: Joi.string().example('test-list')
|
|
435
|
+
})
|
|
436
|
+
.description('Info about skipped message. If this value is set, then the message was not sent')
|
|
437
|
+
.label('SkippedMessageInfo')
|
|
438
|
+
})
|
|
439
|
+
.label('BulkResponseEntry')
|
|
440
|
+
.example({
|
|
441
|
+
success: true,
|
|
442
|
+
to: {
|
|
443
|
+
name: 'Andris 2',
|
|
444
|
+
address: 'andris@ethereal.email'
|
|
445
|
+
},
|
|
446
|
+
messageId: '<19b9c433-d428-f6d8-1d00-d666ebcadfc4@ekiri.ee>',
|
|
447
|
+
queueId: '1812477338914c8372a',
|
|
448
|
+
sendAt: '2021-07-08T07:06:34.336Z'
|
|
449
|
+
})
|
|
450
|
+
.unknown()
|
|
451
|
+
)
|
|
452
|
+
.label('BulkResponseList')
|
|
453
|
+
.description('Bulk message responses')
|
|
454
|
+
}).label('SubmitMessageResponse'),
|
|
455
|
+
failAction: 'log'
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
module.exports = init;
|