Skip to content

Developer Guide

This section covers the data model, configuration reference, sending mechanics, and extension points available to developers integrating Email Manager into an application.

Data Model

Email Manager persists two core entities. An email holds the message metadata, recipient lists, and body. An attachment holds the binary content of a file associated with an email. Recipient lists (To, Cc, Bcc) are stored in dedicated collection tables to support multiple addresses per email.

---
config:
  layout: elk
---
erDiagram
    em_email {
        integer   email_id        PK
        varchar   senderAddress
        varchar   subject
        text      body
        boolean   html
        varchar   status
        timestamp createdInstant
        timestamp sentInstant
        text      stackTrace
    }
    em_attachment {
        integer   attachment_id   PK
        varchar   fileName
        varchar   fileExtension
        timestamp addedInstant
        blob      data
    }
    em_recipientAddresses {
        integer email_id          FK
        varchar recipientAddress
    }
    em_carbonCopyRecipients {
        integer email_id              FK
        varchar carbonCopyRecipients
    }
    em_blindCarbonCopyRecipients {
        integer email_id                  FK
        varchar blindCarbonCopyRecipients
    }

    em_email ||--o{ em_attachment              : "has"
    em_email ||--o{ em_recipientAddresses      : "sent to"
    em_email ||--o{ em_carbonCopyRecipients    : "cc'd to"
    em_email ||--o{ em_blindCarbonCopyRecipients : "bcc'd to"
Hold "Alt" / "Option" to enable pan & zoom

The html flag records whether the body must be delivered as HTML or as plain text. The stackTrace column holds the error trace of the last failed delivery attempt, and is cleared when a later attempt succeeds.

Database Schema

Email Manager manages six tables. The schema below represents the DDL generated by Hibernate for a standard relational database. Column names reflect the exact values declared in the entity annotations.

CREATE SEQUENCE em_email_seq
    START WITH 1 INCREMENT BY 50;

CREATE SEQUENCE em_attachment_seq
    START WITH 1 INCREMENT BY 50;

CREATE TABLE em_email (
    email_id        INTEGER      NOT NULL,
    senderAddress   VARCHAR(255) NOT NULL,
    subject         VARCHAR(255),
    body            TEXT,
    html            BOOLEAN      NOT NULL,
    status          VARCHAR(255),
    createdInstant  TIMESTAMP(6) NOT NULL,
    sentInstant     TIMESTAMP(6),
    stackTrace      TEXT,
    CONSTRAINT pk_em_email PRIMARY KEY (email_id)
);

CREATE TABLE em_attachment (
    attachment_id  INTEGER NOT NULL,
    fileName       VARCHAR(255),
    fileExtension  VARCHAR(255),
    addedInstant   TIMESTAMP(6),
    data           BLOB,
    CONSTRAINT pk_em_attachment PRIMARY KEY (attachment_id)
);

CREATE TABLE em_email_attachments (
    EmailEntity_email_id       INTEGER NOT NULL,
    attachments_attachment_id  INTEGER NOT NULL,
    CONSTRAINT pk_em_email_attachments
        PRIMARY KEY (attachments_attachment_id),
    CONSTRAINT fk_em_email_attachments_email
        FOREIGN KEY (EmailEntity_email_id) REFERENCES em_email (email_id),
    CONSTRAINT fk_em_email_attachments_attachment
        FOREIGN KEY (attachments_attachment_id) REFERENCES em_attachment (attachment_id)
);

CREATE TABLE em_recipientAddresses (
    email_id          INTEGER NOT NULL,
    recipientAddress  VARCHAR(255),
    CONSTRAINT fk_em_recipientaddresses_email
        FOREIGN KEY (email_id) REFERENCES em_email (email_id)
);

CREATE TABLE em_carbonCopyRecipients (
    email_id              INTEGER NOT NULL,
    carbonCopyRecipients  VARCHAR(255),
    CONSTRAINT fk_em_carboncopyrecipients_email
        FOREIGN KEY (email_id) REFERENCES em_email (email_id)
);

CREATE TABLE em_blindCarbonCopyRecipients (
    email_id                  INTEGER NOT NULL,
    blindCarbonCopyRecipients VARCHAR(255),
    CONSTRAINT fk_em_blindcarboncopyrecipients_email
        FOREIGN KEY (email_id) REFERENCES em_email (email_id)
);

The status column stores the string representation of the EmailStatus enum. The body and stackTrace columns are declared as TEXT so that neither the message body nor a full error trace is truncated; the exact type depends on the dialect (CLOB on H2 and Oracle, TEXT on PostgreSQL and MySQL). The data column in em_attachment stores raw binary content; its exact SQL type likewise depends on the dialect (BLOB on MySQL and H2, BYTEA on PostgreSQL). The email-to-attachment relationship uses the Hibernate-generated em_email_attachments join table, as the @OneToMany association carries no explicit @JoinColumn.

Module Overview

Email Manager is structured as six Maven modules following the AppJars layered architecture:

Module Artifact ID Description
Model appjars-email-manager-model DTOs, EmailStatus enum, filter and sort classes, auto-configuration
Business API appjars-email-manager-business EmailService interface
Business Impl appjars-email-manager-business-impl Service implementation, MailSenderService, DefaultMailSenderImpl
Data API appjars-email-manager-data EmailDao and AttachmentDao interfaces
Data Impl appjars-email-manager-data-impl JPA entities and DAO implementations
Flow UI appjars-email-manager-flow Vaadin view and route configuration

A monolithic application includes the three implementation modules (-business-impl, -data-impl, -flow). The API modules (-business, -data) are pulled in transitively.

Spring Auto-Configuration

Email Manager registers itself through Spring Boot's auto-configuration mechanism. The entry point is EmailManagerAutoConfiguration, declared in:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

This class scans the com.appjars.emailmanager package for all Spring components and JPA entities. The business implementation module pulls in spring-boot-starter-mail, which provides the JavaMailSender infrastructure. DefaultMailSenderImpl is registered automatically and configured via the spring.mail.* properties described below.

Configuration Properties

Mail Server

Property Default Description
spring.mail.host localhost SMTP server hostname
spring.mail.port 25 SMTP server port
spring.mail.protocol smtp Mail transport protocol
spring.mail.username SMTP authentication username
spring.mail.password SMTP authentication password
spring.mail.properties.mail.smtp.auth true Enable SMTP authentication
spring.mail.properties.mail.smtp.starttls.enable true Enable STARTTLS encryption

Email Manager

Property Default Description
com.appjars.emailmanager.url.views.emailcrudview em/list URL path of the email management view
com.appjars.emailmanager.from Comma-separated list of default sender addresses offered in the UI
email.task.cronexpression 0/30 * * * * * Cron expression controlling how often MailSenderService is triggered when scheduled via Process Manager
spring.servlet.multipart.max-file-size 1MB Maximum size of individual attachment uploads. Set to 100MB to allow large files
spring.servlet.multipart.max-request-size 10MB Maximum size of multipart requests. Should match max-file-size

Email Status Lifecycle

An email moves through a defined sequence of statuses from creation to delivery:

stateDiagram-v2
    [*] --> CREATED        : email saved
    CREATED --> SEND_PENDING    : added to queue
    SEND_PENDING --> SEND_SUCCESSFUL : sent successfully
    SEND_PENDING --> SEND_FAILED     : sending failed
    CREATED --> SEND_SUCCESSFUL      : sent immediately
    CREATED --> SEND_FAILED          : immediate send failed
Hold "Alt" / "Option" to enable pan & zoom
Status Description
CREATED The email has been saved but not yet submitted for sending
SEND_PENDING The email is queued and will be picked up by the background sender
SEND_SUCCESSFUL The email was accepted by the SMTP server
SEND_FAILED The sending attempt was rejected or produced an error

Emails can be sent immediately via EmailService.attemptSend(), which transitions directly from CREATED to SEND_SUCCESSFUL or SEND_FAILED. Alternatively, an email can be queued by setting its status to SEND_PENDING, allowing the background MailSenderService to process it in a later batch.

When a delivery attempt fails, the resulting stack trace is stored on the email and can be inspected from the management view. A subsequent successful attempt clears it.

Background Sending

MailSenderService is a Spring-managed Runnable that processes emails in SEND_PENDING status. It pages through them fifty at a time, calling EmailService.attemptSend() for each one, and stops once a page returns fewer than fifty results. The total number of emails processed and successfully sent is logged at the end of the run.

Under a free licence the run aborts as soon as the daily send limit is reached, leaving the remaining emails queued for the next day.

The most straightforward way to run MailSenderService on a schedule is to register it as a Process Manager task. Because it implements Runnable and is a Spring component, it appears automatically in the Processes view if Process Manager is also integrated:

// No additional code required — MailSenderService is a @Component Runnable
// and is discovered by Process Manager's task scanning automatically.

Alternatively, it can be invoked directly via a standard Spring @Scheduled method:

@Scheduled(cron = "${email.task.cronexpression:0/30 * * * * *}")
public void sendPendingEmails() {
    mailSenderService.run();
}

Service API

EmailService is the primary interface for managing emails. It extends CrudService and ValidationSupport and exposes the following methods specific to the module:

// Attempt to send an email immediately via SMTP
// Returns true if sent successfully, false if sending failed
boolean attemptSend(EmailDto email);

// Retrieve a paginated, filtered, and sorted stream of emails
Stream<EmailDto> getEmails(int offset, int limit, EmailFilter filter, List<EmailSort> sortOrder);

// Count emails matching a filter
Integer countEmails(int offset, int limit, EmailFilter filter);

// Return the most recently used recipient addresses, up to the requested amount
Set<String> getRecentlyUsedAddresses(int amount);

// Return the number of emails sent during the current day
int getTodayEmailCount();

attemptSend() is transactional: it updates the email status in the database regardless of whether the SMTP call succeeds or fails, ensuring the outcome is always persisted. On failure it also stores the stack trace on the email; on success it clears it.

The service implementation is registered under the bean name AJEmailServiceImpl, and the DAO implementation under AJEmailDaoImpl. Inject them with the matching @Qualifier when the application context contains more than one candidate:

public MyService(@Qualifier("AJEmailServiceImpl") EmailService emailService) {
    this.emailService = emailService;
}

Validation is applied on save and update. Both creation and update validators check that the sender address is a valid email format and that at least one recipient address is present and valid. Invalid addresses cause a validation error before any persistence or sending occurs.

Free Licence Restrictions

Without a valid licence, Email Manager runs in free mode with a limit of five emails sent per day. The counter covers successful deliveries only and resets at midnight in the server's default time zone.

Once the limit is reached:

  • EmailService.attemptSend() throws FreeLimitReachedException instead of contacting the SMTP server, and leaves the email in SEND_PENDING status so it can be sent once the counter resets.
  • MailSenderService stops its current run at the first FreeLimitReachedException.
  • In the management view, the Send now and Edit actions are disabled, and queued emails display a Limit reached badge in place of their status.

A restrictions bar above the grid shows the number of emails sent today against the limit whenever the module is running unlicensed. EmailService.getTodayEmailCount() exposes the same counter programmatically.

Customisation

Replacing the Mail Sender

DefaultMailSenderImpl is registered automatically and reads its configuration from the spring.mail.* properties. To use a custom JavaMailSender implementation — for example, one backed by a third-party mail API — define a bean that overrides it:

@Bean
@Primary
public JavaMailSender customMailSender() {
    // return a custom JavaMailSender implementation
}

Assigning a Router Layout

By default, Email Manager registers its view without a parent layout. To wrap it in the application's main layout, inject the RouteConfigurer bean using its qualifier and call setViewsRouterLayout in a @PostConstruct method:

@Autowired
@Qualifier("EmailManagerRouteConfigurer")
private RouteConfigurer routeConfigurer;

@PostConstruct
public void configure() {
    routeConfigurer.setViewsRouterLayout(MainLayout.class);
}

The @Qualifier is required because multiple AppJars may contribute a bean named RouteConfigurer.

Customising the View URL

The default URL path can be overridden in application.properties:

com.appjars.emailmanager.url.views.emailcrudview=myapp/emails