Send Email from Spring Boot: JavaMailSender SMTP or RestClient to the API
Send email from Spring Boot on your own domain: spring.mail properties for port 587 with STARTTLS and JavaMailSender, or a RestClient bean posting to the API.
Spring Boot auto-configures JavaMailSender from a handful of spring.mail properties, so the SMTP route is five lines of configuration and one injected bean. The HTTPS API fits better when the send is part of a transaction you want to record: the response carries an id, nothing holds a socket open, and the only outbound port is 443. Here are both, with the secret coming from the environment rather than application.properties.
Quick answer
For SMTP: spring.mail.host=mail.oqumail.com, spring.mail.port=587, the mailbox login as username and password, and mail.smtp.auth and mail.smtp.starttls.enable both true. For the API: a RestClient bean with the base URL and a Bearer header read from OQUMAIL_API_KEY, posting a record with to as a list.
Option 1: JavaMailSender
# application.properties
spring.mail.host=mail.oqumail.com
spring.mail.port=587
spring.mail.username=app@yourdomain.com
spring.mail.password=${OQUMAIL_SMTP_PASSWORD}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.smtp.connectiontimeout=10000
spring.mail.properties.mail.smtp.timeout=10000The service builds a MIME message so HTML and a plain-text alternative both go out:
@Service
public class MailService {
private final JavaMailSender sender;
public MailService(JavaMailSender sender) { this.sender = sender; }
public void sendWelcome(String to, String name) throws MessagingException {
MimeMessage message = sender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom("app@yourdomain.com", "Acme");
helper.setTo(to);
helper.setSubject("Welcome to Acme");
helper.setText("Hi " + name + ", your account is ready.",
"<p>Hi " + name + ", your account is ready.</p>");
sender.send(message);
}
}Option 2: RestClient to the API
Spring 6.1 and Boot 3.2 ship RestClient; on older versions use WebClient or RestTemplate the same way. Export OQUMAIL_API_KEY in the environment of the service:
// OquMailConfig.java
@Configuration
public class OquMailConfig {
@Bean
RestClient oquMailClient(@Value("${OQUMAIL_API_KEY}") String apiKey) {
return RestClient.builder()
.baseUrl("https://api.oqumail.com/api/v1")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
// OquMailClient.java
@Component
public class OquMailClient {
public record SendRequest(List<String> to, String subject, String html, String text, String fromName) {}
public record Receipt(String id, String messageId, String status) {}
private final RestClient client;
public OquMailClient(RestClient oquMailClient) { this.client = oquMailClient; }
public Receipt send(SendRequest request) {
return client.post()
.uri("/emails")
.body(request)
.retrieve()
.onStatus(s -> s.value() == 429 || s.value() == 403,
(req, res) -> { throw new RetryLaterException(res.getStatusText()); })
.onStatus(HttpStatusCode::isError,
(req, res) -> { throw new IllegalStateException("send failed " + res.getStatusCode()); })
.body(Receipt.class);
}
}
// usage, inside an @Async or a queue consumer
Receipt r = oquMail.send(new OquMailClient.SendRequest(
List.of(user.email()), "Welcome to Acme", "<p>Your account is ready.</p>", null, "Acme"));
log.info("queued {}", r.id());Free business email on your own domain
OquMail gives you up to 15 mailboxes on your domain — free — with guided SPF/DKIM/DMARC, webmail, IMAP/SMTP for any mail app, and a send API. Setup is a few DNS records; changes usually take effect within minutes, occasionally up to 48 hours. Start at oqumail.com.
Keep it off the request thread
Annotate the sending method with @Async (and @EnableAsync on a configuration class) or publish an event handled by a listener after commit, so a slow mail call never delays the HTTP response. For retries, Spring Retry or a scheduled queue consumer with a backoff beats a loop; the mailbox allows 10 sends a minute and 250 a day.
What the API answers
- 202 Accepted: the message is queued. The JSON carries id, messageId and status; store the id so a support question can be matched to the delivery log.
- 400: the body failed validation, usually to missing or not an array, or neither html nor text present.
- 401: the key is missing, revoked or mistyped.
- 403: the mailbox is suspended, has reached its daily limit of 250, or sent more than 10 messages in a minute ("Sending too fast"). Back off; do not retry in a tight loop.
- 422: the message was blocked by content checks; the response explains why. Do not retry unchanged.
- 429: requests arrived faster than the per-key limit. Honour the Retry-After header.
Common questions
MailSendException: "Could not connect to SMTP host"?
The host blocks outbound SMTP or the port is 465. Use 587 with STARTTLS, or switch to the API, which needs only HTTPS.
AuthenticationFailedException with the right password?
The username must be the full address, and starttls.enable must be true; the server refuses AUTH over plain text.
Can I keep Thymeleaf templates with the API?
Yes. Render the template to a string with SpringTemplateEngine and pass it as html; nothing about templating depends on SMTP.