Skip to content

Commit

Permalink
the ability to send multipart mime messages with base64 encoding (#209)
Browse files Browse the repository at this point in the history
* the ability to send multipart mime messages with base64 encoding

* the ability to send multipart mime messages with base64 encoding

* test, fixed sending the HTML body

* cosmetic

* removed usused import

* fixed violations
  • Loading branch information
musketyr authored Jan 16, 2024
1 parent 9cb63e8 commit e31a224
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 3 deletions.
13 changes: 13 additions & 0 deletions docs/guide/src/docs/asciidoc/ses.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ or
https://agorapulse.github.io/micronaut-aws-sdk/api/com/agorapulse/micronaut/aws/ses/SimpleEmailService.html[SimpleEmailService AWS SDK 1.x]
for the full reference.

==== Configuration
You can configure the SES client using `aws.ses` prefix in your `application.yml` file.
Use `aws.ses.use-base64-encoding-for-multipart-emails` to enable base64 encoding for HTML body of multipart emails.

[source,yaml,indent=0,subs="attributes"]
.application.yml
----
aws:
ses:
use-base64-encoding-for-multipart-emails: true
----


==== Testing
It is recommended just to mock the `SimpleEmailService` in your tests as it only contains single abstract method.

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ slug=agorapulse/micronaut-aws-sdk
group=com.agorapulse
micronautVersion = 4.2.0
micronautGradlePluginVersion = 4.2.0
gruVersion = 2.0.4
gruVersion = 2.0.5
awsSdkVersion = 1.12.299
awsSdk2Version = 2.18.40
testcontainersVersion = 1.17.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* limitations under the License.
*/
dependencies {
implementation group: 'org.testcontainers', name: 'localstack', version: testcontainersVersion
api group: 'org.testcontainers', name: 'localstack', version: testcontainersVersion
implementation 'com.amazonaws:aws-java-sdk-core'
implementation "software.amazon.awssdk:netty-nio-client:$kinesisClientV2Version"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ dependencies {
implementation 'javax.mail:mail:1.4.4'

testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation project(':micronaut-amazon-awssdk-integration-testing')
testImplementation "com.agorapulse:gru-micronaut:$gruVersion"
testImplementation 'io.micronaut:micronaut-jackson-databind'
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ private EmailDeliveryStatus sendEmailWithAttachment(TransactionalEmail email) th

BodyPart p = new MimeBodyPart();
p.setContent(email.getHtmlBody(), "text/html");
if (configuration.getUseBase64EncodingForMultipartEmails().orElse(false)) {
p.setHeader("Content-Transfer-Encoding", "base64");
}

mimeMultipart.addBodyPart(p);

for (TransactionalEmailAttachment attachment : email.getAttachments()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public class SimpleEmailServiceConfiguration extends DefaultRegionAndEndpointCon
private Optional<String> sourceEmail = Optional.empty();
private Optional<String> subjectPrefix = Optional.empty();

private Optional<Boolean> useBase64EncodingForMultipartEmails = Optional.empty();

public Optional<String> getSourceEmail() {
return sourceEmail;
}
Expand All @@ -44,4 +46,12 @@ public void setSubjectPrefix(Optional<String> subjectPrefix) {
this.subjectPrefix = subjectPrefix;
}

public Optional<Boolean> getUseBase64EncodingForMultipartEmails() {
return useBase64EncodingForMultipartEmails;
}

public void setUseBase64EncodingForMultipartEmails(Optional<Boolean> useBase64EncodingForMultipartEmails) {
this.useBase64EncodingForMultipartEmails = useBase64EncodingForMultipartEmails;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2018-2024 Agorapulse.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.agorapulse.micronaut.amazon.awssdk.ses

import com.agorapulse.gru.Gru
import com.agorapulse.gru.minions.JsonMinion
import com.agorapulse.micronaut.amazon.awssdk.itest.localstack.LocalstackContainerHolder
import com.fasterxml.jackson.databind.ObjectMapper
import io.micronaut.context.annotation.Property
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.inject.Inject
import software.amazon.awssdk.services.ses.SesClient
import spock.lang.Specification
import spock.lang.TempDir

import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SES

/**
* Tests for simple email service.
*/
@MicronautTest
@Property(name = 'aws.ses.use-base64-encoding-for-multipart-emails', value = 'true')
@Property(name = 'localstack.tag', value = '3.0.2')
class SimpleEmailServiceIntegrationSpec extends Specification {

@TempDir File tempDir

@Inject SimpleEmailService simpleEmailService
@Inject SesClient sesClient
@Inject LocalstackContainerHolder localstack
@Inject ObjectMapper objectMapper

void "send email with attachemnt and base64 settings"() {
given:
sesClient.verifyDomainIdentity { it.domain('groovycalamari.com') }

File file = new File(tempDir, 'test.testme')
file << 'Hello!'

when:
Map<String, String> customTags = [key1: 'value1', key2: 'value2']
TransactionalEmail transactionalEmail = SimpleEmailService.email {
subject 'Hi Paul'
from '[email protected]'
to '[email protected]'
htmlBody '<p>This is an example body</p>'
tags customTags
attachment {
filename 'test.testme'
filepath file.absolutePath
mimeType 'x-application/testme'
description 'An example file'
}
}

EmailDeliveryStatus status = simpleEmailService.send(transactionalEmail)

then:
status == EmailDeliveryStatus.STATUS_DELIVERED

when:
Gru gru = Gru.create(localstack.getEndpointOverride(SES).toString())

gru.test {
get('/_aws/ses')
expect {
json 'emails.json'
}
}

then:
gru.verify()

when:
String content = gru.squad.ask(JsonMinion) { JsonMinion minion -> minion.responseText }

then:
content

when:
Map messagesResponse = objectMapper.readValue(content, Map)
String rawData = messagesResponse['messages'][0]['RawData']
File emailFile = new File(tempDir, 'email.eml')
emailFile << rawData

then:
// you can inspect the file here using Desktop.getDesktop().open(emailFile)
noExceptionThrown()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"messages": [
{
"Id": "${json-unit:any-string}",
"Region": "us-east-1",
"Source": "[email protected]",
"RawData": "${json-unit:any-string}",
"Timestamp": "${json-unit:any-string}"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ class DefaultSimpleEmailService implements SimpleEmailService {

BodyPart p = new MimeBodyPart()
p.setContent(email.htmlBody, 'text/html')
mimeMultipart.addBodyPart(p)
if (configuration.useBase64EncodingForMultipartEmails.orElse(false)) {
p.setHeader('Content-Transfer-Encoding', 'base64')
}

for (TransactionalEmailAttachment attachment : email.attachments) {
if (!MimeType.isMimeTypeSupported(attachment.mimeType)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ class SimpleEmailServiceConfiguration extends DefaultRegionAndEndpointConfigurat

Optional<String> sourceEmail = Optional.empty()
Optional<String> subjectPrefix = Optional.empty()
Optional<Boolean> useBase64EncodingForMultipartEmails = Optional.empty()

}

0 comments on commit e31a224

Please sign in to comment.