ac-ses 1.2.3 → 2.0.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/.eslintrc.js +1 -1
- package/.ncurc.js +5 -0
- package/CHANGELOG.md +47 -0
- package/README.md +21 -32
- package/index.js +79 -186
- package/package.json +10 -11
- package/test/test.js +23 -108
package/.eslintrc.js
CHANGED
package/.ncurc.js
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,50 @@
|
|
|
1
|
+
<a name="2.0.0"></a>
|
|
2
|
+
|
|
3
|
+
# [2.0.0](https://github.com/admiralcloud/ac-ses/compare/v1.2.4..v2.0.0) (2024-01-06 09:43:38)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fix
|
|
7
|
+
|
|
8
|
+
* **App:** Use async/await and remove some functions | MP | [7f9c4aee85e062dfc08fed09c1f4bf08d37e3673](https://github.com/admiralcloud/ac-ses/commit/7f9c4aee85e062dfc08fed09c1f4bf08d37e3673)
|
|
9
|
+
Package now uses async/await. No more support for blocktime and group messages - use your application logic for that.
|
|
10
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
11
|
+
### Chores
|
|
12
|
+
|
|
13
|
+
* **App:** Some minor updates | MP | [6cdc1d8f9927d5926836b1128e3f84b8ebb1c0f0](https://github.com/admiralcloud/ac-ses/commit/6cdc1d8f9927d5926836b1128e3f84b8ebb1c0f0)
|
|
14
|
+
Some minor updates
|
|
15
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
16
|
+
### Chores
|
|
17
|
+
|
|
18
|
+
* **App:** Updated packages | MP | [21369161d02172ccba704d286dca59b5e0dbbde5](https://github.com/admiralcloud/ac-ses/commit/21369161d02172ccba704d286dca59b5e0dbbde5)
|
|
19
|
+
Updated packages
|
|
20
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
21
|
+
## BREAKING CHANGES
|
|
22
|
+
* **App:** See README breaking changes for version 2
|
|
23
|
+
<a name="1.2.4"></a>
|
|
24
|
+
|
|
25
|
+
## [1.2.4](https://github.com/admiralcloud/ac-ses/compare/v1.2.3..v1.2.4) (2023-01-31 11:26:12)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Bug Fix
|
|
29
|
+
|
|
30
|
+
* **App:** Add debug mode for SES headers | MP | [b11e0374c5956236e80f4b81d9895ba9fd5916f3](https://github.com/admiralcloud/ac-ses/commit/b11e0374c5956236e80f4b81d9895ba9fd5916f3)
|
|
31
|
+
Add debug mode for SES headers
|
|
32
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
33
|
+
### Tests
|
|
34
|
+
|
|
35
|
+
* **App:** Fixed tests after package updates | MP | [ba0a41496ffa202c9bac3d7fbde5a7c4b542d24b](https://github.com/admiralcloud/ac-ses/commit/ba0a41496ffa202c9bac3d7fbde5a7c4b542d24b)
|
|
36
|
+
Fixed tests after package updates
|
|
37
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
38
|
+
### Chores
|
|
39
|
+
|
|
40
|
+
* **App:** Updated packages | MP | [f3affc2c748a2ed6cb96eaad6a31fae2af646d37](https://github.com/admiralcloud/ac-ses/commit/f3affc2c748a2ed6cb96eaad6a31fae2af646d37)
|
|
41
|
+
Updated packages
|
|
42
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
43
|
+
### Chores
|
|
44
|
+
|
|
45
|
+
* **App:** Updated ESLint setting | MP | [a5dd3adfa10ef02ca1c886a0485d324b6ac73aee](https://github.com/admiralcloud/ac-ses/commit/a5dd3adfa10ef02ca1c886a0485d324b6ac73aee)
|
|
46
|
+
Updated ESLint setting to ECMA 2021
|
|
47
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
1
48
|
<a name="1.2.3"></a>
|
|
2
49
|
|
|
3
50
|
## [1.2.3](https://github.com/admiralcloud/ac-ses/compare/v1.2.2..v1.2.3) (2022-07-22 05:34:50)
|
package/README.md
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
# AC SES
|
|
2
2
|
A helper tool to send emails via AWS SES.
|
|
3
|
-
It also support "convenience" calls, to inform groups (e.g. support with informSupport function)
|
|
4
3
|
|
|
5
|
-
##
|
|
4
|
+
## BREAKING CHANGES VERSION 2
|
|
5
|
+
+ use async/await
|
|
6
|
+
+ no more support for blocktime - use your application logic instead
|
|
7
|
+
+ no more support for group messages - use your application logid instead
|
|
6
8
|
|
|
9
|
+
## Usage
|
|
7
10
|
```
|
|
8
11
|
const acses = require('ac-ses')
|
|
9
12
|
|
|
10
13
|
// Min requirements
|
|
11
14
|
acses.init({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
secretAccessKey: 'xxx',
|
|
15
|
-
region: 'eu-central-1'
|
|
15
|
+
defaultSender: {
|
|
16
|
+
address: 'sender@domain.com
|
|
16
17
|
}
|
|
17
18
|
})
|
|
18
19
|
|
|
@@ -34,47 +35,35 @@ let email = {
|
|
|
34
35
|
html: 'This is my <b>message</b>' // optional
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
acses.sendEmail(email
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
await acses.sendEmail(email)
|
|
39
|
+
// -> Response
|
|
40
|
+
{
|
|
41
|
+
'$metadata': {
|
|
42
|
+
httpStatusCode: 200,
|
|
43
|
+
requestId: '123466-557d-4a75-8d5c-d71e336963ec',
|
|
44
|
+
extendedRequestId: undefined,
|
|
45
|
+
cfId: undefined,
|
|
46
|
+
attempts: 1,
|
|
47
|
+
totalRetryDelay: 0
|
|
48
|
+
},
|
|
49
|
+
MessageId: '12356-2c4f41dd-6f2b-402e-9c26-123355-000000'
|
|
50
|
+
}
|
|
42
51
|
```
|
|
43
52
|
|
|
44
53
|
Full setup
|
|
45
|
-
|
|
46
54
|
```
|
|
47
55
|
acses.init({
|
|
48
|
-
aws: {
|
|
49
|
-
accessKeyId: 'xxx',
|
|
50
|
-
secretAccessKey: 'xxx',
|
|
51
|
-
region: 'eu-central-1'
|
|
52
|
-
},
|
|
53
|
-
redis: REDISINSTANCE,
|
|
54
|
-
defaultBlockTime: BLOCKTIME FOR SAME MESSAGE,
|
|
55
56
|
defaultSender: {
|
|
56
57
|
address: 'defaultSender@admiralcloud.com',
|
|
57
58
|
name: 'AdmiralCloud Sender'
|
|
58
59
|
},
|
|
59
|
-
securityRecipient: {
|
|
60
|
-
address: 'defaultSecurityRecipient@admiralcloud.com',
|
|
61
|
-
name: 'AdmiralCloud Security'
|
|
62
|
-
},
|
|
63
|
-
supportRecipient: {
|
|
64
|
-
address: address: 'defaultSupportRecipient@admiralcloud.com',
|
|
65
|
-
name: 'AdmiralCloud Support'
|
|
66
|
-
},
|
|
67
60
|
environment: ENVIRONMENT // defaults to proces.env.NODE_ENV,
|
|
68
61
|
useEnvironmentPrefixInSubject: TRUE|FALSE // defaults to TRUE - prefixes e-mail subject with environment to avoid confusion during development
|
|
69
62
|
})
|
|
70
|
-
|
|
71
|
-
|
|
72
63
|
```
|
|
73
64
|
|
|
74
65
|
## Links
|
|
75
66
|
- [Website](https://www.admiralcloud.com/)
|
|
76
|
-
- [Twitter (@admiralcloud)](https://twitter.com/admiralcloud)
|
|
77
|
-
- [Facebook](https://www.facebook.com/MediaAssetManagement/)
|
|
78
67
|
|
|
79
68
|
## License
|
|
80
|
-
[MIT License](https://opensource.org/licenses/MIT) Copyright © 2009-present, AdmiralCloud, Mark Poepping
|
|
69
|
+
[MIT License](https://opensource.org/licenses/MIT) Copyright © 2009-present, AdmiralCloud AG, Mark Poepping
|
package/index.js
CHANGED
|
@@ -1,39 +1,21 @@
|
|
|
1
1
|
const _ = require('lodash')
|
|
2
|
-
const async = require('async')
|
|
3
|
-
const aws = require('aws-sdk')
|
|
4
|
-
|
|
5
|
-
const crypto = require('crypto')
|
|
6
2
|
const { v4: uuidV4 } = require('uuid')
|
|
7
3
|
|
|
8
4
|
const quotedPrintable = require('quoted-printable')
|
|
9
5
|
const utf8 = require('utf8')
|
|
10
6
|
|
|
11
|
-
const
|
|
7
|
+
const { SESClient, SendRawEmailCommand } = require("@aws-sdk/client-ses")
|
|
8
|
+
|
|
9
|
+
const acses = () => {
|
|
12
10
|
let ses
|
|
13
|
-
let redis // only required if blockTime should be used - use init to set a redis instance from your main app
|
|
14
11
|
|
|
15
12
|
let defaultSender
|
|
16
|
-
let supportRecipient
|
|
17
|
-
let securityRecipient
|
|
18
|
-
let operationsRecipient
|
|
19
|
-
let defaultBlockTime = 60 // used for support and security
|
|
20
13
|
let testMode // if true, no email is sent
|
|
21
14
|
let environment = process.env.NODE_ENV || 'development'
|
|
22
15
|
let useEnvironmentPrefixInSubject = environment !== 'production'
|
|
23
16
|
|
|
24
17
|
const init = function(options) {
|
|
25
|
-
|
|
26
|
-
accessKeyId: _.get(options, 'aws.accessKeyId', process.env.AWS_ACCESS_KEY),
|
|
27
|
-
secretAccessKey: _.get(options, 'aws.secretAccessKey', process.env.AWS_ACCESS_SECRET),
|
|
28
|
-
region: _.get(options, 'aws.region', process.env.AWS_REGION)
|
|
29
|
-
}
|
|
30
|
-
ses = new aws.SES(awsConfig)
|
|
31
|
-
if (_.get(options, 'redis')) {
|
|
32
|
-
redis = _.get(options, 'redis')
|
|
33
|
-
}
|
|
34
|
-
if (_.get(options, 'defaultBlockTime')) {
|
|
35
|
-
defaultBlockTime = _.get(options, 'defaultBlockTime')
|
|
36
|
-
}
|
|
18
|
+
ses = new SESClient()
|
|
37
19
|
if (_.get(options, 'testMode')) {
|
|
38
20
|
testMode = _.get(options, 'testMode')
|
|
39
21
|
}
|
|
@@ -44,19 +26,11 @@ const acses = function() {
|
|
|
44
26
|
|
|
45
27
|
// helper
|
|
46
28
|
if (_.get(options, 'defaultSender') && prepareEmailAddress(_.get(options, 'defaultSender'))) defaultSender = _.get(options, 'defaultSender')
|
|
47
|
-
|
|
48
|
-
if (_.get(options, 'securityRecipient') && prepareEmailAddress(_.get(options, 'securityRecipient'))) securityRecipient = _.get(options, 'securityRecipient')
|
|
49
|
-
if (_.get(options, 'operationsRecipient') && prepareEmailAddress(_.get(options, 'operationsRecipient'))) operationsRecipient = _.get(options, 'operationsRecipient')
|
|
50
|
-
|
|
29
|
+
|
|
51
30
|
return {
|
|
52
|
-
awsConfig: _.pick(awsConfig, ['accessKeyId', 'region']),
|
|
53
31
|
testMode,
|
|
54
|
-
defaultBlockTime,
|
|
55
32
|
environment,
|
|
56
|
-
defaultSender
|
|
57
|
-
supportRecipient,
|
|
58
|
-
securityRecipient,
|
|
59
|
-
operationsRecipient
|
|
33
|
+
defaultSender
|
|
60
34
|
}
|
|
61
35
|
}
|
|
62
36
|
|
|
@@ -67,10 +41,10 @@ const acses = function() {
|
|
|
67
41
|
* OUT John Doe <john.doe@example.com>
|
|
68
42
|
* @param {*} params
|
|
69
43
|
*/
|
|
70
|
-
const prepareEmailAddress =
|
|
71
|
-
if (!
|
|
72
|
-
if (!_.isString(
|
|
73
|
-
let email =
|
|
44
|
+
const prepareEmailAddress = ({ address, name }) => {
|
|
45
|
+
if (!address) throw new Error({ message: 'ACSES.prepareEmailAddress - address_required' })
|
|
46
|
+
if (!_.isString(address)) throw new Error({ message: 'ACSES.prepareEmailAddress - address_mustBeAString' })
|
|
47
|
+
let email = name ? `${name} <${address}>` : address
|
|
74
48
|
return email
|
|
75
49
|
}
|
|
76
50
|
|
|
@@ -89,14 +63,11 @@ const acses = function() {
|
|
|
89
63
|
*
|
|
90
64
|
* @param attachments ARRAY Optional array of objects with properties: filename, content (as base64) and encoding as 'base64'
|
|
91
65
|
*
|
|
92
|
-
* @param blockTime INT OPTIONAL If set, you cannot send an email to the same recipient for the blockTime (helpful for warning messages - you don't want them every second!)
|
|
93
|
-
* @param redisKey STRING OPTIONAL You can use your own redisKey for the blockTime feature, or let this app create an MD5 hash from the parameters
|
|
94
66
|
* @param encoding STRING OPTIONAL Encoding for multipart alternative parts - defaults to "quoted-printable"
|
|
95
67
|
*
|
|
96
|
-
* @param {*} cb Optional callback
|
|
97
68
|
*/
|
|
98
|
-
const sendEmail = (params
|
|
99
|
-
if (!_.isObject(ses))
|
|
69
|
+
const sendEmail = async(params) => {
|
|
70
|
+
if (!_.isObject(ses)) throw new Error('pleaseUseInitBeforeSendingEmail')
|
|
100
71
|
if (!_.get(params, 'from') && defaultSender) _.set(params, 'from', defaultSender)
|
|
101
72
|
const fieldCheck = [
|
|
102
73
|
{ field: 'from', type: _.isPlainObject, required: true },
|
|
@@ -109,173 +80,95 @@ const acses = function() {
|
|
|
109
80
|
// OPTIONS
|
|
110
81
|
{ field: 'replyTo', type: _.isArray },
|
|
111
82
|
{ field: 'attachments', type: _.isArray },
|
|
83
|
+
{ field: 'debug', type: _.isBoolean },
|
|
112
84
|
]
|
|
113
85
|
|
|
114
86
|
_.some(fieldCheck, (field) => {
|
|
115
|
-
if (field.required && !_.has(params, field.field))
|
|
116
|
-
if (_.get(params, field.field) && !field.type(_.get(params, field.field)))
|
|
87
|
+
if (field.required && !_.has(params, field.field)) throw new Error(field.field + '_required')
|
|
88
|
+
if (_.get(params, field.field) && !field.type(_.get(params, field.field))) throw new Error(field.field + '_typeInvalid')
|
|
117
89
|
})
|
|
118
90
|
|
|
119
91
|
const boundaryMixed = uuidV4()
|
|
120
92
|
const boundaryAlternative = uuidV4()
|
|
121
93
|
const encoding = _.get(params, 'encoding', 'quoted-printable')
|
|
122
94
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
text: params.text
|
|
131
|
-
}
|
|
132
|
-
let redisKey = _.get(params, 'redisKey', crypto.createHash('md5').update(JSON.stringify(sesParams)).digest('hex'))
|
|
133
|
-
redis.set(redisKey, 1, 'EX', params.blockTime, 'NX', (err, result) => {
|
|
134
|
-
if (err) return done(err)
|
|
135
|
-
if (result === 'OK') return done()
|
|
136
|
-
return done(423) // the key is already locked
|
|
95
|
+
// sendRawMessage
|
|
96
|
+
let raw = 'From: ' + prepareEmailAddress(_.get(params, 'from')) + '\n'
|
|
97
|
+
// prepare To, Cc, Bcc
|
|
98
|
+
_.forEach(['to', 'cc', 'bcc'], type => {
|
|
99
|
+
if (_.size(_.get(params, type))) {
|
|
100
|
+
let recipients = _.map(_.get(params, type), (recipient) => {
|
|
101
|
+
return prepareEmailAddress(recipient)
|
|
137
102
|
})
|
|
138
|
-
|
|
139
|
-
sendRawMessage: (done) => {
|
|
140
|
-
let raw = 'From: ' + prepareEmailAddress(_.get(params, 'from')) + '\n'
|
|
141
|
-
// prepare To, Cc, Bcc
|
|
142
|
-
_.forEach(['to', 'cc', 'bcc'], type => {
|
|
143
|
-
if (_.size(_.get(params, type))) {
|
|
144
|
-
let recipients = _.map(_.get(params, type), (recipient) => {
|
|
145
|
-
return prepareEmailAddress(recipient)
|
|
146
|
-
})
|
|
147
|
-
raw += _.upperFirst(type) + ': ' + _.join(recipients, ', ') + '\n'
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
if (params.replyTo) {
|
|
151
|
-
let recipients = _.map(_.get(params, 'replyTo'), (recipient) => {
|
|
152
|
-
return prepareEmailAddress(recipient)
|
|
153
|
-
})
|
|
154
|
-
raw += 'Reply-To: ' + _.join(recipients, ', ') + '\n'
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
raw += 'Subject: ' + (useEnvironmentPrefixInSubject ? (_.toUpper(environment) + ' | ') : '') + params.subject + '\n'
|
|
158
|
-
|
|
159
|
-
// announce multipart/mixed
|
|
160
|
-
raw += 'Mime-Version: 1.0\n'
|
|
161
|
-
raw += 'Content-type: multipart/mixed; boundary="' + boundaryMixed + '"\n\n'
|
|
162
|
-
raw += 'This message is in MIME format. Since your mail reader does not understand this format, some or all of this message may not be legible.\n\n'
|
|
163
|
-
|
|
164
|
-
// text and HTML are multipart/alternatives with their own boundaries
|
|
165
|
-
raw += '--' + boundaryMixed + '\nContent-Type: multipart/alternative; boundary="' + boundaryAlternative + '"\n\n'
|
|
166
|
-
if (params.text) {
|
|
167
|
-
raw += '--' + boundaryAlternative + '\nContent-Type: text/plain; charset="UTF-8"\nContent-Transfer-Encoding: ' + encoding + '\n\n'
|
|
168
|
-
raw += quotedPrintable.encode(utf8.encode(params.text)) + '\n\n'
|
|
169
|
-
}
|
|
170
|
-
if (params.html) {
|
|
171
|
-
raw += '--' + boundaryAlternative + '\nContent-Type: text/html; charset="UTF-8"\nContent-Transfer-Encoding: ' + encoding + '\n\n'
|
|
172
|
-
raw += quotedPrintable.encode(utf8.encode(params.html)) + '\n\n'
|
|
173
|
-
}
|
|
174
|
-
raw += '--' + boundaryAlternative + '--\n\n'
|
|
175
|
-
|
|
176
|
-
if (params.attachments) {
|
|
177
|
-
_.forEach(params.attachments, attachment => {
|
|
178
|
-
raw += '--' + boundaryMixed + '\n'
|
|
179
|
-
raw += 'Content-Disposition: attachment; filename="' + attachment.filename + '"\n'
|
|
180
|
-
raw += 'Content-Type: ' + attachment.contentType + '; name="' + attachment.filename + '"\nContent-Transfer-Encoding: ' + attachment.encoding + '\n\n'
|
|
181
|
-
raw += attachment.content + '\n\n'
|
|
182
|
-
})
|
|
183
|
-
raw += '--' + boundaryMixed + '--\n'
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const rawParams = {
|
|
187
|
-
RawMessage: { /* required */
|
|
188
|
-
Data: Buffer.from(raw)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (testMode) {
|
|
192
|
-
// return fake response, but do not send message
|
|
193
|
-
let mockResponse = {
|
|
194
|
-
ResponseMetadata: {
|
|
195
|
-
RequestId: uuidV4()
|
|
196
|
-
},
|
|
197
|
-
MessageId: Math.random().toString(36) + '-' + uuidV4() + '-000000',
|
|
198
|
-
testMode: true
|
|
199
|
-
}
|
|
200
|
-
return done(null, mockResponse)
|
|
201
|
-
}
|
|
202
|
-
ses.sendRawEmail(rawParams, done)
|
|
103
|
+
raw += _.upperFirst(type) + ': ' + _.join(recipients, ', ') + '\n'
|
|
203
104
|
}
|
|
204
|
-
}, (err, result) => {
|
|
205
|
-
if (err && err === 423) err = null // this is not an error, just blocked
|
|
206
|
-
if (_.isFunction(cb)) {
|
|
207
|
-
return cb(err, _.get(result, 'sendRawMessage'))
|
|
208
|
-
}
|
|
209
|
-
if (err) throw new Error(err)
|
|
210
105
|
})
|
|
211
|
-
|
|
106
|
+
if (params.replyTo) {
|
|
107
|
+
let recipients = _.map(_.get(params, 'replyTo'), (recipient) => {
|
|
108
|
+
return prepareEmailAddress(recipient)
|
|
109
|
+
})
|
|
110
|
+
raw += 'Reply-To: ' + _.join(recipients, ', ') + '\n'
|
|
111
|
+
}
|
|
212
112
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
* Differences: no HTML to improve delivery
|
|
217
|
-
* @param {*} params
|
|
218
|
-
* @param {*} cb
|
|
219
|
-
*/
|
|
220
|
-
const informSecurity = function(params, cb) {
|
|
221
|
-
if (!securityRecipient) return cb({ message: 'acses.informSecurity - securityRecipient_notSet' })
|
|
222
|
-
let message = {
|
|
223
|
-
to: [securityRecipient],
|
|
224
|
-
subject: params.subject,
|
|
225
|
-
text: params.text,
|
|
226
|
-
blockTime: _.get(params, 'blockTime', defaultBlockTime),
|
|
227
|
-
redisKey: _.get(params, 'redisKey')
|
|
113
|
+
raw += 'Subject: ' + (useEnvironmentPrefixInSubject ? (_.toUpper(environment) + ' | ') : '') + params.subject + '\n'
|
|
114
|
+
if (params?.debug) {
|
|
115
|
+
console.log('ACSES | DEBUG Headers | %j', raw.split('/n'))
|
|
228
116
|
}
|
|
229
|
-
if (_.isFunction(cb)) sendEmail(message, cb)
|
|
230
|
-
else sendEmail(message)
|
|
231
|
-
}
|
|
232
117
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
text:
|
|
246
|
-
|
|
247
|
-
|
|
118
|
+
// announce multipart/mixed
|
|
119
|
+
raw += 'Mime-Version: 1.0\n'
|
|
120
|
+
raw += 'Content-type: multipart/mixed; boundary="' + boundaryMixed + '"\n\n'
|
|
121
|
+
raw += 'This message is in MIME format. Since your mail reader does not understand this format, some or all of this message may not be legible.\n\n'
|
|
122
|
+
|
|
123
|
+
// text and HTML are multipart/alternatives with their own boundaries
|
|
124
|
+
raw += '--' + boundaryMixed + '\nContent-Type: multipart/alternative; boundary="' + boundaryAlternative + '"\n\n'
|
|
125
|
+
if (params.text) {
|
|
126
|
+
raw += '--' + boundaryAlternative + '\nContent-Type: text/plain; charset="UTF-8"\nContent-Transfer-Encoding: ' + encoding + '\n\n'
|
|
127
|
+
raw += quotedPrintable.encode(utf8.encode(params.text)) + '\n\n'
|
|
128
|
+
}
|
|
129
|
+
if (params.html) {
|
|
130
|
+
raw += '--' + boundaryAlternative + '\nContent-Type: text/html; charset="UTF-8"\nContent-Transfer-Encoding: ' + encoding + '\n\n'
|
|
131
|
+
raw += quotedPrintable.encode(utf8.encode(params.html)) + '\n\n'
|
|
132
|
+
}
|
|
133
|
+
raw += '--' + boundaryAlternative + '--\n\n'
|
|
134
|
+
|
|
135
|
+
if (params.attachments) {
|
|
136
|
+
_.forEach(params.attachments, attachment => {
|
|
137
|
+
raw += '--' + boundaryMixed + '\n'
|
|
138
|
+
raw += 'Content-Disposition: attachment; filename="' + attachment.filename + '"\n'
|
|
139
|
+
raw += 'Content-Type: ' + attachment.contentType + '; name="' + attachment.filename + '"\nContent-Transfer-Encoding: ' + attachment.encoding + '\n\n'
|
|
140
|
+
raw += attachment.content + '\n\n'
|
|
141
|
+
})
|
|
142
|
+
raw += '--' + boundaryMixed + '--\n'
|
|
248
143
|
}
|
|
249
|
-
if (_.isFunction(cb)) sendEmail(message, cb)
|
|
250
|
-
else sendEmail(message)
|
|
251
|
-
}
|
|
252
144
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
145
|
+
const rawParams = {
|
|
146
|
+
RawMessage: { /* required */
|
|
147
|
+
Data: Buffer.from(raw)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (testMode) {
|
|
152
|
+
// return fake response, but do not send message
|
|
153
|
+
let mockResponse = {
|
|
154
|
+
'$metadata': {
|
|
155
|
+
httpStatusCode: 200,
|
|
156
|
+
requestId: uuidV4(),
|
|
157
|
+
attempts: 1
|
|
158
|
+
},
|
|
159
|
+
MessageId: Math.random().toString(36) + '-' + uuidV4() + '-000000',
|
|
160
|
+
testMode: true
|
|
161
|
+
}
|
|
162
|
+
return mockResponse
|
|
268
163
|
}
|
|
269
|
-
|
|
270
|
-
|
|
164
|
+
const command = new SendRawEmailCommand(rawParams)
|
|
165
|
+
const response = await ses.send(command)
|
|
166
|
+
return response
|
|
271
167
|
}
|
|
272
168
|
|
|
273
169
|
return {
|
|
274
170
|
init,
|
|
275
|
-
sendEmail
|
|
276
|
-
informSecurity,
|
|
277
|
-
informSupport,
|
|
278
|
-
informOperations
|
|
171
|
+
sendEmail
|
|
279
172
|
}
|
|
280
173
|
}
|
|
281
174
|
|
package/package.json
CHANGED
|
@@ -4,28 +4,27 @@
|
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": "admiralcloud/ac-ses",
|
|
6
6
|
"homepage": "https://www.admiralcloud.com",
|
|
7
|
-
"version": "
|
|
7
|
+
"version": "2.0.0",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"
|
|
10
|
-
"aws-sdk": "^2.1180.0",
|
|
9
|
+
"@aws-sdk/client-ses": "^3.485.0",
|
|
11
10
|
"lodash": "^4.17.21",
|
|
12
11
|
"quoted-printable": "^1.0.1",
|
|
13
12
|
"utf8": "^3.0.0",
|
|
14
|
-
"uuid": "^
|
|
13
|
+
"uuid": "^9.x"
|
|
15
14
|
},
|
|
16
15
|
"devDependencies": {
|
|
17
|
-
"ac-semantic-release": "^0.
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"mocha": "^
|
|
22
|
-
"mocha-jenkins-reporter": "0.4.
|
|
16
|
+
"ac-semantic-release": "^0.4.2",
|
|
17
|
+
"chai": "^4.4.0",
|
|
18
|
+
"eslint": "8.x",
|
|
19
|
+
"expect": "^29.7.0",
|
|
20
|
+
"mocha": "^10.2.0",
|
|
21
|
+
"mocha-jenkins-reporter": "0.4.8"
|
|
23
22
|
},
|
|
24
23
|
"scripts": {
|
|
25
24
|
"test": "mocha --reporter spec",
|
|
26
25
|
"test-jenkins": "JUNIT_REPORT_PATH=./report.xml mocha --colors --reporter mocha-jenkins-reporter --reporter-options junit_report_name='AC-SES'"
|
|
27
26
|
},
|
|
28
27
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
28
|
+
"node": ">=16.0.0"
|
|
30
29
|
}
|
|
31
30
|
}
|
package/test/test.js
CHANGED
|
@@ -1,126 +1,41 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
1
|
+
const fs = require('fs/promises')
|
|
2
2
|
|
|
3
|
-
const expect = require('expect')
|
|
4
3
|
const acses = require('../index')
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
var redis = new Redis({
|
|
8
|
-
host: process.env.REDIS_HOST || 'localhost',
|
|
9
|
-
port: process.env.REDIS_PORT || 6379
|
|
10
|
-
})
|
|
11
|
-
|
|
5
|
+
const expect = require('chai').expect
|
|
12
6
|
const testConfig = require('./testConfig.js')
|
|
13
7
|
|
|
14
8
|
describe('CHECKING ERRORS', function () {
|
|
15
|
-
it('Send email without init',
|
|
9
|
+
it('Send email without init', async() => {
|
|
16
10
|
let params = testConfig.email
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
try {
|
|
12
|
+
await acses.sendEmail(params)
|
|
13
|
+
}
|
|
14
|
+
catch(e) {
|
|
15
|
+
expect(e.message).to.eql('pleaseUseInitBeforeSendingEmail')
|
|
16
|
+
}
|
|
21
17
|
})
|
|
22
18
|
})
|
|
23
19
|
|
|
24
20
|
describe('TESTING EMAIL', function () {
|
|
25
|
-
it('Init AC SES',
|
|
26
|
-
acses.init(testConfig.init)
|
|
27
|
-
return done()
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('Send a text email', function(done) {
|
|
31
|
-
let params = testConfig.email
|
|
32
|
-
acses.sendEmail(params, (err, result) => {
|
|
33
|
-
if (err) return done(err)
|
|
34
|
-
expect(result).toHaveProperty('ResponseMetadata')
|
|
35
|
-
expect(result).toHaveProperty('MessageId')
|
|
36
|
-
return done()
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('Send a HTML email', function(done) {
|
|
41
|
-
let params = testConfig.email
|
|
42
|
-
fs.readFile(process.cwd() + '/test/htmlTemplate.html', (err, data) => {
|
|
43
|
-
if (err) return done(err)
|
|
44
|
-
params.subject = 'HTML Test E-Mail'
|
|
45
|
-
params.html = data.toString()
|
|
46
|
-
acses.sendEmail(params, (err, result) => {
|
|
47
|
-
if (err) return done(err)
|
|
48
|
-
expect(result).toHaveProperty('ResponseMetadata')
|
|
49
|
-
expect(result).toHaveProperty('MessageId')
|
|
50
|
-
return done()
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('Send a security email', function(done) {
|
|
56
|
-
let params = testConfig.securityEmail
|
|
57
|
-
acses.informSecurity(params, (err, result) => {
|
|
58
|
-
if (err) return done(err)
|
|
59
|
-
expect(result).toHaveProperty('ResponseMetadata')
|
|
60
|
-
expect(result).toHaveProperty('MessageId')
|
|
61
|
-
return done()
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('Send a support email', function(done) {
|
|
66
|
-
let params = testConfig.supportEmail
|
|
67
|
-
acses.informSecurity(params, (err, result) => {
|
|
68
|
-
if (err) return done(err)
|
|
69
|
-
expect(result).toHaveProperty('ResponseMetadata')
|
|
70
|
-
expect(result).toHaveProperty('MessageId')
|
|
71
|
-
return done()
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
describe('TESTING BLOCK TIME', function() {
|
|
77
|
-
this.timeout(60000)
|
|
78
|
-
|
|
79
|
-
it('Init AC SES with Redis support', function(done) {
|
|
80
|
-
testConfig.init.redis = redis
|
|
21
|
+
it('Init AC SES', async() => {
|
|
81
22
|
acses.init(testConfig.init)
|
|
82
|
-
return done()
|
|
83
23
|
})
|
|
84
24
|
|
|
85
|
-
it('Send a text email',
|
|
25
|
+
it('Send a text email', async() => {
|
|
86
26
|
let params = testConfig.email
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
acses.sendEmail(params, (err, result) => {
|
|
91
|
-
if (err) return done(err)
|
|
92
|
-
expect(result).toHaveProperty('ResponseMetadata')
|
|
93
|
-
expect(result).toHaveProperty('MessageId')
|
|
94
|
-
return done()
|
|
95
|
-
})
|
|
27
|
+
let result = await acses.sendEmail(params)
|
|
28
|
+
expect(result).to.have.property('$metadata')
|
|
29
|
+
expect(result).to.have.property('MessageId')
|
|
96
30
|
})
|
|
97
31
|
|
|
98
|
-
it('Send a
|
|
32
|
+
it('Send a HTML email', async() => {
|
|
99
33
|
let params = testConfig.email
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
setTimeout(done, 10000)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('Send a text email - should work again', function(done) {
|
|
112
|
-
let params = testConfig.email
|
|
113
|
-
params.html = null
|
|
114
|
-
params.subject = 'Test after block time'
|
|
115
|
-
acses.sendEmail(params, (err, result) => {
|
|
116
|
-
if (err) return done(err)
|
|
117
|
-
expect(result).toHaveProperty('ResponseMetadata')
|
|
118
|
-
expect(result).toHaveProperty('MessageId')
|
|
119
|
-
return done()
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
it('Close Redis connection', function(done) {
|
|
124
|
-
redis.quit(done)
|
|
125
|
-
})
|
|
126
|
-
})
|
|
34
|
+
const data = await fs.readFile(process.cwd() + '/test/htmlTemplate.html')
|
|
35
|
+
params.subject = 'HTML Test E-Mail'
|
|
36
|
+
params.html = data.toString()
|
|
37
|
+
const result = await acses.sendEmail(params)
|
|
38
|
+
expect(result).to.have.property('$metadata')
|
|
39
|
+
expect(result).to.have.property('MessageId')
|
|
40
|
+
})
|
|
41
|
+
})
|