Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

Sending Email with JavaMail: Templates and TLS

Posted on November 16, 2009 By Luis Fernandez
I once spent a long night hunting a phantom bug. The app said the email was sent. The logs agreed. The mailbox stayed empty. Turned out the SMTP server wanted TLS and I was talking plain old port 25 like it was still dial up time. Switched to STARTTLS on 587 and the floodgates opened. Since then I treat email as a feature that deserves a checklist, not an afterthought.

Send email with JavaMail without drama

Email from Java is simple on the surface. Set some properties, authenticate, build a MimeMessage, press send. The part that bites is TLS vs SSL and which port the server wants. Many providers expect STARTTLS on 587. Some still listen on 465 with SSL from the first byte. If you use Gmail or a corporate relay that enforces encryption, set the right flags or your code will look fine and do nothing. Here is the bare minimum that works today on common SMTP servers.
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");  // STARTTLS on 587
props.put("mail.smtp.host", "smtp.example.com");
props.put("mail.smtp.port", "587");
props.put("mail.smtp.connectiontimeout", "5000");
props.put("mail.smtp.timeout", "8000");

Session session = Session.getInstance(props, new Authenticator() {
  protected PasswordAuthentication getPasswordAuthentication() {
    return new PasswordAuthentication("user@example.com", "secret");
  }
});

// session.setDebug(true);

Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress("no-reply@example.com", "Example App"));
msg.setRecipients(Message.RecipientType.TO,
    InternetAddress.parse("friend@domain.com", false));
msg.setSubject("Your receipt");
msg.setSentDate(new java.util.Date());
msg.setText("Thanks for your order. We will let you know when it ships.");

Transport.send(msg);
Once you can send one email, you will want templates. Text copy changes. Headers change. Buyers want HTML with a plain text fallback. You can keep it simple with String.format for early drafts, then graduate to a template engine. FreeMarker and Velocity are both solid. FreeMarker tends to be strict, which is good for catching missing variables. It also plays well with UTF 8, so your subject lines can carry accents without weird symbols.
// FreeMarker setup
Configuration cfg = new Configuration();
cfg.setClassForTemplateLoading(MyMailer.class, "/mail/");
cfg.setDefaultEncoding("UTF-8");

// data model
Map<String, Object> model = new HashMap<>();
model.put("name", "Guillermo");
model.put("orderId", 12345);
model.put("total", "49.90");

// render
Template tpl = cfg.getTemplate("receipt.ftl"); // stored in resources /mail/
StringWriter out = new StringWriter();
tpl.process(model, out);
String html = out.toString();

// build message with text and HTML
MimeMultipart alt = new MimeMultipart("alternative");

MimeBodyPart textPart = new MimeBodyPart();
textPart.setText("Hi " + model.get("name") + ", your order " + model.get("orderId") + " is paid.", "UTF-8");
alt.addBodyPart(textPart);

MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(html, "text/html; charset=UTF-8");
alt.addBodyPart(htmlPart);

Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress("no-reply@example.com"));
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse("buyer@domain.com", false));
msg.setSubject("Receipt " + model.get("orderId"), "UTF-8");
msg.setContent(alt);

Transport.send(msg);
TLS trips teams in two ways. First, folks confuse SSL on 465 with STARTTLS on 587. Those are not the same handshake. Pick one and set properties to match. Second, some relays use a cert that your JVM does not trust. You will see a handshake alert in debug and nothing goes out. When you control the relay, import the cert into the JVM truststore. When you cannot, JavaMail has a property to trust a host. Use it only when you fully trust the network path.
// If the server presents a cert your JVM does not know
// Best: import the cert into cacerts with keytool
// Quick fix when you trust the host:
props.put("mail.smtp.ssl.trust", "smtp.example.com");

// For SSL on 465 instead of STARTTLS
props.put("mail.smtp.ssl.enable", "true");
props.put("mail.smtp.port", "465");
props.remove("mail.smtp.starttls.enable");

// Turn on debug to see the handshake and server replies
session.setDebug(true);
Production email is about details. Set reply to and return path so bounces do not vanish. Use Message ID and your own List Unsubscribe header if you send newsletters. Keep timeouts short and add retries with a small backoff. Encode everything as UTF 8 and send both text and HTML parts for deliverability. Attachments go in a mixed multipart that wraps the alternative part. And watch your content. Words that look spammy will land you in the junk folder no matter how pretty the template is.
// Headers that help in the real world
msg.setHeader("X-Application", "ExampleApp");
msg.setHeader("List-Unsubscribe", "<mailto:unsubscribe@example.com>");

// Return-Path needs SMTP envelope sender, not only header
// With JavaMail, set it on Transport:
// Transport.send(msg, msg.getAllRecipients()); // then set property
props.put("mail.smtp.from", "bounces@example.com");
A few quick checks before you ship. Use a test account at Gmail and at Hotmail to spot formatting quirks. Send yourself a message with long accents to confirm charset is correct. If your host blocks port 25, try 587 with STARTTLS through your provider. Keep Session debug on in staging and capture logs so support can answer why a message did not arrive. And stash your SMTP password outside the build file, in an env var or a small properties file that is not in version control.Strong recipe. Use JavaMail with the right TLS setting, pick a template engine you can live with, send both text and HTML, and keep an eye on headers and timeouts. The code above covers the common paths for Gmail, hosted relays, and many corporate servers. Do that and your app will send mail that lands where it should, looks good, and is easy to tweak when marketing asks for a new subject line at 5 pm on a Friday.
General Software Software Engineering

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes