Skip to content
Merged
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
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = "<h1>Test test test</h1>"
html = "<h1>Test test test</h1><img src=\"cid:testpng\">"
attachments = listOf(
Mail.Attachment(
file = File("test.pdf"),
name = "test.pdf"
)
)
inlineAttachments = listOf(
file = File("test.png"),
cidName = "testpng",
name = "test.png"
)
}.sendAsync()
````

Expand Down
5 changes: 2 additions & 3 deletions email-service/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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"]}")

Expand Down Expand Up @@ -144,4 +144,3 @@ jreleaser {
}
}
}

39 changes: 31 additions & 8 deletions email-service/src/main/kotlin/com/icerockdev/service/email/Mail.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) {

Expand All @@ -38,6 +38,9 @@ class Mail(private val coroutineScope: CoroutineScope?, private val config: SMTP
var fromEmail: String = ""
var charset = UTF_8
var attachments: List<Attachment> = emptyList()
var inlineAttachments: List<InlineAttachment> = emptyList()
var replyToEmail: String? = null
var replyToName: String? = null
private var isHtml: Boolean = false

private fun prepareEmail(): Email {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
}
Expand Down
54 changes: 54 additions & 0 deletions email-service/src/test/kotlin/EmailTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand Down Expand Up @@ -66,4 +69,55 @@ class EmailTest {
assertEquals(expected = 1, email.getHeaderValues("To").size)
assertTrue { email.getHeaderValues("To").contains("Test Person <to@icerockdev.com>") }
}

@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 = "<h1>Test Inline</h1><img src=\"cid:$cidName\">"

inlineAttachments = listOf(
Mail.InlineAttachment(imageFile, cidName, name)
)
}.sendAsync().join()

val emails = server.receivedEmails
val email = emails[0]

assertContains(email.body, "<img src=\"cid:$cidName\">")
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 = "<h1>Test test test</h1>"
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)
}
}
Binary file added email-service/src/test/resources/kotlin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading