diff --git a/README.md b/README.md index f2e9d63..597cd86 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ repositories { } // Append dependency -implementation("com.icerockdev.service:email-service:0.5.2") +implementation("com.icerockdev.service:email-service:1.0.0") ```` ## Library usage @@ -37,13 +37,18 @@ implementation("com.icerockdev.service:email-service:0.5.2") fromName = "From Person" subject = "TEST EMAIL" to = mutableMapOf("to@icerockdev.com" to "Test Person") - html = "

Test test test

" + html = "

Test test test

" attachments = listOf( Mail.Attachment( file = File("test.pdf"), name = "test.pdf" ) ) + inlineAttachments = listOf( + file = File("test.png"), + cidName = "testpng", + name = "test.png" + ) }.sendAsync() ```` diff --git a/email-service/build.gradle.kts b/email-service/build.gradle.kts index aee9a4e..d3003dd 100644 --- a/email-service/build.gradle.kts +++ b/email-service/build.gradle.kts @@ -19,7 +19,7 @@ apply(plugin = "java") apply(plugin = "kotlin") group = "com.icerockdev.service" -version = "0.5.2" +version = "1.0.0" val sourcesJar by tasks.registering(Jar::class) { archiveClassifier.set("sources") @@ -33,7 +33,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${properties["coroutines_version"]}") // https://mvnrepository.com/artifact/org.apache.commons/commons-email - implementation("org.apache.commons:commons-email:${properties["common_email_version"]}") + implementation("org.apache.commons:commons-email2-jakarta:${properties["common_email2_version"]}") // logging implementation("ch.qos.logback:logback-classic:${properties["logback_version"]}") @@ -144,4 +144,3 @@ jreleaser { } } } - diff --git a/email-service/src/main/kotlin/com/icerockdev/service/email/Mail.kt b/email-service/src/main/kotlin/com/icerockdev/service/email/Mail.kt index a5c35c0..abfd06c 100644 --- a/email-service/src/main/kotlin/com/icerockdev/service/email/Mail.kt +++ b/email-service/src/main/kotlin/com/icerockdev/service/email/Mail.kt @@ -4,21 +4,21 @@ package com.icerockdev.service.email +import jakarta.activation.DataSource +import jakarta.activation.FileDataSource +import jakarta.activation.URLDataSource +import jakarta.mail.util.ByteArrayDataSource import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import org.apache.commons.mail.DefaultAuthenticator -import org.apache.commons.mail.Email -import org.apache.commons.mail.EmailConstants.UTF_8 -import org.apache.commons.mail.HtmlEmail +import org.apache.commons.mail2.core.EmailConstants.UTF_8 +import org.apache.commons.mail2.jakarta.DefaultAuthenticator +import org.apache.commons.mail2.jakarta.Email +import org.apache.commons.mail2.jakarta.HtmlEmail import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.File import java.net.URL -import javax.activation.DataSource -import javax.activation.FileDataSource -import javax.activation.URLDataSource -import javax.mail.util.ByteArrayDataSource class Mail(private val coroutineScope: CoroutineScope?, private val config: SMTPConfig) { @@ -38,6 +38,9 @@ class Mail(private val coroutineScope: CoroutineScope?, private val config: SMTP var fromEmail: String = "" var charset = UTF_8 var attachments: List = emptyList() + var inlineAttachments: List = emptyList() + var replyToEmail: String? = null + var replyToName: String? = null private var isHtml: Boolean = false private fun prepareEmail(): Email { @@ -76,6 +79,14 @@ class Mail(private val coroutineScope: CoroutineScope?, private val config: SMTP email.attach(attachment.dataSource, attachment.name, attachment.description) } + inlineAttachments.forEach { inline -> + email.embed(inline.dataSource, inline.name, inline.cidName) + } + + if (!replyToEmail.isNullOrBlank()) { + email.addReplyTo(replyToEmail, replyToName) + } + return email } @@ -114,6 +125,18 @@ class Mail(private val coroutineScope: CoroutineScope?, private val config: SMTP ) } + class InlineAttachment( + val dataSource: DataSource, + val cidName: String, + val name: String, + ) { + constructor(file: File, cidName: String, name: String) : this(FileDataSource(file), cidName, name) + constructor(url: URL, cidName: String, name: String) : this(URLDataSource(url), cidName, name) + constructor(data: ByteArray, cidName: String, name: String, charset: String = UTF_8) : this( + ByteArrayDataSource(data, "application/octet-stream;charset=$charset"), cidName, name + ) + } + private companion object { val LOGGER: Logger = LoggerFactory.getLogger(Mail::class.java) } diff --git a/email-service/src/test/kotlin/EmailTest.kt b/email-service/src/test/kotlin/EmailTest.kt index 9924754..75d763f 100644 --- a/email-service/src/test/kotlin/EmailTest.kt +++ b/email-service/src/test/kotlin/EmailTest.kt @@ -3,6 +3,7 @@ */ import com.dumbster.smtp.SimpleSmtpServer +import com.icerockdev.service.email.Mail import com.icerockdev.service.email.MailerService import com.icerockdev.service.email.SMTPConfig import kotlinx.coroutines.CoroutineScope @@ -12,8 +13,10 @@ import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before import org.junit.Test +import java.io.File import kotlin.test.assertContains import kotlin.test.assertEquals +import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -66,4 +69,55 @@ class EmailTest { assertEquals(expected = 1, email.getHeaderValues("To").size) assertTrue { email.getHeaderValues("To").contains("Test Person ") } } + + @Test + fun testSendWithInlineAttachments() = runBlocking { + val imageFile = File("src/test/resources/kotlin.png") + val cidName = "kotlin-name" + val name = "kotlinlogo" + + mailerService.compose().apply { + fromEmail = "from@icerockdev.com" + fromName = "From Person" + subject = "TEST EMAIL" + to = mutableMapOf("to@icerockdev.com" to "Test Person") + html = "

Test Inline

" + + inlineAttachments = listOf( + Mail.InlineAttachment(imageFile, cidName, name) + ) + }.sendAsync().join() + + val emails = server.receivedEmails + val email = emails[0] + + assertContains(email.body, "") + assertContains(email.body, "Content-ID: <$cidName>") + assertContains(email.body, "Content-Disposition: inline") + assertContains(email.body, "name=$name") + } + + @Test + fun testSendWithReplyTo() = runBlocking { + val replyToEmail = "replyto@icerockdev.com" + val replyToName = "Reply Person" + + mailerService.compose().apply { + fromEmail = "from@icerockdev.com" + fromName = "From Person" + subject = "TEST EMAIL" + to = mutableMapOf("to@icerockdev.com" to "Test Person") + html = "

Test test test

" + this.replyToEmail = replyToEmail + this.replyToName = replyToName + }.sendAsync().join() + + val emails = server.receivedEmails + assertEquals(1, emails.size) + val email = emails[0] + + val replyToHeader = email.getHeaderValue("Reply-To") + assertNotNull(replyToHeader) + assertEquals("$replyToName <$replyToEmail>", replyToHeader) + } } diff --git a/email-service/src/test/resources/kotlin.png b/email-service/src/test/resources/kotlin.png new file mode 100644 index 0000000..ac4a2da Binary files /dev/null and b/email-service/src/test/resources/kotlin.png differ diff --git a/gradle.properties b/gradle.properties index 85efe6e..d51793a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ logback_version=1.4.5 kotlin.code.style=official kotlin_version=1.7.21 coroutines_version=1.6.4 -common_email_version=1.5 +common_email2_version=2.0.0-M1