Skip to content

#595 migrate to sesv2 #1336

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions docs/src/main/asciidoc/ses.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,7 @@ The Spring Boot Starter for SES provides the following configuration options:
| `spring.cloud.aws.ses.enabled` | Enables the SES integration. | No | `true`
| `spring.cloud.aws.ses.endpoint` | Configures endpoint used by `SesClient`. | No |
| `spring.cloud.aws.ses.region` | Configures region used by `SesClient`. | No |
| `spring.cloud.aws.ses.source-arn` | Configures source ARN, used only for sending authorization. | No |
| `spring.cloud.aws.ses.from-arn` | Configures from ARN, used only for sending authorization in the SendRawEmail operation. | No |
| `spring.cloud.aws.ses.identity-arn` | Configures identity ARN, used only for sending authorization. | No |
| `spring.cloud.aws.ses.configuration-set-name` | The configuration set name used for every message | No |
|===

Expand All @@ -146,20 +145,16 @@ service will produce an error while using the mail service. Therefore, the regio
sender configuration. The example below shows a typical combination of a region (`EU-CENTRAL-1`) that does not provide
an SES service where the client is overridden to use a valid region (`EU-WEST-1`).

`sourceArn` is the ARN of the identity that is associated with the sending authorization policy. For information about when to use this parameter, see the
description see https://docs.aws.amazon.com/ses/latest/dg/sending-authorization-delegate-sender-tasks-email.html[Amazon SES Developer Guide].

`fromArn` is the ARN of the identity that is associated with the sending authorization policy that permits you to specify a particular "From" address in the header of the raw email.
For information about when to use this parameter, see the description see https://docs.aws.amazon.com/ses/latest/dg/sending-authorization-delegate-sender-tasks-email.html[Amazon SES Developer Guide].
`identityArn` is the ARN of the identity that is associated with the sending authorization policy. For information about when to use this parameter, see the
description see https://docs.aws.amazon.com/ses/latest/dg/sending-authorization-overview.html[https://docs.aws.amazon.com/ses/latest/dg/sending-authorization-delegate-sender-tasks-email.html][Amazon SES Developer Guide].

`configurationSetName` sets the configuration set name on mail sender level and applies to every mail. For information about when to use this parameter, see the
description https://docs.aws.amazon.com/ses/latest/dg/using-configuration-sets.html[Using configuration sets in Amazon SES].

[source,properties,indent=0]
----
spring.cloud.aws.ses.region=eu-west-1
spring.cloud.aws.ses.source-arn=arn:aws:ses:eu-west-1:123456789012:identity/example.com
spring.cloud.aws.ses.from-arn=arn:aws:ses:eu-west-1:123456789012:identity/example.com
spring.cloud.aws.ses.identity-arn=arn:aws:ses:eu-west-1:123456789012:identity/example.com
spring.cloud.aws.ses.configuration-set-name=ConfigSet
----

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
import org.springframework.context.annotation.Bean;
import org.springframework.mail.MailSender;
import org.springframework.mail.javamail.JavaMailSender;
import software.amazon.awssdk.services.ses.SesClient;
import software.amazon.awssdk.services.ses.SesClientBuilder;
import software.amazon.awssdk.services.sesv2.SesV2Client;
import software.amazon.awssdk.services.sesv2.SesV2ClientBuilder;

/**
* {@link EnableAutoConfiguration} for {@link SimpleEmailServiceMailSender} and
Expand All @@ -45,37 +45,38 @@
* @author Agim Emruli
* @author Eddú Meléndez
* @author Arun Patra
* @author Dominik Kovács
*/
@AutoConfiguration
@EnableConfigurationProperties(SesProperties.class)
@ConditionalOnClass({ SesClient.class, MailSender.class, SimpleEmailServiceJavaMailSender.class })
@ConditionalOnClass({ SesV2Client.class, MailSender.class, SimpleEmailServiceJavaMailSender.class })
@AutoConfigureAfter({ CredentialsProviderAutoConfiguration.class, RegionProviderAutoConfiguration.class })
@ConditionalOnProperty(name = "spring.cloud.aws.ses.enabled", havingValue = "true", matchIfMissing = true)
public class SesAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public SesClient sesClient(SesProperties properties, AwsClientBuilderConfigurer awsClientBuilderConfigurer,
ObjectProvider<AwsClientCustomizer<SesClientBuilder>> configurer,
public SesV2Client sesClient(SesProperties properties, AwsClientBuilderConfigurer awsClientBuilderConfigurer,
ObjectProvider<AwsClientCustomizer<SesV2ClientBuilder>> configurer,
ObjectProvider<AwsConnectionDetails> connectionDetails,
ObjectProvider<SesClientCustomizer> sesClientCustomizers,
ObjectProvider<AwsSyncClientCustomizer> awsSyncClientCustomizers) {
return awsClientBuilderConfigurer.configureSyncClient(SesClient.builder(), properties,
return awsClientBuilderConfigurer.configureSyncClient(SesV2Client.builder(), properties,
connectionDetails.getIfAvailable(), configurer.getIfAvailable(), sesClientCustomizers.orderedStream(),
awsSyncClientCustomizers.orderedStream()).build();
}

@Bean
@ConditionalOnMissingClass("jakarta.mail.Session")
public MailSender simpleMailSender(SesClient sesClient, SesProperties properties) {
return new SimpleEmailServiceMailSender(sesClient, properties.getSourceArn(),
public MailSender simpleMailSender(SesV2Client sesClient, SesProperties properties) {
return new SimpleEmailServiceMailSender(sesClient, properties.getIdentityArn(),
properties.getConfigurationSetName());
}

@Bean
@ConditionalOnClass(name = "jakarta.mail.Session")
public JavaMailSender javaMailSender(SesClient sesClient, SesProperties properties) {
return new SimpleEmailServiceJavaMailSender(sesClient, properties.getSourceArn(),
public JavaMailSender javaMailSender(SesV2Client sesClient, SesProperties properties) {
return new SimpleEmailServiceJavaMailSender(sesClient, properties.getIdentityArn(),
properties.getConfigurationSetName());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
package io.awspring.cloud.autoconfigure.ses;

import io.awspring.cloud.autoconfigure.AwsClientCustomizer;
import software.amazon.awssdk.services.ses.SesClientBuilder;
import software.amazon.awssdk.services.sesv2.SesV2ClientBuilder;

/**
* Callback interface that can be used to customize a {@link SesClientBuilder}.
* Callback interface that can be used to customize a {@link SesV2ClientBuilder}.
*
* @author Maciej Walkowiak
* @since 3.3.0
*/
@FunctionalInterface
public interface SesClientCustomizer extends AwsClientCustomizer<SesClientBuilder> {
public interface SesClientCustomizer extends AwsClientCustomizer<SesV2ClientBuilder> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
*
* @author Eddú Meléndez
* @author Arun Patra
* @author Dominik Kovács
*/
@ConfigurationProperties(prefix = SesProperties.PREFIX)
public class SesProperties extends AwsClientProperties {
Expand All @@ -34,48 +35,33 @@ public class SesProperties extends AwsClientProperties {
public static final String PREFIX = "spring.cloud.aws.ses";

/**
* Configures source ARN. Used only for sending authorization.
* Configures identity ARN. Used only for sending authorization.
*/
@Nullable
private String sourceArn;
private String identityArn;

/**
* Configures configuration set name.
*/
@Nullable
private String configurationSetName;

/**
* Configures from ARN. Only applies to SendRawEmail operation.
*/
@Nullable
private String fromArn;

@Nullable
public String getSourceArn() {
return sourceArn;
public String getIdentityArn() {
return identityArn;
}

@Nullable
public String getConfigurationSetName() {
return configurationSetName;
}

@Nullable
public String getFromArn() {
return fromArn;
}

public void setSourceArn(@Nullable String sourceArn) {
this.sourceArn = sourceArn;
public void setIdentityArn(@Nullable String identityArn) {
this.identityArn = identityArn;
}

public void setConfigurationSetName(@Nullable String configurationSetName) {
this.configurationSetName = configurationSetName;
}

public void setFromArn(@Nullable String fromArn) {
this.fromArn = fromArn;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.ses.SesClient;
import software.amazon.awssdk.services.ses.SesClientBuilder;
import software.amazon.awssdk.services.sesv2.SesV2Client;
import software.amazon.awssdk.services.sesv2.SesV2ClientBuilder;

/**
* Tests for class {@link SesAutoConfiguration}.
Expand All @@ -63,7 +63,7 @@ void mailSenderWithJavaMail() {

@Test
void mailSenderWithoutSesClientInTheClasspath() {
this.contextRunner.withClassLoader(new FilteredClassLoader(software.amazon.awssdk.services.ses.SesClient.class))
this.contextRunner.withClassLoader(new FilteredClassLoader(software.amazon.awssdk.services.sesv2.SesV2Client.class))
.run(context -> {
assertThat(context).doesNotHaveBean(MailSender.class);
assertThat(context).doesNotHaveBean(JavaMailSender.class);
Expand All @@ -89,7 +89,7 @@ void sesAutoConfigurationIsDisabled() {
@Test
void withCustomEndpoint() {
this.contextRunner.withPropertyValues("spring.cloud.aws.ses.endpoint:http://localhost:8090").run(context -> {
ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SesClient.class));
ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SesV2Client.class));
assertThat(client.getEndpoint()).isEqualTo(URI.create("http://localhost:8090"));
assertThat(client.isEndpointOverridden()).isTrue();
});
Expand All @@ -98,7 +98,7 @@ void withCustomEndpoint() {
@Test
void withCustomGlobalEndpoint() {
this.contextRunner.withPropertyValues("spring.cloud.aws.endpoint:http://localhost:8090").run(context -> {
ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SesClient.class));
ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SesV2Client.class));
assertThat(client.getEndpoint()).isEqualTo(URI.create("http://localhost:8090"));
assertThat(client.isEndpointOverridden()).isTrue();
});
Expand All @@ -108,7 +108,7 @@ void withCustomGlobalEndpoint() {
void withCustomGlobalEndpointAndSesEndpoint() {
this.contextRunner.withPropertyValues("spring.cloud.aws.endpoint:http://localhost:8090",
"spring.cloud.aws.ses.endpoint:http://localhost:9999").run(context -> {
ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SesClient.class));
ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SesV2Client.class));
assertThat(client.getEndpoint()).isEqualTo(URI.create("http://localhost:9999"));
assertThat(client.isEndpointOverridden()).isTrue();
});
Expand All @@ -117,7 +117,7 @@ void withCustomGlobalEndpointAndSesEndpoint() {
@Test
void customSesClientConfigurer() {
this.contextRunner.withUserConfiguration(CustomAwsClientConfig.class).run(context -> {
ConfiguredAwsClient sesClient = new ConfiguredAwsClient(context.getBean(SesClient.class));
ConfiguredAwsClient sesClient = new ConfiguredAwsClient(context.getBean(SesV2Client.class));
assertThat(sesClient.getApiCallTimeout()).isEqualTo(Duration.ofMillis(2000));
assertThat(sesClient.getSyncHttpClient()).isNotNull();
});
Expand All @@ -127,11 +127,11 @@ void customSesClientConfigurer() {
static class CustomAwsClientConfig {

@Bean
AwsClientCustomizer<SesClientBuilder> snsClientBuilderAwsClientConfigurer() {
AwsClientCustomizer<SesV2ClientBuilder> snsClientBuilderAwsClientConfigurer() {
return new CustomAwsClientConfig.SesAwsClientConfigurer();
}

static class SesAwsClientConfigurer implements AwsClientCustomizer<SesClientBuilder> {
static class SesAwsClientConfigurer implements AwsClientCustomizer<SesV2ClientBuilder> {
@Override
public ClientOverrideConfiguration overrideConfiguration() {
return ClientOverrideConfiguration.builder().apiCallTimeout(Duration.ofMillis(2000)).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.ses.SesClient;
import software.amazon.awssdk.services.sesv2.SesV2Client;

/**
* Tests for {@link SesClientCustomizer}.
Expand All @@ -48,7 +48,7 @@ class SesClientCustomizerTests {
@Test
void customClientCustomizer() {
contextRunner.withUserConfiguration(CustomizerConfig.class).run(context -> {
ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SesClient.class));
ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SesV2Client.class));
assertThat(client.getApiCallTimeout()).describedAs("sets property from first customizer")
.isEqualTo(Duration.ofMillis(2001));
assertThat(client.getApiCallAttemptTimeout()).describedAs("sets property from second customizer")
Expand All @@ -61,7 +61,7 @@ void customClientCustomizer() {
@Test
void customClientCustomizerWithOrder() {
contextRunner.withUserConfiguration(CustomizerConfigWithOrder.class).run(context -> {
ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SesClient.class));
ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SesV2Client.class));
assertThat(client.getApiCallTimeout())
.describedAs("property from the customizer with higher order takes precedence")
.isEqualTo(Duration.ofMillis(2001));
Expand Down
2 changes: 1 addition & 1 deletion spring-cloud-aws-ses/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>ses</artifactId>
<artifactId>sesv2</artifactId>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.ses.SesClient;
import software.amazon.awssdk.services.ses.model.RawMessage;
import software.amazon.awssdk.services.ses.model.SendRawEmailRequest;
import software.amazon.awssdk.services.ses.model.SendRawEmailResponse;
import software.amazon.awssdk.services.sesv2.SesV2Client;
import software.amazon.awssdk.services.sesv2.model.RawMessage;
import software.amazon.awssdk.services.sesv2.model.SendEmailRequest;
import software.amazon.awssdk.services.sesv2.model.SendEmailResponse;

/**
* {@link JavaMailSender} implementation that allows to send {@link MimeMessage} using the Simple E-Mail Service. In
Expand All @@ -55,6 +55,7 @@
* @author Agim Emruli
* @author Eddú Meléndez
* @author Arun Patra
* @author Dominik Kovács
* @since 1.0
*/
public class SimpleEmailServiceJavaMailSender extends SimpleEmailServiceMailSender implements JavaMailSender {
Expand All @@ -74,26 +75,17 @@ public class SimpleEmailServiceJavaMailSender extends SimpleEmailServiceMailSend
@Nullable
private FileTypeMap defaultFileTypeMap;

@Nullable
private String fromArn;

public SimpleEmailServiceJavaMailSender(SesClient sesClient) {
public SimpleEmailServiceJavaMailSender(SesV2Client sesClient) {
this(sesClient, null);
}

public SimpleEmailServiceJavaMailSender(SesClient sesClient, @Nullable String sourceArn) {
this(sesClient, sourceArn, null);
public SimpleEmailServiceJavaMailSender(SesV2Client sesClient, @Nullable String identityArn) {
this(sesClient, identityArn, null);
}

public SimpleEmailServiceJavaMailSender(SesClient sesClient, @Nullable String sourceArn,
public SimpleEmailServiceJavaMailSender(SesV2Client sesClient, @Nullable String identityArn,
@Nullable String configurationSetName) {
super(sesClient, sourceArn, configurationSetName);
}

public SimpleEmailServiceJavaMailSender(SesClient sesClient, @Nullable String sourceArn,
@Nullable String configurationSetName, @Nullable String fromArn) {
super(sesClient, sourceArn, configurationSetName);
this.fromArn = fromArn;
super(sesClient, identityArn, configurationSetName);
}

/**
Expand Down Expand Up @@ -215,19 +207,18 @@ public void send(MimeMessage mimeMessage) throws MailException {
public void send(MimeMessage... mimeMessages) throws MailException {
Assert.notNull(mimeMessages, "mimeMessages are required");
Map<Object, Exception> failedMessages = new HashMap<>();

for (MimeMessage mimeMessage : mimeMessages) {
try {
RawMessage rawMessage = createRawMessage(mimeMessage);
SendEmailRequest request = SendEmailRequest.builder().fromEmailAddressIdentityArn(getIdentityArn())
.configurationSetName(getConfigurationSetName())
.content(content -> content.raw(createRawMessage(mimeMessage))).build();

SendRawEmailResponse sendRawEmailResponse = getEmailService()
.sendRawEmail(SendRawEmailRequest.builder().sourceArn(getSourceArn()).fromArn(this.fromArn)
.configurationSetName(getConfigurationSetName()).rawMessage(rawMessage).build());
SendEmailResponse sendEmailResponse = getEmailService().sendEmail(request);

if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Message with id: {} successfully sent", sendRawEmailResponse.messageId());
LOGGER.debug("Message with id: {} successfully sent", sendEmailResponse.messageId());
}
mimeMessage.setHeader("Message-ID", sendRawEmailResponse.messageId());
mimeMessage.setHeader("Message-ID", sendEmailResponse.messageId());
}
catch (Exception e) {
// Ignore Exception because we are collecting and throwing all if any
Expand Down
Loading
Loading