Skip to content

Commit

Permalink
feat(mail-connector): Adding Dropdown for Outbound Connector to switc…
Browse files Browse the repository at this point in the history
…h content type between plain text and html (#3569)

* feat(smpt-outbound-connector): adding drop down to distinguish between mime type of plain text or html to send html templates

* fix: formating of code

* chore: generate template and use an enum and backwards compatible

* chore: adding multipart to dropdown and change content type to mutlipart mixed

* feat(email-connector): correct PR to add multipart and HTML in the body

* feat(email-connector):change labels

* feat(email-connector): make Multipart work correctly for email connector

* others(tests-email-connector): add a tests and correct a test function

* others(email-connector): fixes after rebase

* others(email-connector): fixes after rebase 2

---------

Co-authored-by: Mathias Vandaele <[email protected]>
  • Loading branch information
itsnuyen and mathias-vandaele authored Nov 25, 2024
1 parent bb91a09 commit 1aa3e05
Show file tree
Hide file tree
Showing 10 changed files with 355 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import jakarta.mail.Address;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.Multipart;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMessage;
import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -67,7 +69,31 @@ protected static List<String> getReceivers(Message message) {

protected static String getPlainTextBody(Message message) {
try {
return message.getContent().toString().trim();
if (message.getContent() instanceof Multipart multipart) {
for (int i = 0; i < multipart.getCount(); i++) {
MimeBodyPart bodyPart = (MimeBodyPart) multipart.getBodyPart(i);
if (bodyPart.isMimeType("text/plain")) {
return (String) bodyPart.getContent();
}
}
}
return null;
} catch (MessagingException | IOException e) {
throw new RuntimeException(e);
}
}

protected static String getHtmlBody(Message message) {
try {
if (message.getContent() instanceof Multipart multipart) {
for (int i = 0; i < multipart.getCount(); i++) {
MimeBodyPart bodyPart = (MimeBodyPart) multipart.getBodyPart(i);
if (bodyPart.isMimeType("text/html")) {
return (String) bodyPart.getContent();
}
}
}
return null;
} catch (MessagingException | IOException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@
import io.camunda.operate.CamundaOperateClient;
import io.camunda.operate.exception.OperateException;
import io.camunda.operate.model.ProcessDefinition;
import io.camunda.process.test.api.CamundaAssert;
import io.camunda.process.test.api.CamundaSpringProcessTest;
import io.camunda.zeebe.client.ZeebeClient;
import io.camunda.zeebe.model.bpmn.BpmnModelInstance;
import io.camunda.zeebe.model.bpmn.instance.Process;
import jakarta.mail.Flags;
import jakarta.mail.MessagingException;
import java.time.Duration;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.Executors;
Expand Down Expand Up @@ -70,6 +72,7 @@ public class InboundEmailTest extends BaseEmailTest {
@BeforeEach
public void beforeEach() {
super.reset();
CamundaAssert.setAssertionTimeout(Duration.ofSeconds(20));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
classes = {TestConnectorRuntimeApplication.class},
properties = {
"spring.main.allow-bean-definition-overriding=true",
"camunda.connector.polling.enabled=false",
},
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@CamundaSpringProcessTest
Expand Down Expand Up @@ -82,7 +83,7 @@ public void beforeEach() {
}

@Test
public void shouldSendSMTPEmail() {
public void shouldSendSMTPTextEmail() {
File elementTemplate =
ElementTemplate.from(ELEMENT_TEMPLATE_PATH)
.property("authentication.type", "simple")
Expand All @@ -96,6 +97,7 @@ public void shouldSendSMTPEmail() {
.property("smtpFrom", "[email protected]")
.property("smtpTo", "[email protected]")
.property("smtpSubject", "subject")
.property("contentType", "PLAIN")
.property("smtpBody", "content")
.property("resultExpression", RESULT_EXPRESSION_SEND_EMAIL)
.writeTo(new File(tempDir, "template.json"));
Expand All @@ -116,6 +118,42 @@ public void shouldSendSMTPEmail() {
assertThat(getPlainTextBody(message.getFirst())).isEqualTo("content");
}

@Test
public void shouldSendSMTPHtmlEmail() {
File elementTemplate =
ElementTemplate.from(ELEMENT_TEMPLATE_PATH)
.property("authentication.type", "simple")
.property("authentication.simpleAuthenticationUsername", "[email protected]")
.property("authentication.simpleAuthenticationPassword", "password")
.property("protocol", "smtp")
.property("data.smtpPort", getUnsecureSmtpPort())
.property("data.smtpHost", LOCALHOST)
.property("smtpCryptographicProtocol", "NONE")
.property("data.smtpActionDiscriminator", "sendEmailSmtp")
.property("smtpFrom", "[email protected]")
.property("smtpTo", "[email protected]")
.property("smtpSubject", "subject")
.property("contentType", "HTML")
.property("smtpHtmlBody", "<h1>content</h1>")
.property("resultExpression", RESULT_EXPRESSION_SEND_EMAIL)
.writeTo(new File(tempDir, "template.json"));

BpmnModelInstance model = getBpmnModelInstance("sendEmailTask");
BpmnModelInstance updatedModel = getBpmnModelInstance(model, elementTemplate, "sendEmailTask");
var result = getZeebeTest(updatedModel);

assertThat(result).isNotNull();
assertThat(result.getProcessInstanceEvent()).hasVariable("sent", true);

assertTrue(super.waitForNewEmails(5000, 1));
List<Message> message = List.of(super.getLastReceivedEmails());
assertThat(message).isNotNull();
assertThat(getSenders(message.getFirst())).hasSize(1).first().isEqualTo("[email protected]");
assertThat(getReceivers(message.getFirst())).hasSize(1).first().isEqualTo("[email protected]");
assertThat(getSubject(message.getFirst())).isEqualTo("subject");
assertThat(getHtmlBody(message.getFirst())).isEqualTo("<h1>content</h1>");
}

@Test
public void shouldListPop3Email() {

Expand Down
68 changes: 66 additions & 2 deletions connectors/email/element-templates/email-outbound-connector.json
Original file line number Diff line number Diff line change
Expand Up @@ -588,12 +588,45 @@
"tooltip" : "Email's subject",
"type" : "String"
}, {
"id" : "smtpBody",
"label" : "Email Content",
"id" : "contentType",
"label" : "ContentType",
"optional" : false,
"value" : "PLAIN",
"constraints" : {
"notEmpty" : true
},
"group" : "sendEmailSmtp",
"binding" : {
"name" : "data.smtpAction.contentType",
"type" : "zeebe:input"
},
"condition" : {
"allMatch" : [ {
"property" : "data.smtpActionDiscriminator",
"equals" : "sendEmailSmtp",
"type" : "simple"
}, {
"property" : "protocol",
"equals" : "smtp",
"type" : "simple"
} ]
},
"tooltip" : "Email's contentType",
"type" : "Dropdown",
"choices" : [ {
"name" : "PLAIN",
"value" : "PLAIN"
}, {
"name" : "HTML",
"value" : "HTML"
}, {
"name" : "HTML & Plaintext",
"value" : "MULTIPART"
} ]
}, {
"id" : "smtpBody",
"label" : "Email Text Content",
"optional" : false,
"feel" : "optional",
"group" : "sendEmailSmtp",
"binding" : {
Expand All @@ -602,6 +635,10 @@
},
"condition" : {
"allMatch" : [ {
"property" : "contentType",
"oneOf" : [ "PLAIN", "MULTIPART" ],
"type" : "simple"
}, {
"property" : "data.smtpActionDiscriminator",
"equals" : "sendEmailSmtp",
"type" : "simple"
Expand All @@ -613,6 +650,33 @@
},
"tooltip" : "Email's content",
"type" : "Text"
}, {
"id" : "smtpHtmlBody",
"label" : "Email Html Content",
"optional" : false,
"feel" : "optional",
"group" : "sendEmailSmtp",
"binding" : {
"name" : "data.smtpAction.htmlBody",
"type" : "zeebe:input"
},
"condition" : {
"allMatch" : [ {
"property" : "contentType",
"oneOf" : [ "HTML", "MULTIPART" ],
"type" : "simple"
}, {
"property" : "data.smtpActionDiscriminator",
"equals" : "sendEmailSmtp",
"type" : "simple"
}, {
"property" : "protocol",
"equals" : "smtp",
"type" : "simple"
} ]
},
"tooltip" : "Email's Html content",
"type" : "Text"
}, {
"id" : "pop3maxToBeRead",
"label" : "Maximum number of emails to be read",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,12 +593,45 @@
"tooltip" : "Email's subject",
"type" : "String"
}, {
"id" : "smtpBody",
"label" : "Email Content",
"id" : "contentType",
"label" : "ContentType",
"optional" : false,
"value" : "PLAIN",
"constraints" : {
"notEmpty" : true
},
"group" : "sendEmailSmtp",
"binding" : {
"name" : "data.smtpAction.contentType",
"type" : "zeebe:input"
},
"condition" : {
"allMatch" : [ {
"property" : "data.smtpActionDiscriminator",
"equals" : "sendEmailSmtp",
"type" : "simple"
}, {
"property" : "protocol",
"equals" : "smtp",
"type" : "simple"
} ]
},
"tooltip" : "Email's contentType",
"type" : "Dropdown",
"choices" : [ {
"name" : "PLAIN",
"value" : "PLAIN"
}, {
"name" : "HTML",
"value" : "HTML"
}, {
"name" : "HTML & Plaintext",
"value" : "MULTIPART"
} ]
}, {
"id" : "smtpBody",
"label" : "Email Text Content",
"optional" : false,
"feel" : "optional",
"group" : "sendEmailSmtp",
"binding" : {
Expand All @@ -607,6 +640,10 @@
},
"condition" : {
"allMatch" : [ {
"property" : "contentType",
"oneOf" : [ "PLAIN", "MULTIPART" ],
"type" : "simple"
}, {
"property" : "data.smtpActionDiscriminator",
"equals" : "sendEmailSmtp",
"type" : "simple"
Expand All @@ -618,6 +655,33 @@
},
"tooltip" : "Email's content",
"type" : "Text"
}, {
"id" : "smtpHtmlBody",
"label" : "Email Html Content",
"optional" : false,
"feel" : "optional",
"group" : "sendEmailSmtp",
"binding" : {
"name" : "data.smtpAction.htmlBody",
"type" : "zeebe:input"
},
"condition" : {
"allMatch" : [ {
"property" : "contentType",
"oneOf" : [ "HTML", "MULTIPART" ],
"type" : "simple"
}, {
"property" : "data.smtpActionDiscriminator",
"equals" : "sendEmailSmtp",
"type" : "simple"
}, {
"property" : "protocol",
"equals" : "smtp",
"type" : "simple"
} ]
},
"tooltip" : "Email's Html content",
"type" : "Text"
}, {
"id" : "pop3maxToBeRead",
"label" : "Maximum number of emails to be read",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@
import io.camunda.connector.email.outbound.protocols.actions.*;
import io.camunda.connector.email.response.*;
import jakarta.mail.*;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.*;
import jakarta.mail.search.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class JakartaEmailActionExecutor implements EmailActionExecutor {
Expand Down Expand Up @@ -257,7 +256,8 @@ private SendEmailResponse smtpSendEmail(
if (bcc.isPresent()) message.setRecipients(Message.RecipientType.BCC, bcc.get());
headers.ifPresent(stringObjectMap -> setMessageHeaders(stringObjectMap, message));
message.setSubject(smtpSendEmail.subject());
message.setText(smtpSendEmail.body());
Multipart multipart = getMultipart(smtpSendEmail);
message.setContent(multipart);
try (Transport transport = session.getTransport()) {
this.jakartaUtils.connectTransport(transport, authentication);
transport.sendMessage(message, message.getAllRecipients());
Expand All @@ -279,6 +279,31 @@ private void setMessageHeaders(Map<String, String> stringObjectMap, Message mess
});
}

private Multipart getMultipart(SmtpSendEmail smtpSendEmail) throws MessagingException {
Multipart multipart = new MimeMultipart();
switch (smtpSendEmail.contentType()) {
case PLAIN -> {
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(smtpSendEmail.body(), StandardCharsets.UTF_8.name());
multipart.addBodyPart(textPart);
}
case HTML -> {
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(smtpSendEmail.htmlBody(), JakartaUtils.HTML_CHARSET);
multipart.addBodyPart(htmlPart);
}
case MULTIPART -> {
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(smtpSendEmail.body(), StandardCharsets.UTF_8.name());
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(smtpSendEmail.htmlBody(), JakartaUtils.HTML_CHARSET);
multipart.addBodyPart(textPart);
multipart.addBodyPart(htmlPart);
}
}
return multipart;
}

private SearchTerm createSearchTerms(JsonNode jsonNode) throws AddressException {
List<SearchTerm> searchTerms = new ArrayList<>();
if (jsonNode.has("operator")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class JakartaUtils {

private static final Logger LOGGER = LoggerFactory.getLogger(JakartaUtils.class);
private static final String REGEX_PATH_SPLITTER = "[./]";
public static final String HTML_CHARSET = "text/html; charset=utf-8";

public Session createSession(Configuration configuration) {
return Session.getInstance(
Expand Down
Loading

0 comments on commit 1aa3e05

Please sign in to comment.