Skip to content

Handle SSL and TLS for SMTP/IMAP/POP #2573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 16, 2025
Merged
2 changes: 2 additions & 0 deletions server/libs/modules/components/email/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
dependencies {
implementation("org.eclipse.angus:angus-mail")
api(project(":server:libs:core:commons:commons-util"))

testImplementation("com.icegreen:greenmail:2.1.0")
testImplementation("com.icegreen:greenmail-junit5:2.1.0")
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-engine")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation(project(":server:libs:config:jackson-config"))

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@
import static com.bytechef.component.definition.Authorization.PASSWORD;
import static com.bytechef.component.definition.Authorization.USERNAME;
import static com.bytechef.component.definition.ComponentDsl.action;
import static com.bytechef.component.definition.ComponentDsl.integer;
import static com.bytechef.component.definition.ComponentDsl.option;
import static com.bytechef.component.definition.ComponentDsl.string;
import static com.bytechef.component.email.constant.EmailConstants.HOST;
import static com.bytechef.component.email.constant.EmailConstants.PORT;
import static com.bytechef.component.email.constant.EmailConstants.PROTOCOL;
import static com.bytechef.component.email.constant.EmailConstants.SSL;
import static com.bytechef.component.email.constant.EmailConstants.TLS;

import com.bytechef.commons.util.JsonUtils;
import com.bytechef.component.definition.ActionContext;
import com.bytechef.component.definition.ComponentDsl.ModifiableActionDefinition;
import com.bytechef.component.definition.Parameters;
import com.bytechef.component.definition.Property;
import com.bytechef.component.email.EmailProtocol;
import com.bytechef.component.email.commons.EmailUtils;
import jakarta.mail.Authenticator;
import jakarta.mail.Folder;
import jakarta.mail.Message;
Expand All @@ -39,11 +42,8 @@
import jakarta.mail.Session;
import jakarta.mail.Store;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

/**
* @author Igor Beslic
Expand All @@ -63,6 +63,21 @@ public class ReadEmailAction {
.title("Get Mail")
.description("Get emails from inbox.")
.properties(
integer(PORT)
.label("Port")
.description("Defines the port to connect to the email server.")
.required(true)
.defaultValue(25),
string(PROTOCOL)
.controlType(Property.ControlType.SELECT)
.defaultValue(EmailProtocol.imap.name())
.label("Protocol")
.description(
"Protocol defines communication procedure. IMAP allows receiving emails. POP3 is older protocol for receiving emails.")
.options(
option(EmailProtocol.imap.name(), EmailProtocol.imap.name(), "IMAP is used to receive email"),
option(EmailProtocol.pop3.name(), EmailProtocol.pop3.name(), "POP3 is used to receive email"))
.required(true),
string(FROM)
.label("From Email")
.description("From who the email was sent.")
Expand All @@ -79,20 +94,23 @@ protected static Object perform(
throws MessagingException, IOException {

Session session;
EmailProtocol emailProtocol = EmailProtocol.valueOf(connectionParameters.getRequiredString(PROTOCOL));
EmailProtocol emailProtocol = EmailProtocol.valueOf(inputParameters.getRequiredString(PROTOCOL));
int port = inputParameters.getRequiredInteger(PORT);

if (connectionParameters.containsKey(USERNAME)) {
session = Session.getInstance(getProperties(emailProtocol, connectionParameters), new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
connectionParameters.getRequiredString(USERNAME),
connectionParameters.getRequiredString(PASSWORD));
}
});

session =
Session.getInstance(EmailUtils.getMailSessionProperties(port, emailProtocol, connectionParameters),
new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
connectionParameters.getRequiredString(USERNAME),
connectionParameters.getRequiredString(PASSWORD));
}
});
} else {
session = Session.getInstance(getProperties(emailProtocol, connectionParameters));
session = Session.getInstance(
EmailUtils.getMailSessionProperties(port, emailProtocol, connectionParameters));
}

Store protocolStore = session.getStore(emailProtocol.name());
Expand All @@ -112,10 +130,10 @@ protected PasswordAuthentication getPasswordAuthentication() {

filtered[i] = new HashMap<>();

filtered[i].put(FROM, Arrays.toString(message.getFrom()));
filtered[i].put(TO, Arrays.toString(message.getRecipients(RecipientType.TO)));
filtered[i].put(CC, Arrays.toString(message.getRecipients(RecipientType.CC)));
filtered[i].put(BCC, Arrays.toString(message.getRecipients(RecipientType.BCC)));
filtered[i].put(FROM, JsonUtils.write(message.getFrom()));
filtered[i].put(TO, JsonUtils.write(message.getRecipients(RecipientType.TO)));
filtered[i].put(CC, JsonUtils.write(message.getRecipients(RecipientType.CC)));
filtered[i].put(BCC, JsonUtils.write(message.getRecipients(RecipientType.BCC)));
filtered[i].put(SUBJECT, message.getSubject());
filtered[i].put(CONTENT, message.getContent()
.toString());
Expand All @@ -128,29 +146,4 @@ protected PasswordAuthentication getPasswordAuthentication() {

return filtered;
}

private static Properties getProperties(EmailProtocol protocol, Parameters connectionParameters) {
Properties props = new Properties();

props.setProperty("mail.store.protocol", protocol.name());
props.setProperty("mail.debug", "true");

if (Objects.equals(connectionParameters.getBoolean(TLS), false)) {
props.put(String.format("mail.%s.starttls.enable", protocol), "true");
}

if (Objects.equals(connectionParameters.getBoolean(SSL), false)) {
props.put(String.format("mail.%s.ssl.enable", protocol), "true");
}

if (connectionParameters.containsKey(USERNAME)) {
props.put(String.format("mail.%s.user", protocol), connectionParameters.getRequiredString(USERNAME));
props.put(String.format("mail.%s.auth", protocol), true);
}

props.put(String.format("mail.%s.host", protocol), connectionParameters.getRequiredString(HOST));
props.put(String.format("mail.%s.port", protocol), connectionParameters.getRequiredInteger(PORT));

return props;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,17 @@
import static com.bytechef.component.definition.ComponentDsl.action;
import static com.bytechef.component.definition.ComponentDsl.array;
import static com.bytechef.component.definition.ComponentDsl.fileEntry;
import static com.bytechef.component.definition.ComponentDsl.integer;
import static com.bytechef.component.definition.ComponentDsl.string;
import static com.bytechef.component.email.constant.EmailConstants.HOST;
import static com.bytechef.component.email.constant.EmailConstants.PORT;
import static com.bytechef.component.email.constant.EmailConstants.TLS;

import com.bytechef.component.definition.ActionContext;
import com.bytechef.component.definition.ComponentDsl.ModifiableActionDefinition;
import com.bytechef.component.definition.FileEntry;
import com.bytechef.component.definition.Parameters;
import com.bytechef.component.definition.Property;
import com.bytechef.component.email.EmailProtocol;
import com.bytechef.component.email.commons.EmailUtils;
import jakarta.activation.DataHandler;
import jakarta.mail.Authenticator;
import jakarta.mail.Message;
Expand All @@ -48,11 +49,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

/**
* @author Ivica Cardic
* @author Igor Beslic
*/
public class SendEmailAction {

Expand All @@ -69,6 +69,11 @@ public class SendEmailAction {
.title("Send")
.description("Send an email to any address.")
.properties(
integer(PORT)
.label("Port")
.description("Defines the port to connect to the email server.")
.required(true)
.defaultValue(25),
string(FROM)
.label("From Email")
.description("From who to send the email.")
Expand Down Expand Up @@ -108,31 +113,23 @@ protected static Object perform(
Parameters inputParameters, Parameters connectionParameters, ActionContext context)
throws MessagingException, IOException {

Properties properties = new Properties();

properties.put("mail.smtp.host", connectionParameters.getRequiredString(HOST));
properties.put("mail.smtp.port", connectionParameters.getRequiredInteger(PORT));

if (Objects.equals(connectionParameters.getBoolean(TLS), true)) {
properties.put("mail.smtp.starttls.enable", "true");
// prop.put("mail.smtp.ssl.trust", MapUtils.getRequiredString(context.getConnectionParameters(), HOST));
}

int port = inputParameters.getRequiredInteger(PORT);
Session session;

if (connectionParameters.containsKey(USERNAME)) {
properties.put("mail.smtp.auth", true);

session = Session.getInstance(properties, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
connectionParameters.getRequiredString(USERNAME),
connectionParameters.getRequiredString(PASSWORD));
}
});
session =
Session.getInstance(EmailUtils.getMailSessionProperties(port, EmailProtocol.smtp, connectionParameters),
new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
connectionParameters.getRequiredString(USERNAME),
connectionParameters.getRequiredString(PASSWORD));
}
});
} else {
session = Session.getInstance(properties);
session = Session
.getInstance(EmailUtils.getMailSessionProperties(port, EmailProtocol.smtp, connectionParameters));
}

Message message = new MimeMessage(session);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2025 ByteChef
*
* 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.bytechef.component.email.commons;

import static com.bytechef.component.definition.Authorization.USERNAME;
import static com.bytechef.component.email.constant.EmailConstants.CRYPTOGRAPHIC_PROTOCOL;
import static com.bytechef.component.email.constant.EmailConstants.HOST;
import static com.bytechef.component.email.constant.EmailConstants.TLS;

import com.bytechef.component.definition.Parameters;
import com.bytechef.component.email.EmailProtocol;
import java.util.Properties;

/**
* @author Igor Beslic
*/
public class EmailUtils {

public static Properties
getMailSessionProperties(int port, EmailProtocol protocol, Parameters connectionParameters) {
Properties props = new Properties();

props.setProperty("mail.store.protocol", protocol.name());
props.setProperty("mail.debug", "true");

if (connectionParameters.containsKey(CRYPTOGRAPHIC_PROTOCOL)) {
if (TLS.contentEquals(connectionParameters.getRequiredString(CRYPTOGRAPHIC_PROTOCOL))) {
props.put(String.format("mail.%s.starttls.enable", protocol), "true");
} else {
props.put(String.format("mail.%s.ssl.enable", protocol), "true");
props.put(String.format("mail.%s.ssl.trust", protocol), connectionParameters.getRequiredString(HOST));
}
}

if (connectionParameters.containsKey(USERNAME)) {
props.put(String.format("mail.%s.user", protocol), connectionParameters.getRequiredString(USERNAME));
props.put(String.format("mail.%s.auth", protocol), true);
}

props.put(String.format("mail.%s.host", protocol), connectionParameters.getRequiredString(HOST));
props.put(String.format("mail.%s.port", protocol), port);

return props;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,14 @@
import static com.bytechef.component.definition.ComponentDsl.authorization;
import static com.bytechef.component.definition.ComponentDsl.bool;
import static com.bytechef.component.definition.ComponentDsl.connection;
import static com.bytechef.component.definition.ComponentDsl.integer;
import static com.bytechef.component.definition.ComponentDsl.option;
import static com.bytechef.component.definition.ComponentDsl.string;
import static com.bytechef.component.email.constant.EmailConstants.CRYPTOGRAPHIC_PROTOCOL;
import static com.bytechef.component.email.constant.EmailConstants.HOST;
import static com.bytechef.component.email.constant.EmailConstants.PORT;
import static com.bytechef.component.email.constant.EmailConstants.PROTOCOL;

import com.bytechef.component.definition.Authorization.AuthorizationType;
import com.bytechef.component.definition.ComponentDsl.ModifiableConnectionDefinition;
import com.bytechef.component.definition.Property;
import com.bytechef.component.email.EmailProtocol;
import com.bytechef.component.email.constant.EmailConstants;

/**
Expand All @@ -45,25 +42,20 @@ public class EmailConnection {
string(HOST)
.label("Host")
.required(true),
integer(PORT)
.label("Port")
.description("")
.required(true)
.defaultValue(25),
string(PROTOCOL)
bool(EmailConstants.TLS)
.label("Use TLS")
.description("If selected the connection will use TLS when connecting to server."),
string(CRYPTOGRAPHIC_PROTOCOL)
.controlType(Property.ControlType.SELECT)
.defaultValue(EmailProtocol.smtp.name())
.label("Protocol")
.label("Connection Security")
.description(
"Protocol defines communication procedure. SMTP allows sending emails, IMAP allows receiving emails. POP3 is older protocol for receiving emails.")
"Connection security activates cryptographic protocol to secure communication over a network.")
.options(
option(EmailProtocol.smtp.name(), EmailProtocol.smtp.name(), "sending email"),
option(EmailProtocol.imap.name(), EmailProtocol.imap.name(), "receive email"),
option(EmailProtocol.pop3.name(), EmailProtocol.pop3.name(), "receive email"))
.required(true),
bool(EmailConstants.TLS)
.label("Use TLS")
.description("If selected the connection will use TLS when connecting to server."))
option(EmailConstants.TLS, EmailConstants.TLS,
"Transport Layer Security is the modern, more secure replacement for SSL"),
option(EmailConstants.SSL, EmailConstants.SSL,
"Secure Sockets Layer is an older protocol that has been deprecated due to security vulnerabilities."))
.required(false))
.authorizationRequired(false)
.authorizations(
authorization(AuthorizationType.BASIC_AUTH)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class EmailConstants {
public static final String HOST = "host";
public static final String PORT = "port";
public static final String PROTOCOL = "protocol";
public static final String CRYPTOGRAPHIC_PROTOCOL = "cryptoProtocol";
public static final String TLS = "tls";
public static final String SSL = "ssl";
}
Loading
Loading