#JavaMail
📧 How to Send Emails with Spring Boot

Start with the spring-boot-starter-mail dependency. It integrates JavaMail for easy email handling via SMTP. Simple setup, robust features! ✉️🚀

#JavaMail #springboot #java
December 2, 2024 at 5:58 PM
🚀 New Post: How I Solved SMTP Timeouts with Mailpit and JavaMail

I ran into a frustrating issue when testing email sending in a Spring Boot app with Mailpit and Testcontainers — local SMTP connections were hanging for seconds before failing.

👉 martinelli.ch/how-i-solved...
How I solved SMTP Timeouts with Mailpit and JavaMail - Martinelli
When I started testing email sending in my Spring Boot app with Mailpit and Testcontainers, I ran into a frustrating problem: sending mail locally to localhost often hung for many seconds before…
martinelli.ch
February 21, 2026 at 8:55 AM
Spin up temp email inboxes in Java, send and receive messages (with attachments), and automate it all with MailSlurp’s API.

#JavaDev #EmailAPI #AutomationTesting #JavaMail #MailSlurp #DevTutorial

www.youtube.com/watch?v=bPXl...
August 25, 2025 at 9:42 PM
SMTP outbound as a first-class citizen: send emails without pip install yagmail
> Notifications, password resets, magic links, alerts. Every language solves SMTP outbound with an external library. In Fitz, `smtp.send(opts)` is a language builtin. Async from day one. Bit-for-bit parity between `fitz run` and `fitz build`. No `pip install yagmail` / `npm install nodemailer` / `cargo add lettre` / Maven `JavaMail`. ## The detail that gets forgotten Any serious application eventually needs to send emails: * Notifications when a service goes down (incidents → on-call email). * Password reset / magic-link auth (unique link to the user's email). * Welcome email after a signup. * Daily / weekly digest reports dispatched from a cron job. * Alerts when a nightly job fails. And every language solves this with an external library. We hit the gap while building **fitzwatch** (open-source status page written in pure Fitz): the heart of the product alerts on-call when a monitor goes down. Without native SMTP, the only option was webhook → n8n/zapier → email. Two external systems just to send one email. Today, the 8 sub-blocks of the mini-release are in `main`. One line — `smtp.send({...}).await?` — and the email goes out. ## The typical Python stack pip install yagmail # or use stdlib smtplib (RFC 5321 by hand) # With yagmail (friendly syntax): import yagmail yag = yagmail.SMTP("user@example.com", "password") yag.send("dest@example.com", "Subject", "Body text") # With stdlib smtplib (low-level): import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart msg = MIMEMultipart("alternative") msg["Subject"] = "Subject" msg["From"] = "user@example.com" msg["To"] = "dest@example.com" msg.attach(MIMEText("Body text", "plain")) msg.attach(MIMEText("<p>Body HTML</p>", "html")) with smtplib.SMTP("smtp.gmail.com", 587) as server: server.starttls() server.login("user@example.com", "password") server.send_message(msg) Two options, both require reading the RFC or learning the yagmail API. ## The typical JS/Node stack npm install nodemailer import nodemailer from "nodemailer" const transporter = nodemailer.createTransport({ host: "smtp.gmail.com", port: 587, secure: false, auth: { user: "user@example.com", pass: "password" }, }) await transporter.sendMail({ from: "user@example.com", to: "dest@example.com", subject: "Subject", text: "Body text", html: "<p>Body HTML</p>", }) One library, well-designed, but `npm install` brings ~50 transitive packages to `package.json`. ## The typical Rust stack cargo add lettre cargo add tokio --features full use lettre::message::{header::ContentType, Mailbox, MultiPart, SinglePart}; use lettre::transport::smtp::authentication::Credentials; use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor}; let creds = Credentials::new("user@example.com".into(), "password".into()); let mailer = AsyncSmtpTransport::<Tokio1Executor>::starttls_relay("smtp.gmail.com") .unwrap() .port(587) .credentials(creds) .build(); let email = Message::builder() .from("user@example.com".parse::<Mailbox>().unwrap()) .to("dest@example.com".parse::<Mailbox>().unwrap()) .subject("Subject") .multipart(MultiPart::alternative() .singlepart(SinglePart::builder().header(ContentType::TEXT_PLAIN).body("Body text".to_string())) .singlepart(SinglePart::builder().header(ContentType::TEXT_HTML).body("<p>Body HTML</p>".to_string())) ) .unwrap(); mailer.send(email).await.unwrap(); Functional, but verbose. 25 lines to send ONE email. ## The typical Java stack <dependency> <groupId>com.sun.mail</groupId> <artifactId>jakarta.mail</artifactId> <version>2.0.1</version> </dependency> And then the canonical recipe of `Properties` + `Session` + `MimeMessage` + `Transport.send(...)` that probably adds 40 lines. ## The Fitz stack let r = smtp.send({ "to": "dest@example.com", "from": "user@example.com", "subject": "Subject", "body_text": "Body text", "body_html": "<p>Body HTML</p>", }).await? That's it. * `smtp` is a language builtin. No `pip install`/`npm install`/`cargo add`. * `body_text` + `body_html` together → automatic multipart/alternative. * `.await?` propagates errors as `Result::Err(Str)`. The compiler enforces handling. * Config (host, port, user, password, TLS) is read from env vars at first send. Same convention any app in Kubernetes/Docker expects. `r.delivered`, `r.message_id`, and `r.duration_ms` come out typed as `Bool`/`Str`/`Int`. ## What `smtp.send` guarantees you ### 1. Zero external deps The `fitz` binary ships with `lettre 0.11` statically linked inside. When you `fitz build`, the result is ONE executable of ~10 MB containing the SMTP client, TLS via rustls, no openssl on the target host. ldd ./my-app # linux-vdso.so.1 # libgcc_s.so.1 # libc.so.6 # (no smtp libs, no openssl) ### 2. Bit-for-bit parity `fitz run` ↔ `fitz build` The interpreter (`fitz run`) and codegen to Rust (`fitz build`) emit the same wire SMTP. Same handshake, same SCRAM auth, same Message-ID. Bug in one path = bug in the other. E2E tests validate this on every commit. ### 3. Async from day one `smtp.send(...)` returns `Future<Result<SmtpResult>>`. Integrates naturally with the rest of the stack: @background async fn send_welcome(email: Str) -> Null { let _ = smtp.send({ "to": email, "subject": "Welcome", "body": "Hi, thanks for joining.", }).await return null } @post("/signup") fn signup(input: SignupInput) { // ... create the user in the DB ... let _ = spawn(send_welcome(input.email)) return 201 { "id": new_user_id } } The handler returns 201 to the client **immediately**. The email goes out in another tokio task. If the SMTP server takes 2 seconds, the client doesn't care — it already responded 2 seconds ago. And with `@cron` you build digests: @cron("0 0 9 * * *") // every day at 09:00 async fn daily_digest() -> Null { let r = smtp.send({ "to": "team@example.com", "subject": "Daily digest", "body_html": "<p>Today: ...</p>", }).await match r { Ok(_) => log.info("digest.sent"), Err(e) => log.error("digest.failed", error: e), } return null } ### 4. `Result<T>` as error model Transport errors (DNS, auth, TLS, server reject) come as `Result::Err(Str)` with `"smtp: "` prefix: match smtp.send(opts).await { Ok(r) => log.info("smtp.delivered", message_id: r.message_id), Err(e) => { // The prefix lets you classify: // "smtp: server rejected mail: ..." → 5xx from server // "smtp: transient error: ..." → 4xx temporary // "smtp: client error: ..." → TLS/auth fail // "smtp: invalid `to` address ..." → parse error log.warn("smtp.failed", error: e) } } The static checker enforces handling. If your fn returns `Result<...>`, you can propagate with `?`. If not, the checker demands `match`. Zero runtime exceptions. ### 5. Magic-link auth in 30 lines The showcase case of Fitz's first-class web stack: HTTP server-side + auth with `jwt.encode` + SMTP outbound, all combined in one binary. type EmailRequest { email: Str } @post("/auth/magic-link") async fn magic_link(input: EmailRequest) -> Result<Str> { let payload = { "email": input.email } let token = jwt.encode(payload, "secret") let link = "https://app.example.com/verify?t={token}" let r = smtp.send({ "to": input.email, "subject": "Your login link", "body_text": "Click here:\n{link}\n\nExpires in 5 min.", "body_html": "<p>Click <a href=\"{link}\">here</a>.</p>", }).await? return Ok(r.message_id) } No Auth0, no Supabase, no Stripe webhooks. One binary, one port, one command to deploy. ## Local setup with MailHog For dev you don't want to send real emails. MailHog is a fake SMTP server with a web UI that retains emails without forwarding: docker run -d --name mailhog -p 1025:1025 -p 8025:8025 mailhog/mailhog export SMTP_HOST=localhost export SMTP_PORT=1025 export SMTP_TLS=none fitz run app.fitz # Then open http://localhost:8025 in the browser. In production you change the env vars to Gmail/SES/SendGrid/Mailgun/whatever — your Fitz code doesn't change. ## Honest trade-offs (MVP) * **No attachments yet**. The `attachments` key is rejected with a clear error. Workaround: bundle attachments via S3/object storage + link in the body. The cleanest pattern for serious production anyway. * **No programmatic`smtp.configure(...)`**. Config only comes from env vars today. If you need multi-tenant with a different SMTP per tenant, that's pending as post-MVP debt. * **No built-in retry**. If `smtp.send` fails, the caller decides what to do. The canonical pattern for production is outbox table + `@cron` with retry. This is NOT FUD — these are explicit MVP decisions. All 3 are refinable if real demand shows up. ## Closing `smtp.send` isn't "another easier library". It's **the same mental model** you already use elsewhere in Fitz: `Result<T>` as return type, `?` to propagate, `.await` for async, `@background + spawn` for fire-and-forget. If you already know how to chain `http.get(...).await?` or `db.query(...).await?`, you already know how to send emails. Zero new API to learn. And when fitzwatch resumes, the line that was pending to notify incidents goes from "webhook to n8n + translation to SMTP" to `smtp.send({...}).await?`. One line. That's Fitz. **Try it** : `fitz` version `0.18.0+` (already in GitHub releases). Exhaustive chapter + 3 runnable examples against MailHog in the "SMTP outbound" subsection of chapter 17 of the guide. **Stay tuned** : the Fitz series publishes on dev.to every time we close a major mini-release. The next one will cover a concrete feature detected during fitzwatch development.
dev.to
July 13, 2026 at 6:12 PM
SMTP outbound como ciudadano de primera clase: mandá emails sin pip install yagmail
> Notificaciones, password resets, magic links, alerts. Todos los lenguajes resuelven SMTP outbound con una librería externa. En Fitz, `smtp.send(opts)` es builtin del lenguaje. Async desde día uno. Paridad bit-a-bit `fitz run` ↔ `fitz build`. Sin `pip install yagmail` / `npm install nodemailer` / `cargo add lettre` / Maven `JavaMail`. ## El detalle que se olvida Cualquier aplicación seria termina necesitando mandar mails: * Notificación cuando se cae un servicio (incidents → email al on-call). * Password reset / magic-link auth (link único al email del user). * Welcome email después de un signup. * Reportes diarios / weekly digests despachados desde un cron job. * Alertas de jobs nocturnos que fallan. Y todos los lenguajes lo resuelven con una librería externa. Detectamos el gap construyendo **fitzwatch** (status page open-source escrito en Fitz puro): el corazón del producto avisa al on-call cuando un monitor cae. Sin SMTP nativo, la única opción era webhook → n8n/zapier → email. Dos sistemas externos para mandar un email. Hoy, los 8 sub-bloques de la mini-tanda están en `main`. Una línea — `smtp.send({...}).await?` — y el email sale. ## El stack típico de Python pip install yagmail # o usar smtplib del stdlib (RFC 5321 a mano) # Con yagmail (sintaxis amigable): import yagmail yag = yagmail.SMTP("user@example.com", "password") yag.send("dest@example.com", "Subject", "Body text") # Con smtplib stdlib (bajo nivel): import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart msg = MIMEMultipart("alternative") msg["Subject"] = "Subject" msg["From"] = "user@example.com" msg["To"] = "dest@example.com" msg.attach(MIMEText("Body text", "plain")) msg.attach(MIMEText("<p>Body HTML</p>", "html")) with smtplib.SMTP("smtp.gmail.com", 587) as server: server.starttls() server.login("user@example.com", "password") server.send_message(msg) Dos opciones, ambas requieren leerte la RFC o aprender la API de yagmail. ## El stack típico de JS/Node npm install nodemailer import nodemailer from "nodemailer" const transporter = nodemailer.createTransport({ host: "smtp.gmail.com", port: 587, secure: false, auth: { user: "user@example.com", pass: "password" }, }) await transporter.sendMail({ from: "user@example.com", to: "dest@example.com", subject: "Subject", text: "Body text", html: "<p>Body HTML</p>", }) Una sola lib, bien diseñada, pero `npm install` agrega ~50 paquetes transitivos al `package.json`. ## El stack típico de Rust cargo add lettre cargo add tokio --features full use lettre::message::{header::ContentType, Mailbox, MultiPart, SinglePart}; use lettre::transport::smtp::authentication::Credentials; use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor}; let creds = Credentials::new("user@example.com".into(), "password".into()); let mailer = AsyncSmtpTransport::<Tokio1Executor>::starttls_relay("smtp.gmail.com") .unwrap() .port(587) .credentials(creds) .build(); let email = Message::builder() .from("user@example.com".parse::<Mailbox>().unwrap()) .to("dest@example.com".parse::<Mailbox>().unwrap()) .subject("Subject") .multipart(MultiPart::alternative() .singlepart(SinglePart::builder().header(ContentType::TEXT_PLAIN).body("Body text".to_string())) .singlepart(SinglePart::builder().header(ContentType::TEXT_HTML).body("<p>Body HTML</p>".to_string())) ) .unwrap(); mailer.send(email).await.unwrap(); Funcional, pero verboso. 25 líneas para mandar UN mail. ## El stack típico de Java <dependency> <groupId>com.sun.mail</groupId> <artifactId>jakarta.mail</artifactId> <version>2.0.1</version> </dependency> Y después la receta canónica de `Properties` + `Session` + `MimeMessage` + `Transport.send(...)` que probablemente sumás 40 líneas. ## El stack de Fitz let r = smtp.send({ "to": "dest@example.com", "from": "user@example.com", "subject": "Subject", "body_text": "Body text", "body_html": "<p>Body HTML</p>", }).await? Que es esto. * `smtp` es builtin del lenguaje. No hay `pip install`/`npm install`/`cargo add`. * `body_text` + `body_html` juntos → multipart/alternative automático. * `.await?` propaga errores como `Result::Err(Str)`. El compilador exige manejo. * Config (host, port, user, password, TLS) se lee de env vars al primer send. Lo mismo que cualquier app en Kubernetes/Docker espera. `r.delivered`, `r.message_id` y `r.duration_ms` salen tipados como `Bool`/`Str`/`Int`. ## Lo que `smtp.send` te garantiza ### 1. Cero deps externas El binario `fitz` ya viene con `lettre 0.11` linkeado estático adentro. Cuando hacés `fitz build`, el resultado es UN ejecutable de ~10 MB que tiene cliente SMTP, TLS via rustls, sin openssl en el host destino. ldd ./mi-app # linux-vdso.so.1 # libgcc_s.so.1 # libc.so.6 # (nada de smtp libs ni openssl) ### 2. Paridad bit-a-bit `fitz run` ↔ `fitz build` El intérprete (`fitz run`) y el codegen a Rust (`fitz build`) emiten el mismo wire SMTP. Mismo handshake, mismo SCRAM auth, mismo Message-ID. Bug en un path = bug en el otro. Tests E2E lo validan en cada commit. ### 3. Async desde día uno `smtp.send(...)` devuelve `Future<Result<SmtpResult>>`. Integra natural con el resto del stack: @background async fn send_welcome(email: Str) -> Null { let _ = smtp.send({ "to": email, "subject": "Bienvenido", "body": "Hola, gracias por unirte.", }).await return null } @post("/signup") fn signup(input: SignupInput) { // ... crear el user en la DB ... let _ = spawn(send_welcome(input.email)) return 201 { "id": new_user_id } } El handler responde 201 al cliente **inmediato**. El email se manda en otro task tokio. Si el SMTP server tarda 2 segundos, no le importa al cliente — ya respondió hace 2 segundos. Y con `@cron` armás digests: @cron("0 0 9 * * *") // todos los días 09:00 async fn daily_digest() -> Null { let r = smtp.send({ "to": "team@example.com", "subject": "Daily digest", "body_html": "<p>Hoy: ...</p>", }).await match r { Ok(_) => log.info("digest.sent"), Err(e) => log.error("digest.failed", error: e), } return null } ### 4. `Result<T>` como modelo de error Errores de transporte (DNS, auth, TLS, server reject) llegan como `Result::Err(Str)` con prefijo `"smtp: "`: match smtp.send(opts).await { Ok(r) => log.info("smtp.delivered", message_id: r.message_id), Err(e) => { // El prefijo te permite clasificar: // "smtp: server rejected mail: ..." → 5xx del server // "smtp: transient error: ..." → 4xx temporario // "smtp: client error: ..." → TLS/auth fail // "smtp: invalid `to` address ..." → parse error log.warn("smtp.failed", error: e) } } El checker estático exige manejo. Si tu fn retorna `Result<...>`, podés propagar con `?`. Si no, te exige `match`. Cero excepciones runtime. ### 5. Magic-link auth en 30 líneas El caso showcase del stack web first-class de Fitz: HTTP server-side + auth con `jwt.encode` + SMTP outbound, todo combinado en un binario. type EmailRequest { email: Str } @post("/auth/magic-link") async fn magic_link(input: EmailRequest) -> Result<Str> { let payload = { "email": input.email } let token = jwt.encode(payload, "secret") let link = "https://app.example.com/verify?t={token}" let r = smtp.send({ "to": input.email, "subject": "Tu link de login", "body_text": "Hacé click acá:\n{link}\n\nExpira en 5 min.", "body_html": "<p>Hacé click <a href=\"{link}\">acá</a>.</p>", }).await? return Ok(r.message_id) } Sin Auth0, sin Supabase, sin Stripe webhooks. Un binario, un puerto, un comando para deployarlo. ## Setup local con MailHog Para dev no querés mandar mails reales. MailHog es un SMTP server fake con UI web que retiene los mails sin reenviar: docker run -d --name mailhog -p 1025:1025 -p 8025:8025 mailhog/mailhog export SMTP_HOST=localhost export SMTP_PORT=1025 export SMTP_TLS=none fitz run app.fitz # Después abrís http://localhost:8025 en el browser. En producción cambiás las env vars a Gmail/SES/SendGrid/Mailgun/lo que sea — el código de Fitz no cambia. ## Trade-offs honestos (MVP) * **Sin attachments todavía**. La key `attachments` se rechaza con error claro. Workaround: bundle attachments via S3/object storage + link en el body. Lo más limpio para producción serio igual. * **Sin`smtp.configure(...)` programático**. Config sale solo de env vars hoy. Si necesitás multi-tenant con un SMTP por tenant, te queda como deuda post-MVP. * **Sin retry built-in**. Si `smtp.send` falla, el caller decide qué hacer. El patrón canónico para producción es tabla de outbox + `@cron` con retry. Esto NO es FUD — son decisiones explícitas del MVP. Los 3 son refinables si entra demanda real. ## Cierre `smtp.send` no es "una librería más fácil". Es **el mismo modelo mental** que ya usás en el resto de Fitz: `Result<T>` como retorno, `?` para propagar, `.await` para async, `@background + spawn` para fire-and-forget. Si ya sabés cómo encadenar `http.get(...).await?` o `db.query(...).await?`, ya sabés cómo mandar mails. Cero API nuevo que aprender. Y cuando fitzwatch reanude, la línea que tenía pendiente para notificar incidents pasa de "webhook a n8n + traducción a SMTP" a `smtp.send({...}).await?`. Una línea. Eso es Fitz. **Probalo** : `fitz` versión `0.18.0+` (ya en GitHub releases). Cap exhaustivo + 3 ejemplos runnable contra MailHog en la sub-sección "SMTP outbound" del cap 17 de la guía. **Mantenete al día** : la serie Fitz se publica en dev.to cada vez que cerramos una mini-tanda grande. La próxima cubrirá una feature concreta detectada durante fitzwatch.
dev.to
July 13, 2026 at 6:12 PM
CVE-2026-46584: Apache Camel: Camel-Mail: The mail producer applied attacker-supplied mail.smtp.* / mail.smtps.* message headers as JavaMail session properties, allowing an attacker to weaken the SMTP transport security and, on releases before 4.19.0, redirect the connection and steal
oss-sec: CVE-2026-46584: Apache Camel: Camel-Mail: The mail producer applied attacker-supplied mail.smtp.* / mail.smtps.* message headers as JavaMail session properties, allowing an attacker to weaken the SMTP transport security and, on releases before 4.19.0, redirect the connection and steal
Posted by Andrea Cosentino on Jul 05 Severity: moderate Affected versions: - Apache Camel (org.apache.camel:camel-mail) 4.0.0 before 4.14.8 - Apache Camel (org.apache.camel:camel-mail) 4.15.0 before 4.18.3 - Apache Camel (org.apache.camel:camel-mail) 4.19.0 before r Description: Improper Input Validation, Exposure of Sensitive Information to an Unauthorized Actor vulnerability in Apache Camel Mail Component. The camel-mail producer (MailProducer.getSender) scanned the...
seclists.org
July 5, 2026 at 7:26 PM
Handle EML file with JavaMail - Real's Java How-to : www.rgagnon.com/javadetails/...
Real's How-to
Real's HowTo : Useful code snippets for Java, JS, PB and more
www.rgagnon.com
March 2, 2025 at 11:21 PM
https://mail.jodd.org has a decent fluent api on top of javamail…unfortunately it executes to many operations for my taste/performance needs. Interesting approach though!
Jodd Mail | Jodd Mail
Send and receive emails in Java
mail.jodd.org
November 18, 2024 at 5:35 PM
https://mail.jodd.org has a decent fluent api on top of javamail…unfortunately it executes to many operations for my taste/performance needs. Interesting approach though!
Jodd Mail | Jodd Mail
Send and receive emails in Java
mail.jodd.org
November 18, 2024 at 9:16 AM
https://mail.jodd.org has a decent fluent api on top of javamail…unfortunately it executes to many operations for my taste/performance needs. Interesting approach though!
Jodd Mail | Jodd Mail
Send and receive emails in Java
mail.jodd.org
November 17, 2024 at 11:18 PM
Feed: "Java Code Geeks"
By: Yatin Batra on Monday, September 8, 2025
Java Mail Inline Images in Emails Example
Java mail inline images: Send emails with inline images using JavaMail with MimeMultipart, MimeBodyPart, and practical examples.
www.javacodegeeks.com
September 8, 2025 at 12:48 PM
JavaMail 1.6 dépoussière cette vielle API, au menu : generics, autocloseable, exception, ... : https://java.net/projects/javamail/sources/mercurial/content/doc/spec/JavaMail-1.6-changes.txt #java
November 26, 2024 at 11:17 AM
JavaMail で RFC822 違反の MID をつけてくる奴は容赦なく落とします
January 21, 2024 at 2:22 PM
🚨 EUVD-2026-36203
📊 5.0/10
🏢 Spring

📝 Spring Boot's Mail auto-configuration does not enable hostname verification. Applications that set the relevant JavaMail property, such as spring.mail.prop...

🔗 https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-36203

#cybersecurity #infosec #cve #euvd
June 11, 2026 at 8:00 AM
Is UTF-7 supported by Javamail on traditional WebSphere Application Server or on Liberty Profile ? https://tinyurl.com/26r356u6
February 23, 2026 at 9:25 PM
MustGather: JavaMail https://tinyurl.com/2c2vmnef
October 30, 2025 at 9:20 PM
How to test javamail using a Google Gmail SMTP server https://tinyurl.com/23ky343l
October 29, 2025 at 7:05 PM
I still conceptually like the idea of OpenDoc/OLE, i.e. a system that is centered around documents that do not belong to one application but multiple, each providing components. JavaMail was (is?) somewhat similar (w/ components/actions being tied to the MIME types of the parts of an email) […]
Original post on mastodon.social
mastodon.social
June 3, 2025 at 9:26 PM