Developer Guide
This section covers the data model, configuration reference, sending mechanics, templates, and extension points available to developers integrating Email Manager into an application.
Data Model
Email Manager persists four entities in two independent groups.
The first group holds outgoing email. An email holds the message metadata, recipient lists, and body. An attachment holds the binary content of a file associated with an email, and carries a foreign key back to the email it belongs to. Recipient lists (To, Cc, Bcc) are stored in dedicated collection tables to support multiple addresses per email.
The second group holds templates. A template is a reusable subject and body, identified by its name and language. Its declared placeholders are children of the template, kept in display order, and are created, updated and removed with it. Emails keep no link to a template: applying a template copies the rendered subject and body into the email, so the two groups share no foreign key.
---
config:
layout: elk
---
erDiagram
em_email {
integer email_id PK
varchar sender_address
varchar subject
text body
boolean html
varchar status
timestamp created_instant
timestamp sent_instant
text failure_reason
}
em_attachment {
integer attachment_id PK
integer email_id FK
varchar file_name
varchar content_type
timestamp added_instant
blob data
}
em_recipient_addresses {
integer email_id FK
varchar recipient_address
}
em_carbon_copy_recipients {
integer email_id FK
varchar carbon_copy_recipient
}
em_blind_carbon_copy_recipients {
integer email_id FK
varchar blind_carbon_copy_recipient
}
em_template {
integer template_id PK
varchar name
varchar description
varchar locale
varchar subject
text body
boolean html
boolean active
timestamp created_instant
timestamp last_modified_instant
}
em_template_placeholder {
integer placeholder_id PK
integer template_id FK
varchar name
varchar label
varchar sample_value
boolean required
boolean html
integer display_order
}
em_email ||--o{ em_attachment : "has"
em_email ||--o{ em_recipient_addresses : "sent to"
em_email ||--o{ em_carbon_copy_recipients : "cc'd to"
em_email ||--o{ em_blind_carbon_copy_recipients : "bcc'd to"
em_template ||--o{ em_template_placeholder : "declares"
The html flag records whether the body must be delivered as HTML or as plain text. The failure_reason column holds the type and message of the last failed delivery attempt, and is cleared when a later attempt succeeds. It deliberately does not hold the full stack trace: a JavaMail trace carries the SMTP host, port, handshake detail and frequently the user name, and the failure reason is shown in the management view. The complete trace goes to the application log instead.
Database Schema
Email Manager manages seven tables: the five email tables, plus em_template and em_template_placeholder for templates. The schema below represents the DDL generated by Hibernate for a standard relational database. Every table and column is named explicitly in the entity annotations, so the names below do not depend on the naming strategy in force.
CREATE SEQUENCE em_email_seq
START WITH 1 INCREMENT BY 50;
CREATE SEQUENCE em_attachment_seq
START WITH 1 INCREMENT BY 50;
CREATE SEQUENCE em_template_seq
START WITH 1 INCREMENT BY 50;
CREATE SEQUENCE em_template_placeholder_seq
START WITH 1 INCREMENT BY 50;
CREATE TABLE em_email (
email_id INTEGER NOT NULL,
sender_address VARCHAR(255) NOT NULL,
subject VARCHAR(255),
body TEXT,
html BOOLEAN NOT NULL,
status VARCHAR(255),
created_instant TIMESTAMP(6) NOT NULL,
sent_instant TIMESTAMP(6),
failure_reason TEXT,
CONSTRAINT pk_em_email PRIMARY KEY (email_id)
);
CREATE TABLE em_attachment (
attachment_id INTEGER NOT NULL,
email_id INTEGER,
file_name VARCHAR(255),
content_type VARCHAR(255),
added_instant TIMESTAMP(6),
data BLOB,
CONSTRAINT pk_em_attachment PRIMARY KEY (attachment_id),
CONSTRAINT fk_em_attachment_email
FOREIGN KEY (email_id) REFERENCES em_email (email_id)
);
CREATE TABLE em_recipient_addresses (
email_id INTEGER NOT NULL,
recipient_address VARCHAR(255),
CONSTRAINT fk_em_recipient_addresses_email
FOREIGN KEY (email_id) REFERENCES em_email (email_id)
);
CREATE TABLE em_carbon_copy_recipients (
email_id INTEGER NOT NULL,
carbon_copy_recipient VARCHAR(255),
CONSTRAINT fk_em_carbon_copy_recipients_email
FOREIGN KEY (email_id) REFERENCES em_email (email_id)
);
CREATE TABLE em_blind_carbon_copy_recipients (
email_id INTEGER NOT NULL,
blind_carbon_copy_recipient VARCHAR(255),
CONSTRAINT fk_em_blind_carbon_copy_recipients_email
FOREIGN KEY (email_id) REFERENCES em_email (email_id)
);
CREATE TABLE em_template (
template_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
description VARCHAR(1000),
locale VARCHAR(35) NOT NULL,
subject VARCHAR(1000),
body TEXT,
html BOOLEAN NOT NULL,
active BOOLEAN NOT NULL,
created_instant TIMESTAMP(6) NOT NULL,
last_modified_instant TIMESTAMP(6) NOT NULL,
CONSTRAINT pk_em_template PRIMARY KEY (template_id),
CONSTRAINT uk_em_template_name_locale UNIQUE (name, locale)
);
CREATE TABLE em_template_placeholder (
placeholder_id INTEGER NOT NULL,
template_id INTEGER,
name VARCHAR(100) NOT NULL,
label VARCHAR(255),
sample_value VARCHAR(1000),
required BOOLEAN NOT NULL,
html BOOLEAN NOT NULL,
display_order INTEGER NOT NULL,
CONSTRAINT pk_em_template_placeholder PRIMARY KEY (placeholder_id),
CONSTRAINT fk_em_template_placeholder_template
FOREIGN KEY (template_id) REFERENCES em_template (template_id)
);
The status column stores the string representation of the EmailStatus enum. The body and failure_reason columns are declared as TEXT so that neither the message body nor a long error message 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 association is a unidirectional one-to-many with an explicit join column, so em_attachment carries the email_id foreign key and no join table is created.
On the template side, a template name is unique within a language: the uk_em_template_name_locale constraint covers the pair (name, locale). The locale column holds a language tag such as es or es-AR, and the empty string for the default variant. It is NOT NULL on purpose, because most databases let NULL values repeat in a unique index, which would allow two default variants with the same name. The body column is TEXT and holds the body exactly as written, placeholders included; it is sanitised only when it is rendered (see Templates). The template-to-placeholder association follows the same pattern as attachments: a unidirectional one-to-many with an explicit template_id join column, ordered by display_order, with placeholders removed when they are removed from their template.
Module Overview
Email Manager is structured as six Maven modules following the AppJars layered architecture:
| Module | Artifact ID | Description |
|---|---|---|
| Model | appjars-email-manager-model |
Email and template DTOs, EmailStatus enum, filter and sort classes, PlaceholderSyntax, PlaceholderDefinition, TemplateLimits, auto-configuration |
| Business API | appjars-email-manager-business |
EmailService and TemplateService interfaces, PlaceholderCatalog, MissingPlaceholderException, HtmlSanitizer |
| Business Impl | appjars-email-manager-business-impl |
Service implementations, MailSenderService, DefaultMailSenderImpl |
| Data API | appjars-email-manager-data |
EmailDao, AttachmentDao and TemplateDao interfaces |
| Data Impl | appjars-email-manager-data-impl |
JPA entities and DAO implementations |
| Flow UI | appjars-email-manager-flow |
Vaadin views for emails and templates, HTML body editor, route configuration |
A monolithic application includes the three implementation modules (-business-impl, -data-impl, -flow). The API modules (-business, -data) are pulled in transitively.
The flow module depends on org.parttio:hugerte-for-flow, the Vaadin add-on for the MIT-licensed HugeRTE editor, which provides the visual body editor. The business modules depend on org.jsoup:jsoup, which HtmlSanitizer uses.
Spring Auto-Configuration
Email Manager registers itself through Spring Boot's auto-configuration mechanism. It contributes two auto-configuration classes, each declared in its own module under:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
EmailManagerAutoConfiguration, in the model module, is the main entry point. It scans the com.appjars.emailmanager package for Spring components and JPA entities, excluding the classes annotated with @AutoConfiguration so that they are contributed once, through the imports file, rather than twice.
EmailManagerMailSenderAutoConfiguration, in the business implementation module, contributes the fallback mail sender. The business implementation module pulls in spring-boot-starter-mail, which provides the JavaMailSender infrastructure. This class is ordered after Spring Boot's own MailSenderAutoConfiguration and registers DefaultMailSenderImpl, built from the spring.mail.* properties described below, only when the application context contains no other JavaMailSender. An application that declares its own sender, or that lets Spring Boot build one from spring.mail.*, keeps it: the fallback backs off.
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 |
(empty) | SMTP authentication username |
spring.mail.password |
(empty) | SMTP authentication password |
spring.mail.properties.mail.smtp.auth |
false |
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.url.views.templatecrudview |
em/templates |
URL path of the template management view |
com.appjars.emailmanager.from |
— | Comma-separated list of default sender addresses offered in the UI |
com.appjars.emailmanager.attachments.max-size |
26214400 |
Maximum size in bytes of a single attachment (25 MB) |
com.appjars.emailmanager.attachments.max-files |
5 |
Maximum number of attachments per email |
com.appjars.emailmanager.attachments.accepted-types |
(see below) | Comma-separated list of accepted attachment content types |
spring.servlet.multipart.max-file-size |
1MB |
Maximum size of individual attachment uploads. Raise it to at least the value of attachments.max-size |
spring.servlet.multipart.max-request-size |
10MB |
Maximum size of multipart requests. Should match max-file-size |
The default value of com.appjars.emailmanager.attachments.accepted-types is:
application/pdf,
image/png,image/jpeg,image/gif,image/webp,image/svg+xml,
text/plain,text/csv,
application/zip,application/x-zip-compressed,
application/msword,
application/vnd.openxmlformats-officedocument.wordprocessingml.document,
application/vnd.ms-excel,
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,
application/vnd.ms-powerpoint,
application/vnd.openxmlformats-officedocument.presentationml.presentation
Setting the property to an empty value accepts any content type. All three bounds are enforced on the server after the file has been received, not only in the browser.
Templates
| Property | Default | Description |
|---|---|---|
com.appjars.emailmanager.templates.max-body-size |
262144 |
Maximum length of a template body, in characters (256 K). A longer body is rejected on save |
com.appjars.emailmanager.templates.max-rendered-size |
1048576 |
Maximum length of a rendered subject or body, in characters (1 M). Rendering fails rather than truncating |
com.appjars.emailmanager.templates.locales |
(empty) | Comma-separated list of language tags, such as en,es,es-AR, offered in the template details dialog. Empty offers only the default variant, and the Language field is hidden |
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
| 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 type and message of the failure are stored on the email and can be inspected from the management view. A subsequent successful attempt clears them.
Background Sending
MailSenderService is a Spring-managed Runnable that processes emails in SEND_PENDING status. Each round it reads the first fifty queued emails and calls EmailService.attemptSend() for each one. It always re-reads the first page rather than advancing an offset, because a sent email leaves the SEND_PENDING filter and the result set shrinks underneath the cursor, which would make an advancing offset skip over the emails that moved up. The run ends when a round comes back empty, when a whole round makes no progress, or when the free-tier limit is reached. 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. The schedule belongs to the host application; Email Manager reads no cron property of its own:
@Scheduled(cron = "${myapp.email.cron:0/30 * * * * *}")
public void sendPendingEmails() {
mailSenderService.run();
}
Templates
A template is a reusable subject and body with named placeholders, stored in the database. Administrators manage templates in the template management view and apply them from the email form; applications render them through TemplateService. Rendering produces a subject, a body and an HTML flag. The email built from them keeps no reference to the template, so editing or deleting a template never changes an email that already exists.
flowchart TD
A[Template] --> B{Required placeholders<br/>have values?}
B -- no --> X[MissingPlaceholderException]
B -- yes --> C[Substitute each placeholder<br/>with its formatted value]
C --> D{HTML template?}
D -- yes --> E[Escape each value, or sanitise it<br/>if declared HTML]
E --> F[Sanitise the whole body]
D -- no --> G[Insert values as written]
F --> H{Within max-rendered-size?}
G --> H
H -- no --> Y[IllegalStateException]
H -- yes --> I[RenderedTemplate:<br/>subject, body, html]
Placeholder Syntax
A placeholder is a name between double braces, such as {{customerName}}. Spaces are allowed inside the braces, so {{ customerName }} is the same placeholder. A name starts with a letter, followed by letters, digits or underscores ([A-Za-z][A-Za-z0-9_]*); the pattern is published as PlaceholderSyntax.NAME_REGEX and PlaceholderSyntax.TOKEN.
Dear {{customerName}},
Your order {{orderNumber}} shipped on {{shippedDate}}.
Text that does not match the pattern, such as {{ two words }} or a lone brace, is left exactly as written. There is no escape sequence for a literal {{. Templates have no logic: there are no conditionals, loops or expressions, and a placeholder is only ever replaced with a value.
A template may use a placeholder it does not declare. Declaring a placeholder adds a label, a sample value for the editor preview, and the Required and Value is HTML options; an undeclared placeholder is rendered from the supplied values like any other.
Value Rendering
Values are supplied as a Map<String, Object> keyed by placeholder name, and are formatted as follows:
- An absent key, a
nullvalue or a blank string renders empty, never as the raw token. - A
Numberis formatted with the number format of the template's locale. - A
java.util.Date,java.sql.Date,java.sql.Timeor ajava.timevalue with a date, a time or both is formatted with the medium localised date, time or date-time style of the template's locale. - A partial temporal value, such as
YearMonth, renders as itstoString(). - Anything else renders as
String.valueOf(value).
The locale is the template's language tag, or the server's default locale for the default variant. Instants, and any value carrying a time zone, are shown in the server's default time zone.
In the subject, line breaks in a value are replaced with spaces, so a value cannot inject a mail header. In a plain-text body, values are inserted as written.
HTML Bodies and Sanitising
In an HTML template, every value is HTML-escaped before it is inserted. A placeholder declared Value is HTML is inserted as markup instead, after sanitising; it is meant for values the application controls, such as a signature block. After substitution, the whole body is sanitised again, which removes, for example, a javascript: URL that arrives through a value.
HtmlSanitizer, in the business module, is the single sanitising policy. It allows the jsoup Safelist.relaxed() policy, plus the style, class, dir and lang attributes on every element, the layout attributes of tables, rows, cells and images, and target on links. Inline styles that can execute script (expression(, url(javascript:, -moz-binding, behavior: or behaviour:) are removed. The same policy is applied to rendered templates, to the HTML preview dialog, and to the rendered body shown when an HTML email is viewed. HtmlSanitizer.sanitise(String) is public, for applications that need the same policy.
Required Placeholders and Size Limits
render() and toEmail() throw MissingPlaceholderException when a placeholder declared Required has no value or a blank one. The exception names every missing placeholder at once, through getMissingPlaceholders(). renderPreview(), used by the editor, renders with each declared placeholder's sample value and does not enforce required placeholders.
A rendered subject or body longer than com.appjars.emailmanager.templates.max-rendered-size makes rendering fail with an IllegalStateException; the output is never truncated.
Language Variants
Several templates can share a name if each has a different language: the default variant (an empty locale), and one variant per language tag, such as es or es-AR. The tags offered in the editor are set with com.appjars.emailmanager.templates.locales.
TemplateService.getActiveTemplates(String locale) returns each active template name once, ordered by name, in its best variant for the given language tag. Tags are compared case-insensitively, and the variant is chosen in this order:
- The variant for the exact tag, such as
es-AR. - The variant for the language alone, such as
es. - The default variant.
A name with none of these variants is left out. The email form passes the user's UI locale, so the Apply template dialog offers each template in the user's language when a variant exists.
Validation on Save
TemplateService validates on save and on update. A template is rejected when:
- Its name is blank, or another template already uses the name in the same language.
- A declared placeholder name is not valid, or the same name is declared twice.
- In an HTML body, a placeholder is broken up by formatting, such as
{{na<b>me</b>}}, which would never be replaced. - The name is longer than 255 characters, the description or the subject longer than 1000, or a placeholder's name, label or sample value longer than 100, 255 and 1000 characters respectively. The limits are published as constants in
TemplateLimits. - The body is longer than
com.appjars.emailmanager.templates.max-body-size.
Each error carries a message key under appjars.emailmanager.templateservice.*, translated by the flow module's message bundle. The service sets the creation and last-modified instants itself, and renumbers the declared placeholders' display order on every save.
Rendering from Application Code
toEmail() renders a template and builds an email from the result, ready to be saved or sent through EmailService:
EmailDto email = templateService.toEmail(
template,
Map.of("customerName", "Ada", "orderNumber", "A-1"),
"shop@example.com",
Set.of("ada@example.com"));
emailService.save(email);
toEmail() neither saves nor queues the email. The email carries the rendered subject, body and HTML flag, and no reference to the template.
HTML Body Editor
The visual editor in the template editor and in the email form is HugeRTE, through the hugerte-for-flow add-on. Placeholders appear in it as chips that cannot be edited in part, and the stored body always contains the plain {{name}} token.
Note
The add-on serves the editor's JavaScript from the application's own /assets/ path, not from a CDN, so the editor works without outbound internet access. Vaadin's Spring Security integration permits that path by default. An application with a custom Spring Security configuration must permit /assets/** as well, or the editor does not load.
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);
// Place an email in the send queue
void enqueue(EmailDto email);
// Take an email out of the send queue
void dequeue(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
int countEmails(EmailFilter filter);
// Return the most recently used recipient addresses, inspecting the given number of emails
Set<String> getRecentlyUsedAddresses(int emailsToInspect);
// Return the number of emails sent during the current day
int getTodayEmailCount();
enqueue() and dequeue() move an email in and out of the send queue, so a caller does not have to assemble the status transition itself. getEmails() takes its sort order as a list of EmailSort, each built from an EmailSortProperty — a closed set of sortable properties rather than a free-form property name.
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 type and message of the failure on the email; on success it clears them.
TemplateService manages and renders templates. It also extends CrudService and ValidationSupport, and adds the following methods:
// Retrieve a paginated, filtered, and sorted stream of templates
Stream<TemplateDto> getTemplates(int offset, int limit, TemplateFilter filter, List<TemplateSort> sortOrder);
// Count templates matching a filter
int countTemplates(TemplateFilter filter);
// Return the best active variant of each template name for a language tag, ordered by name
List<TemplateDto> getActiveTemplates(String locale);
// Substitute the placeholders with the given values
// Throws MissingPlaceholderException if a required placeholder has no value
RenderedTemplate render(TemplateDto template, Map<String, Object> values);
// Render with each declared placeholder's sample value, without enforcing required placeholders
RenderedTemplate renderPreview(TemplateDto template);
// Render the template into a new, unsaved email
EmailDto toEmail(TemplateDto template, Map<String, Object> values, String senderAddress, Set<String> recipientAddresses);
// Return the placeholder names used in the subject and body, declared or not, in order of first use
Set<String> findUsedPlaceholders(TemplateDto template);
// Return the placeholders a caller must supply: the declared ones in display order, then the undeclared ones in use
List<TemplatePlaceholderDto> findInputs(TemplateDto template);
RenderedTemplate is a record with three components: subject(), body() and html(). getTemplates() takes its sort order as a list of TemplateSort, each built from a TemplateSortProperty. TemplateFilter matches templates whose name or description contains the given text, ignoring case, whose language tag is exactly the given one, and whose active status matches.
Every bean the module publishes is named through a constant in EmailManagerBeanNames. The email service implementation is registered under EmailManagerEmailServiceImpl (EMAIL_SERVICE) and its DAO under EmailManagerEmailDaoImpl (EMAIL_DAO). The template service implementation is registered under EmailManagerTemplateServiceImpl (TEMPLATE_SERVICE) and its DAO under EmailManagerTemplateDaoImpl (TEMPLATE_DAO). Inject them with the matching @Qualifier when the application context contains more than one candidate:
public MyService(
@Qualifier(EmailManagerBeanNames.EMAIL_SERVICE) EmailService emailService) {
this.emailService = emailService;
}
The implementation classes themselves are not public. Behaviour is replaced by publishing a competing EmailService, EmailDao, TemplateService, TemplateDao or JavaMailSender bean, not by subclassing.
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, exposed as the constant EmailManagerLimits.FREE_SENT_LIMIT. The counter covers successful deliveries only and resets at midnight in the server's default time zone.
Once the limit is reached:
EmailService.attemptSend()throwsFreeLimitReachedExceptioninstead of contacting the SMTP server, and leaves the email inSEND_PENDINGstatus so it can be sent once the counter resets.MailSenderServicestops its current run at the firstFreeLimitReachedException.- 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.
The daily allowance is claimed before the message is handed to the mail server and given back if the send does not happen, so concurrent callers cannot each read the same count and all send. The reservation is held per service instance: several application instances sharing one database are not serialised against each other.
Templates are not restricted in free mode: there is no limit on the number of templates, and the template management view and the Apply template action work the same with or without a licence. The daily limit applies when an email built from a template is sent.
Customisation
Replacing the Mail Sender
DefaultMailSenderImpl is a fallback: it is registered only when the application context contains no other JavaMailSender, and it reads its configuration from the spring.mail.* properties. To use a custom implementation — for example, one backed by a third-party mail API — declare it as a bean:
@Bean
public JavaMailSender customMailSender() {
// return a custom JavaMailSender implementation
}
No @Primary is needed: the condition on the fallback means it steps aside as soon as another JavaMailSender is present, whether the application declares it or Spring Boot's own mail auto-configuration builds it from spring.mail.*.
Offering Application Placeholders
An application can offer its own placeholder names in the editor of every template, so that template authors do not have to declare them by hand. Publish one or more PlaceholderCatalog beans; each one is shown as a group in the editor's placeholder list:
@Component
public class CustomerPlaceholders implements PlaceholderCatalog {
@Override
public String getGroupName() {
return "Customer";
}
@Override
public List<PlaceholderDefinition> getPlaceholders() {
return List.of(
new PlaceholderDefinition("customerName", "Customer name", "Ada Lovelace"),
new PlaceholderDefinition("orderNumber", "Order number", "A-1001"));
}
}
PlaceholderDefinition is a record of the placeholder name, the label shown in the editor, and the sample value used by the preview. A catalogue is editor metadata only: it does not supply values, which are still passed to render() or toEmail() for each email. When an author inserts a catalogue placeholder into a template, it is declared on that template with the catalogue's label and sample value.
No @Primary is needed: Email Manager injects every PlaceholderCatalog bean as a list, and none is required. The editor shows its Filter type selector only when at least one catalogue offers a placeholder.
Assigning a Router Layout
By default, Email Manager registers its views without a parent layout. To wrap them in the application's main layout, inject the RouteConfigurer bean using its qualifier and call setViewsRouterLayout in a @PostConstruct method. The call applies to both the email and the template management views:
@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 URLs
The default URL paths can be overridden in application.properties:
com.appjars.emailmanager.url.views.emailcrudview=myapp/emails
com.appjars.emailmanager.url.views.templatecrudview=myapp/email-templates
RouteConfigurer exposes both sides of each property. RouteConfigurer.URL_EMAIL_LIST_PROPERTY and RouteConfigurer.URL_TEMPLATE_LIST_PROPERTY are the unresolved property expressions, for use in an application's own @Value, and getEmailListsUrl() and getTemplateListUrl() return the routes they resolved to:
@Value(RouteConfigurer.URL_EMAIL_LIST_PROPERTY)
private String emailListUrl;
@Value(RouteConfigurer.URL_TEMPLATE_LIST_PROPERTY)
private String templateListUrl;