Emails Plugin
RESTHeartThe emails plugin (restheart-emails module) provides SMTP email sending capabilities to any RESTHeart plugin. It wraps the ermes-mail library and exposes a simple EmailSender interface via dependency injection.
|
Tip
|
Available since version 9.6.0. |
The plugin is used by restheart-accounts for transactional emails (registration verification, team invitations, password reset) and can be used by any custom plugin that needs to send emails.
|
Note
|
The restheart-emails module is bundled with RESTHeart. No separate installation is required — just enable and configure it below.
|
Configuration
The emails block in restheart.yml configures the SMTP connection. The plugin is disabled by default — set enabled: true to activate it.
emails:
enabled: true
# Display name used in the email "From" header.
app-name: "My App"
# From email address.
sender-email: noreply@example.com
# SMTP connection settings.
smtp-hostname: email-smtp.eu-central-1.amazonaws.com
smtp-port: 465 # 465 = SMTPS (implicit TLS); 587 = STARTTLS
smtp-username: AKIAxxxxxxxx
smtp-password: secret
# Optional: explicit SSL port (defaults to 465).
# ssl-port: 465
When enabled is false or the configuration block is absent, the plugin is inert: sendEmail() and sendEmailAsync() log a warning and return without sending. This ensures services continue to operate even when SMTP is not configured.
Usage in plugins
Inject the EmailSender provider using @Inject("emails"):
@RegisterPlugin(name = "myPlugin", description = "...")
public class MyPlugin implements JsonService {
@Inject("emails")
private EmailSender emails;
@Override
public void handle(JsonRequest req, JsonResponse res) {
if (emails.isEnabled()) {
// Non-blocking send — the response is not delayed by the SMTP round trip
emails.sendEmailAsync(
"user@example.com",
"John",
"Welcome!",
"<h1>Hello John</h1><p>Welcome to our app.</p>"
);
// Non-blocking send with per-request SMTP overrides
emails.sendEmailAsync(
req,
"user@example.com",
"John",
"Welcome!",
"<h1>Hello John</h1><p>Welcome to our app.</p>"
);
}
}
}
The EmailSender interface is defined in restheart-commons (org.restheart.emails.EmailSender), so your plugin only needs restheart-commons as a compile-time dependency — no dependency on restheart-emails or ermes-mail is required.
Interface
public interface EmailSender {
// Blocking — runs the SMTP transaction on the calling thread
void sendEmail(String to, String recipientName, String subject, String htmlBody);
// Blocking, reads per-request SMTP overrides from attached parameters
void sendEmail(Request<?> request, String to, String recipientName, String subject, String htmlBody);
// Non-blocking (default method) — dispatches the send on the shared virtual threads executor
void sendEmailAsync(String to, String recipientName, String subject, String htmlBody);
// Non-blocking (default method), reads per-request SMTP overrides from attached parameters
void sendEmailAsync(Request<?> request, String to, String recipientName, String subject, String htmlBody);
boolean isEnabled();
}
Execution model
|
Tip
|
The sendEmailAsync() methods are available since version 9.7.0.
|
sendEmail() is blocking: the whole SMTP transaction — TCP connect, TLS handshake, AUTH, DATA, QUIT — runs on the calling thread. A remote SMTP provider typically takes a few hundred milliseconds to over a second, and up to ermes-mail’s connection (10s) and socket (60s) timeouts when it is unresponsive.
sendEmailAsync() dispatches that work on RESTHeart’s shared virtual threads executor and returns immediately.
Choose between them based on where your code runs:
| Use | When |
|---|---|
|
Inside a service or a request-phase interceptor, where the SMTP round trip would otherwise be added to the response latency |
|
Inside a |
Delivery is best effort in both cases: EmailSender returns void and never throws. SMTP failures are logged by the plugin, so a failed email never breaks the calling request.
|
Note
|
The plugin configures ermes-mail with no internal thread pool, and EmailService holds no persistent SMTP connection: every send opens and closes its own connection. This makes the sender a plain value object, which is why per-request SMTP overrides are built on the fly rather than pooled or cached.
|
Per-request SMTP overrides
In multi-tenant deployments, SMTP settings can be overridden per request by attaching parameters to the request via request.attachParam(). The SmtpEmailSender reads these attached parameters before falling back to the static YAML configuration.
The following attached parameters are supported:
| Attached parameter | Description |
|---|---|
|
Override the "From" email address |
|
Override the "From" display name |
|
Override the SMTP hostname |
|
Override the SMTP port |
|
Override the SMTP username |
|
Override the SMTP password |
If at least one override parameter is present, the send uses the overridden values. Parameters not specified fall back to the static YAML configuration. If no override parameters are present, the static configuration is used as-is.
Example from an interceptor:
req.attachParam("override-emails-sender-email", "noreply@tenant-a.com");
req.attachParam("override-emails-smtp-hostname", "smtp.tenant-a.com");
emails.sendEmailAsync(req, "user@tenant-a.com", "User", "Subject", "<p>Body</p>");
sendEmailAsync(Request, …) reads the attached parameters on the calling thread before dispatching, so it is safe to use even though the exchange may be completed and recycled by the time the send actually runs.