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
4 changes: 2 additions & 2 deletions sources/core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
<parent>
<artifactId>tools.dynamia.modules.email.parent</artifactId>
<groupId>tools.dynamia.modules</groupId>
<version>3.4.0</version>
<version>3.5.0</version>
</parent>

<artifactId>tools.dynamia.modules.email</artifactId>
<name>DynamiaModules - Email</name>
<version>3.4.0</version>
<version>3.5.0</version>
<url>https://www.dynamiasoluciones.com</url>


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

/*
* Copyright (C) 2023 Dynamia Soluciones IT S.A.S - NIT 900302344-1
* Colombia / South America
Expand All @@ -19,15 +18,42 @@
package tools.dynamia.modules.email;

/**
* Implement this class if you need listener sms message sending process. You also need annotated
* with @{@link tools.dynamia.integration.sterotypes.Listener}
* Listener interface for the SMS message sending lifecycle.
* <p>
* Implement this interface to receive callbacks during the SMS dispatch process. To enable discovery
* and registration by the integration framework, annotate your implementation class with
* {@link tools.dynamia.integration.sterotypes.Listener}.
* </p>
* <p>
* Typical implementations may log audit trails, collect metrics, or alter message metadata before
* sending. Note: method names reflect existing API and should not be changed for compatibility.
* </p>
*
* @author Mario Serrano Leones
*/
public interface SMSServiceListener {

/**
* Callback invoked right before an {@link SMSMessage} is sent.
* <p>
* Use this hook to validate, enrich, or log the message prior to delivery. Avoid long-running
* operations to prevent delaying the sending process.
* </p>
*
* @param message The SMS message about to be sent. Never null.
*/
void onMessageSending(SMSMessage message);

/**
* Callback invoked immediately after an {@link SMSMessage} has been sent.
* <p>
* Despite the name "Sended" kept for backward compatibility, this method indicates that the
* sending operation was executed. Implementations may record provider responses or update
* delivery tracking. This does not necessarily guarantee final delivery to the recipient.
* </p>
*
* @param message The SMS message that was processed by the sender. Never null.
*/
void onMessageSended(SMSMessage message);


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

/*
* Copyright (C) 2023 Dynamia Soluciones IT S.A.S - NIT 900302344-1
* Colombia / South America
Expand All @@ -18,113 +17,178 @@

package tools.dynamia.modules.email.services;

import org.springframework.transaction.annotation.Transactional;
import tools.dynamia.modules.email.EmailMessage;
import tools.dynamia.modules.email.EmailSendResult;
import tools.dynamia.modules.email.domain.EmailAccount;
import tools.dynamia.modules.email.domain.EmailAddress;
import tools.dynamia.modules.email.domain.EmailTemplate;

import java.util.concurrent.Future;
import java.util.concurrent.CompletableFuture;

/**
* Email service for sending emails
* Email service for sending emails and managing email-related resources.
* <p>
* This service defines the contract to send emails synchronously and asynchronously, resolve
* preferred/notification accounts, manage templates, log addresses, and control internal caches.
* Implementations may rely on Spring's async and scheduling infrastructure.
* </p>
*
* @author Mario Serrano Leones
*/
public interface EmailService {

/**
* Send email message asynchronously. Default implementation use Spring Scheduling and Async API. Make sure your
* application has {@link org.springframework.scheduling.annotation.EnableAsync} and
* {@link org.springframework.scheduling.annotation.EnableScheduling} configured.
* Sends an email message asynchronously.
* <p>
* Default implementations are expected to use Spring's {@code @EnableAsync} and scheduling facilities.
* Ensure your application has {@link org.springframework.scheduling.annotation.EnableAsync} and
* {@link org.springframework.scheduling.annotation.EnableScheduling} enabled so the task executor handles
* background execution properly.
* </p>
*
* @param message
* @param message Fully built {@link EmailMessage} to be sent, including recipients, subject, content,
* attachments, and any headers.
* @return a {@link CompletableFuture} that completes with an {@link EmailSendResult} indicating success or failure.
* The future is completed exceptionally if an unrecoverable error occurs during dispatch.
*/
Future<EmailSendResult> send(EmailMessage message);
CompletableFuture<EmailSendResult> send(EmailMessage message);

/**
* Build and send email message asynchronously. See {@link EmailService#send(EmailMessage)}
* Builds and sends an email message asynchronously based on simple inputs.
* <p>
* This is a convenience method that internally creates an {@link EmailMessage} with a single recipient,
* subject, and content, and then delegates to {@link #send(EmailMessage)}.
* </p>
*
* @param to
* @param subject
* @param content
* @param to Recipient email address (e.g., "user@example.com"). Must be a valid RFC 5322 address.
* @param subject Email subject line.
* @param content Email body content. Implementations may treat this as plain text or HTML depending on configuration.
* @return a {@link CompletableFuture} that completes with an {@link EmailSendResult} when sending finishes.
*/
Future<EmailSendResult> send(String to, String subject, String content);
CompletableFuture<EmailSendResult> send(String to, String subject, String content);

/**
* Returns the default notification {@link EmailAccount} to be used for system-generated emails
* in the current SaaS context.
*
* @return the configured notification email account, or {@code null} if none is configured.
*/
EmailAccount getNotificationEmailAccount();

/**
* Returns the notification {@link EmailAccount} associated with the given SaaS account id.
*
* @param accountId SaaS account identifier.
* @return the notification email account for the provided {@code accountId}, or {@code null} if not found.
*/
EmailAccount getNotificationEmailAccount(Long accountId);

/**
* Setup preferred email account in current SaaS account
* Sets the preferred {@link EmailAccount} for the current SaaS account.
* Implementations should persist this preference so subsequent calls to
* {@link #getPreferredEmailAccount()} return this account.
*
* @param account
* @param account The {@link EmailAccount} to set as preferred. Must be a valid, enabled account.
*/
void setPreferredEmailAccount(EmailAccount account);

/**
* Sends an email message synchronously and waits for the result.
* <p>
* Use this method when you need immediate feedback about delivery status. Consider timeouts and
* potential blocking behavior in your calling thread.
* </p>
*
* @param mailMessage Fully built {@link EmailMessage} to be sent.
* @return the {@link EmailSendResult} containing delivery details, success flag, and error information if any.
*/
EmailSendResult sendAndWait(EmailMessage mailMessage);

/**
* Get preferred email account in current SaaS account
* Returns the preferred {@link EmailAccount} for the current SaaS account.
*
* @return
* @return the preferred account, or {@code null} if none has been configured.
*/
EmailAccount getPreferredEmailAccount();

/**
* Get preferred email account in SaaS account ID
* Returns the preferred {@link EmailAccount} for the specified SaaS account id.
*
* @return
* @param accountId SaaS account identifier.
* @return the preferred email account for {@code accountId}, or {@code null} if not configured.
*/
EmailAccount getPreferredEmailAccount(Long accountId);

/**
* Find email template by name in current SaaS account. If autocreate is true a new blank email template is created
* Finds an {@link EmailTemplate} by name within the current SaaS account.
* If {@code autocreate} is {@code true} and the template does not exist, a new blank template will be created
* and returned.
*
* @param name
* @param autocreate
* @return
* @param name Template name to search for.
* @param autocreate Whether to create a new template if one is not found.
* @return the existing or newly created {@link EmailTemplate}, or {@code null} if not found and {@code autocreate}
* is {@code false}.
*/
EmailTemplate getTemplateByName(String name, boolean autocreate);

/**
* Finds an {@link EmailTemplate} by name for a specific SaaS account id.
* If {@code autocreate} is {@code true} and the template does not exist, a new blank template will be created
* under that account.
*
* @param name Template name to search for.
* @param autocreate Whether to create a new template if one is not found.
* @param accountId SaaS account identifier.
* @return the existing or newly created {@link EmailTemplate}, or {@code null} if not found and {@code autocreate}
* is {@code false}.
*/
EmailTemplate getTemplateByName(String name, boolean autocreate, Long accountId);

/**
* Find email template by name
* Finds an {@link EmailTemplate} by name using default lookup rules for the current SaaS account.
*
* @param name
* @return
* @param name Template name to search for.
* @return the matching {@link EmailTemplate}, or {@code null} if none exists.
*/
EmailTemplate getTemplateByName(String name);

/**
* Log all email address from message
* Logs all email addresses present in the given {@link EmailMessage}.
* <p>
* Implementations should inspect TO, CC, BCC, REPLY-TO (and possibly FROM) fields and persist or update
* a registry of known {@link EmailAddress} entries associated with the supplied {@link EmailAccount}.
* </p>
*
* @param message
* @param account The {@link EmailAccount} context in which addresses will be logged.
* @param message The {@link EmailMessage} whose addresses should be extracted and logged.
*/
void logEmailAddress(EmailAccount account, EmailMessage message);

/**
* Log email address
* Logs a single email address with an optional tag for classification.
*
* @param address
* @param tag
* @param account The {@link EmailAccount} context used to associate the address entry.
* @param address A valid email address string to log.
* @param tag A marker or label (e.g., "customer", "supplier", "notification") to categorize the address.
*/
void logEmailAddress(EmailAccount account, String address, String tag);

/**
* Find a logged email address
* Retrieves a previously logged {@link EmailAddress} by its string representation.
*
* @param address
* @return
* @param address Email address string to look up.
* @return the {@link EmailAddress} entity if found; otherwise {@code null}.
*/
EmailAddress getEmailAddress(String address);

/**
* Clears the mail sender cache for the specified email account.
* Clears the internal mail-sender cache for the specified {@link EmailAccount}.
* <p>
* Implementations that cache mail sender instances (e.g., JavaMailSender) should discard and recreate them
* after configuration changes like credentials, host, or port updates.
* </p>
*
* @param account The email account for which to clear the cache.
* @param account The email account whose cache entries should be invalidated. Must not be {@code null}.
*/
void clearCache(EmailAccount account);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,35 @@
import tools.dynamia.modules.email.OTPMessage;
import tools.dynamia.modules.email.OTPSendResult;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import: java.util.concurrent.Future is imported but no longer used in this interface. The return type has been changed to CompletableFuture and this import should be removed.

Suggested change
import java.util.concurrent.Future;

Copilot uses AI. Check for mistakes.

/**
* Service interface to dispatch One-Time Password (OTP) messages.
* <p>
* Implementations may deliver OTP codes via email, SMS, or both, depending on configuration and
* the contents of the provided {@link OTPMessage}. The service abstracts transport selection,
* formatting, and delivery tracking, returning an asynchronous handle to inspect the result.
* </p>
* <p>
* Typical usage: build an {@link OTPMessage} with target channels and the OTP value, then call {@link #send(OTPMessage)}.
* </p>
*/
public interface OTPService {


/**
* Send OTP message using email, sms or both service
* Sends an OTP message using email, SMS, or both channels.
* <p>
* The selected transport(s) depend on implementation and the {@link OTPMessage} fields (e.g., presence of
* email address, phone number, or explicit channel selection). Delivery may happen asynchronously; use the
* returned {@link Future} to check completion and obtain the {@link OTPSendResult}.
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent documentation: The javadoc references {@link Future} but the actual return type is CompletableFuture. The javadoc should be updated to {@link CompletableFuture} for accuracy.

Copilot uses AI. Check for mistakes.
* </p>
*
* @param message
* @return
* @param message Fully populated {@link OTPMessage} containing the OTP code, target recipient(s), preferred
* channel(s), expiration and metadata as required by the implementation. Must not be null.
* @return a {@link Future} that completes with {@link OTPSendResult} indicating success, failure, and relevant
* details (e.g., which channels were used). The future may complete exceptionally if an unrecoverable error occurs.
Comment on lines +32 to +33
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent documentation: The javadoc references {@link Future} but the actual return type is CompletableFuture. The javadoc should be updated to {@link CompletableFuture} for accuracy.

Copilot uses AI. Check for mistakes.
*/
Future<OTPSendResult> send(OTPMessage message);
CompletableFuture<OTPSendResult> send(OTPMessage message);

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,27 @@
import tools.dynamia.modules.email.SMSMessage;

/**
* Simple SMS sender service
* Simple SMS sender service.
* <p>
* Defines the contract to dispatch SMS messages using the configured provider. Implementations
* are responsible for formatting, transport selection, and returning a provider-specific response
* string that may include tracking identifiers or delivery status notes.
* </p>
*/
public interface SMSService {

/**
* Sends an SMS message.
* <p>
* Implementations should validate destination numbers and message length according to provider limits
* (e.g., GSM-7 vs. Unicode, segmentation). The returned value is the raw response from the underlying
* SMS provider and can be used for logging or troubleshooting.
* </p>
*
* @param message the SMS message to be sent
* @return the response from the SMS service
* @param message The {@link SMSMessage} to be sent, containing destination number, text content, and optional metadata.
* Must not be {@code null}.
* @return The response string from the SMS service/provider. The format depends on the implementation and provider;
* may include message ID or delivery status information.
*/
String send(SMSMessage message);
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ public EmailServiceImpl(TemplateEngine templateEngine, CrudService crudService,
private final LoggingService logger = new SLF4JLoggingService(EmailService.class);

@Override
public Future<EmailSendResult> send(String to, String subject, String content) {
public CompletableFuture<EmailSendResult> send(String to, String subject, String content) {
return send(new EmailMessage(to, subject, content));
}

public Future<EmailSendResult> send(final EmailMessage mailMessage) {
public CompletableFuture<EmailSendResult> send(final EmailMessage mailMessage) {
try {

loadEmailAccount(mailMessage);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package tools.dynamia.modules.email.services.impl;

import tools.dynamia.integration.scheduling.SchedulerUtil;
import tools.dynamia.integration.scheduling.TaskWithResult;
import tools.dynamia.integration.sterotypes.Service;
import tools.dynamia.modules.email.EmailMessage;
import tools.dynamia.modules.email.OTPMessage;
Expand All @@ -12,7 +11,7 @@
import tools.dynamia.modules.email.services.OTPService;
import tools.dynamia.modules.email.services.SMSService;

import java.util.concurrent.Future;
import java.util.concurrent.CompletableFuture;

@Service
public class OTPServiceImpl implements OTPService {
Expand All @@ -26,7 +25,7 @@ public OTPServiceImpl(EmailService emailService, SMSService smsService) {
}

@Override
public Future<OTPSendResult> send(OTPMessage message) {
public CompletableFuture<OTPSendResult> send(OTPMessage message) {
final var emailAccount = message.getAccountId() != null ? emailService.getPreferredEmailAccount(message.getAccountId()) : emailService.getPreferredEmailAccount();
final var emailMessage = buildEmailMessage(emailAccount, message);
final var smsMessage = buildSMSMessage(emailAccount, message);
Expand Down
Loading