Skip to content

Commit c8e65c7

Browse files
author
Tobias S. Keller
committed
refactor: Upgrade AWS SDK SES client to v2.
1 parent 0b6d34a commit c8e65c7

File tree

4 files changed

+153
-98
lines changed

4 files changed

+153
-98
lines changed

package-lock.json

Lines changed: 53 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"@aws-sdk/client-dynamodb": "^3.478.0",
3434
"@aws-sdk/client-eventbridge": "^3.481.0",
3535
"@aws-sdk/client-s3": "^3.478.0",
36-
"@aws-sdk/client-ses": "^3.484.0",
36+
"@aws-sdk/client-sesv2": "^3.946.0",
3737
"@aws-sdk/lib-dynamodb": "^3.478.0",
3838
"@aws-sdk/s3-request-presigner": "^3.481.0",
3939
"@e-invoice-eu/core": "^2.1.8",

src/backend/services/ses.service.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* SES and SMTP, and that attachments are handled as expected.
1010
*/
1111

12-
import { SESClient } from '@aws-sdk/client-ses';
12+
import { SESv2Client } from '@aws-sdk/client-sesv2';
1313
import type { SendMailOptions } from 'nodemailer';
1414
import nodemailer from 'nodemailer';
1515
import {
@@ -36,9 +36,9 @@ vi.mock('nodemailer', () => {
3636
});
3737

3838
// Mock AWS SDK SESClient
39-
vi.mock('@aws-sdk/client-ses', () => {
39+
vi.mock('@aws-sdk/client-sesv2', () => {
4040
return {
41-
SESClient: vi.fn(),
41+
SESv2Client: vi.fn(),
4242
};
4343
});
4444

@@ -129,7 +129,7 @@ describe('SESService', () => {
129129
createTransportMock = vi.fn(() => ({ sendMail: sendMailMock }));
130130
(nodemailer.createTransport as unknown as typeof createTransportMock) =
131131
createTransportMock;
132-
(SESClient as unknown as { mockClear: () => void }).mockClear();
132+
(SESv2Client as unknown as { mockClear: () => void }).mockClear();
133133
createTransportMock.mockClear();
134134
sendMailMock.mockClear();
135135
});
@@ -219,7 +219,7 @@ describe('SESService', () => {
219219
html: '<b>SES</b>',
220220
});
221221
// Should initialize SESClient
222-
expect(SESClient).toHaveBeenCalledWith(
222+
expect(SESv2Client).toHaveBeenCalledWith(
223223
expect.objectContaining({
224224
endpoint: config.endpoints.ses,
225225
region: config.region,
Lines changed: 94 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as ses from '@aws-sdk/client-ses';
1+
import * as ses from '@aws-sdk/client-sesv2';
22
import nodemailer from 'nodemailer';
33
import type SESTransport from 'nodemailer/lib/ses-transport';
44
import type SMTPTransport from 'nodemailer/lib/smtp-transport';
@@ -14,97 +14,100 @@ import { Logger } from './logger.service';
1414
*/
1515
@Service()
1616
export class SESService {
17-
private logger = new Logger('SESService');
18-
private client: ses.SESClient | undefined;
19-
private transporter: nodemailer.Transporter;
17+
private logger = new Logger('SESService');
18+
private client: ses.SESv2Client | undefined;
19+
private transporter: nodemailer.Transporter;
2020

21-
constructor(@Inject(CONFIG_SERVICE) private readonly configuration: ConfigService) {
22-
if (this.configuration.email.type === EmailType.SES) {
23-
const sesOptions = {
24-
...(this.configuration.endpoints.ses
25-
? {
26-
endpoint: this.configuration.endpoints.ses,
27-
}
28-
: {}),
29-
region: this.configuration.region,
30-
credentials:
31-
this.configuration.email.accessKeyId && this.configuration.email.secretAccessKey
32-
? {
33-
accessKeyId: this.configuration.email.accessKeyId,
34-
secretAccessKey: this.configuration.email.secretAccessKey,
35-
}
36-
: undefined,
37-
};
38-
this.client = new ses.SESClient(sesOptions);
39-
this.transporter = nodemailer.createTransport({
40-
SES: { ses: this.client, aws: ses },
41-
});
42-
this.logger.info('SES client initialized', {
43-
region: this.configuration.region,
44-
endpoint: this.configuration.endpoints.ses,
45-
});
46-
} else {
47-
// SMTP configuration
48-
this.transporter = nodemailer.createTransport({
49-
host: this.configuration.email.host,
50-
port: this.configuration.email.port,
51-
secure: this.configuration.email.port === 465,
52-
auth: {
53-
user: this.configuration.email.user,
54-
pass: this.configuration.email.password,
55-
},
56-
} as SMTPTransport.Options);
57-
this.logger.info('SMTP client initialized', {
58-
host: this.configuration.email.host,
59-
port: this.configuration.email.port,
60-
user: this.configuration.email.user,
61-
});
62-
}
63-
}
21+
constructor(
22+
@Inject(CONFIG_SERVICE) private readonly configuration: ConfigService,
23+
) {
24+
if (this.configuration.email.type === EmailType.SES) {
25+
const sesOptions = {
26+
...(this.configuration.endpoints.ses
27+
? {
28+
endpoint: this.configuration.endpoints.ses,
29+
}
30+
: {}),
31+
region: this.configuration.region,
32+
credentials:
33+
this.configuration.email.accessKeyId &&
34+
this.configuration.email.secretAccessKey
35+
? {
36+
accessKeyId: this.configuration.email.accessKeyId,
37+
secretAccessKey: this.configuration.email.secretAccessKey,
38+
}
39+
: undefined,
40+
};
41+
this.client = new ses.SESv2Client(sesOptions);
42+
this.transporter = nodemailer.createTransport({
43+
SES: { ses: this.client, aws: ses },
44+
} as any);
45+
this.logger.info('SES client initialized', {
46+
region: this.configuration.region,
47+
endpoint: this.configuration.endpoints.ses,
48+
});
49+
} else {
50+
// SMTP configuration
51+
this.transporter = nodemailer.createTransport({
52+
host: this.configuration.email.host,
53+
port: this.configuration.email.port,
54+
secure: this.configuration.email.port === 465,
55+
auth: {
56+
user: this.configuration.email.user,
57+
pass: this.configuration.email.password,
58+
},
59+
} as SMTPTransport.Options);
60+
this.logger.info('SMTP client initialized', {
61+
host: this.configuration.email.host,
62+
port: this.configuration.email.port,
63+
user: this.configuration.email.user,
64+
});
65+
}
66+
}
6467

65-
@Span('SESService.sendEmail')
66-
public sendEmail({
67-
from,
68-
replyTo,
69-
to,
70-
subject,
71-
html,
72-
attachments,
73-
}: {
74-
from: string;
75-
replyTo?: string;
76-
to: string;
77-
subject: string;
78-
html: string;
79-
attachments?: {
80-
filename: string;
81-
content: Buffer | string;
82-
}[];
83-
}): Promise<SESTransport.SentMessageInfo | SMTPTransport.SentMessageInfo> {
84-
// If using SMTP and no from address is provided, use the configured default
85-
const fromAddress =
86-
this.configuration.email.type === EmailType.SMTP && !from
87-
? this.configuration.email.from
88-
: from;
68+
@Span('SESService.sendEmail')
69+
public sendEmail({
70+
from,
71+
replyTo,
72+
to,
73+
subject,
74+
html,
75+
attachments,
76+
}: {
77+
from: string;
78+
replyTo?: string;
79+
to: string;
80+
subject: string;
81+
html: string;
82+
attachments?: {
83+
filename: string;
84+
content: Buffer | string;
85+
}[];
86+
}): Promise<SESTransport.SentMessageInfo | SMTPTransport.SentMessageInfo> {
87+
// If using SMTP and no from address is provided, use the configured default
88+
const fromAddress =
89+
this.configuration.email.type === EmailType.SMTP && !from
90+
? this.configuration.email.from
91+
: from;
8992

90-
return new Promise((resolve, reject) => {
91-
this.transporter.sendMail(
92-
{
93-
from: fromAddress,
94-
to,
95-
subject,
96-
replyTo,
97-
html,
98-
attachments,
99-
},
100-
(err, info) => {
101-
if (err) {
102-
reject(err);
103-
} else {
104-
resolve(info);
105-
}
106-
}
107-
);
108-
});
109-
}
93+
return new Promise((resolve, reject) => {
94+
this.transporter.sendMail(
95+
{
96+
from: fromAddress,
97+
to,
98+
subject,
99+
replyTo,
100+
html,
101+
attachments,
102+
},
103+
(err, info) => {
104+
if (err) {
105+
reject(err);
106+
} else {
107+
resolve(info);
108+
}
109+
},
110+
);
111+
});
112+
}
110113
}

0 commit comments

Comments
 (0)