Skip to content

Commit

Permalink
make it possible to redefine from address in an implementation of Ema…
Browse files Browse the repository at this point in the history
…ilContent
  • Loading branch information
angryziber committed Jan 7, 2025
1 parent a268a0d commit 65206f8
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 22 deletions.
2 changes: 2 additions & 0 deletions smtp/src/EmailContent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import klite.i18n.Lang.translate
import org.intellij.lang.annotations.Language
import java.net.URI
import java.util.*
import javax.mail.internet.InternetAddress

open class EmailContent(val lang: String, val labelKey: String, val substitutions: Map<String, String> = emptyMap(), val actionUrl: URI? = null) {
open val subject get() = translate(lang, "emails.$labelKey.subject", substitutions)
open val body get() = translate(lang, "emails.$labelKey.body", substitutions)
open val actionLabel get() = translate(lang, "emails.$labelKey.action", substitutions)
open val from: InternetAddress? get() = null

override fun equals(other: Any?) = other is EmailContent && lang == other.lang && labelKey == other.labelKey && substitutions == other.substitutions && actionUrl == other.actionUrl
override fun hashCode() = Objects.hash(lang, labelKey, substitutions, actionUrl)
Expand Down
32 changes: 17 additions & 15 deletions smtp/src/EmailService.kt
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
package klite.smtp

import klite.*
import klite.i18n.Lang
import klite.i18n.Lang.translate
import java.util.*
import javax.activation.DataHandler
import javax.mail.Authenticator
import javax.mail.Message.RecipientType.*
import javax.mail.Message.RecipientType.CC
import javax.mail.Message.RecipientType.TO
import javax.mail.Part.ATTACHMENT
import javax.mail.PasswordAuthentication
import javax.mail.Session
import javax.mail.Transport
import javax.mail.internet.*
import javax.mail.util.ByteArrayDataSource
import kotlin.text.Charsets.UTF_8

interface EmailService {
fun send(to: Email, subject: String, body: String, bodyMimeType: String = MimeTypes.text, attachments: Map<String, ByteArray> = emptyMap(), cc: List<Email> = emptyList())
val defaultFrom: InternetAddress get() = InternetAddress(Config["MAIL_FROM"], Config.optional("MAIL_FROM_NAME", translate(Lang.available.first(), "title")))

fun send(to: Email, subject: String, body: String, bodyMimeType: String = MimeTypes.text, attachments: Map<String, ByteArray> = emptyMap(), cc: List<Email> = emptyList(), from: InternetAddress = defaultFrom)

fun send(to: Email, content: EmailContent, attachments: Map<String, ByteArray> = emptyMap(), cc: List<Email> = emptyList()) =
send(to, content.subject, content.fullHtml(), MimeTypes.html, attachments, cc)
send(to, content.subject, content.fullHtml(), MimeTypes.html, attachments, cc, content.from ?: defaultFrom)
}

class FakeEmailService: EmailService {
open class FakeEmailService: EmailService {
private val log = logger()
lateinit var lastSentEmail: String

override fun send(to: Email, subject: String, body: String, bodyMimeType: String, attachments: Map<String, ByteArray>, cc: List<Email>) {
override fun send(to: Email, subject: String, body: String, bodyMimeType: String, attachments: Map<String, ByteArray>, cc: List<Email>, from: InternetAddress) {
lastSentEmail = """
Email to $to, CC: $cc
Subject: $subject
Expand All @@ -35,8 +39,7 @@ class FakeEmailService: EmailService {
}
}

class RealEmailService(
internal val mailFrom: InternetAddress = InternetAddress(Config["MAIL_FROM"], Config.optional("MAIL_FROM_NAME")),
open class RealEmailService(
smtpUser: String? = Config.optional("SMTP_USER"),
smtpPort: String? = Config.optional("SMTP_PORT", "25"),
props: Properties = Properties().apply {
Expand All @@ -51,8 +54,8 @@ class RealEmailService(
},
private val session: Session = Session.getInstance(props, authenticator.takeIf { smtpUser != null })
): EmailService {
override fun send(to: Email, subject: String, body: String, bodyMimeType: String, attachments: Map<String, ByteArray>, cc: List<Email>) {
send(to, subject) {
override fun send(to: Email, subject: String, body: String, bodyMimeType: String, attachments: Map<String, ByteArray>, cc: List<Email>, from: InternetAddress) {
send(to, subject, from) {
cc.forEach { setRecipient(CC, InternetAddress(it.value)) }
if (attachments.isEmpty())
setBody(body, bodyMimeType)
Expand All @@ -71,13 +74,12 @@ class RealEmailService(
}
}

private fun MimePart.setBody(body: String, bodyMimeType: String) = setContent(body, "$bodyMimeType; charset=UTF-8")
private fun MimePart.setBody(body: String, bodyMimeType: String) = setContent(body, MimeTypes.withCharset(bodyMimeType))

private fun send(to: Email, subject: String, block: MimeMessage.() -> Unit) = MimeMessage(session).apply {
setFrom(mailFrom)
setRecipient(BCC, mailFrom)
protected fun send(to: Email, subject: String, from: InternetAddress, block: MimeMessage.() -> Unit) = MimeMessage(session).apply {
setFrom(from)
setRecipient(TO, InternetAddress(to.value))
setSubject(subject, UTF_8.name())
setSubject(subject, MimeTypes.textCharset.name())
block()
Transport.send(this)
}
Expand Down
13 changes: 6 additions & 7 deletions smtp/test/RealEmailServiceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import klite.Config
import klite.MimeTypes
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import javax.mail.Message.RecipientType.BCC
import javax.mail.Session
import javax.mail.internet.InternetAddress
import javax.mail.internet.MimeMessage
Expand All @@ -25,19 +25,18 @@ class RealEmailServiceTest {
service.send(email, subject = "Subject", body = "Body")
val message = slot<MimeMessage>()
val toAddress = InternetAddress(email.value)
verify { session.getTransport(toAddress).sendMessage(capture(message), arrayOf(toAddress, service.mailFrom)) }
expect(message.captured.from.toList()).toContainExactly(service.mailFrom)
expect(message.captured.getRecipients(BCC).toList()).toContainExactly(service.mailFrom)
verify { session.getTransport(toAddress).sendMessage(capture(message), arrayOf(toAddress)) }
expect(message.captured.from.toList()).toContainExactly(service.defaultFrom)
expect(message.captured.subject).toEqual("Subject")
expect(message.captured.contentType).toEqual("text/plain; charset=UTF-8")
expect(message.captured.content).toEqual("Body")
}

@Test fun `send html`() = runTest {
service.send(email, subject = "Subject", body = "<Body>", bodyMimeType = "text/html")
service.send(email, subject = "Subject", body = "<Body>", bodyMimeType = MimeTypes.html)
val message = slot<MimeMessage>()
val toAddress = InternetAddress(email.value)
verify { session.getTransport(toAddress).sendMessage(capture(message), arrayOf(toAddress, service.mailFrom)) }
verify { session.getTransport(toAddress).sendMessage(capture(message), arrayOf(toAddress)) }
expect(message.captured.contentType).toEqual("text/html; charset=UTF-8")
expect(message.captured.content).toEqual("<Body>")
}
Expand All @@ -46,7 +45,7 @@ class RealEmailServiceTest {
service.send(email, subject = "Subject", body = "Body", attachments = mapOf("hello.pdf" to ByteArray(0)))
val message = slot<MimeMessage>()
val toAddress = InternetAddress(email.value)
verify { session.getTransport(toAddress).sendMessage(capture(message), arrayOf(toAddress, service.mailFrom)) }
verify { session.getTransport(toAddress).sendMessage(capture(message), arrayOf(toAddress)) }
expect(message.captured.from.size).toEqual(1)
expect(message.captured.subject).toEqual("Subject")
expect(message.captured.getHeader("Content-Type")[0]).toStartWith("multipart/mixed")
Expand Down

0 comments on commit 65206f8

Please sign in to comment.