← Back to blog
·8 min read·
Vanessa LozzardoVanessa Lozzardo

How to send email with Java using an email API or SMTP

Send email from Java apps using the Sendkit SDK. Covers Maven setup, HTML email, attachments, error handling, and SMTP.

javaemail-apitransactional-emailtutorial
How to send email with Java using an email API or SMTP

JavaMail has been around since the late '90s. It still works, but every time I use it I end up writing 40 lines of boilerplate just to send a plain text message. Between creating a Session, building MimeMessage objects, and handling SMTP transport manually, most of the code has nothing to do with the email itself.

An API call replaces all of that. This guide covers sending email from Java using the Sendkit email API, starting with basic setup and working through HTML, attachments, error handling, and SMTP as a fallback.

Adding the SDK to your project

The Sendkit Java SDK requires Java 11 or higher. Add the dependency to your build file.

Maven:

<dependency>
    <groupId>dev.sendkit</groupId>
    <artifactId>sendkit</artifactId>
    <version>1.0.0</version>
</dependency>

Gradle:

implementation 'dev.sendkit:sendkit:1.0.0'

Grab an API key from your Sendkit dashboard. Keys starting with sk_live_ send real email. Keys starting with sk_test_ go through the same validation but don't deliver anything, which is useful for automated tests.

import dev.sendkit.Sendkit;

Sendkit sendkit = new Sendkit("sk_live_your_api_key");

Don't hardcode the key. Pull it from an environment variable or your app's config:

Sendkit sendkit = new Sendkit(System.getenv("SENDKIT_API_KEY"));

Send a plain text email

The simplest case:

import dev.sendkit.Emails;
import java.util.List;

Emails.SendEmailParams params = new Emails.SendEmailParams(
    "[email protected]",
    List.of("[email protected]"),
    "Your order has shipped"
).text("Order #4821 shipped via FedEx. Tracking number: 7489201384.");

Emails.SendEmailResponse response = sendkit.emails().send(params);
System.out.println(response.getId());

The from address must be on a domain you've verified in Sendkit. If you haven't configured domain authentication yet, set up DMARC, DKIM, and SPF first. Without those DNS records, Gmail and Outlook will likely reject your messages or dump them in spam.

The to parameter takes a List<String>, so you can send to multiple recipients in a single call.

Send HTML email

Most transactional email needs formatting. Pass both .text() and .html() so clients that can't render HTML still display something useful.

public String sendWelcomeEmail(String userEmail, String userName) {
    Emails.SendEmailParams params = new Emails.SendEmailParams(
        "[email protected]",
        List.of(userEmail),
        "Welcome, " + userName
    )
    .text("Hey " + userName + ", thanks for signing up. Your account is ready.")
    .html(
        "<div style=\"font-family: sans-serif; max-width: 600px; margin: 0 auto;\">"
        + "<h2>Welcome aboard, " + userName + "</h2>"
        + "<p>Your account is set up and ready to go.</p>"
        + "<p>Here are a few things to try first:</p>"
        + "<ul>"
        + "  <li>Complete your profile</li>"
        + "  <li>Connect your first integration</li>"
        + "  <li>Invite your team</li>"
        + "</ul>"
        + "<a href=\"https://app.yoursite.com/getting-started\""
        + "   style=\"background: #0066ff; color: white; padding: 12px 24px;"
        + "   text-decoration: none; border-radius: 4px;"
        + "   display: inline-block; margin-top: 16px;\">Get started</a>"
        + "</div>"
    );

    Emails.SendEmailResponse response = sendkit.emails().send(params);
    return response.getId();
}

Use inline CSS. Email clients strip <style> blocks and ignore external stylesheets. Inline styles are the only thing that works reliably across Gmail, Outlook, Apple Mail, and Yahoo.

If you're building a lot of HTML in Java strings like this, consider a templating library like Thymeleaf or FreeMarker. The string concatenation gets old fast.

Developer working at a laptop

Send email with attachments

Attachments use base64-encoded content. Read the file, encode it, and pass it as an Emails.Attachment:

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;

public String sendInvoice(String recipient, String invoiceNumber, Path pdfPath)
        throws Exception {

    byte[] fileBytes = Files.readAllBytes(pdfPath);
    String base64Content = Base64.getEncoder().encodeToString(fileBytes);

    Emails.Attachment attachment = new Emails.Attachment(
        "invoice-" + invoiceNumber + ".pdf",
        base64Content
    ).contentType("application/pdf");

    Emails.SendEmailParams params = new Emails.SendEmailParams(
        "[email protected]",
        List.of(recipient),
        "Invoice #" + invoiceNumber
    )
    .text("Invoice #" + invoiceNumber + " is attached.")
    .html("<p>Invoice #" + invoiceNumber + " is attached.</p>")
    .attachments(List.of(attachment));

    Emails.SendEmailResponse response = sendkit.emails().send(params);
    return response.getId();
}

Keep total attachment size under 10MB. Anything larger should be uploaded somewhere with a download link in the email body. Large attachments slow delivery and get flagged by spam filters more often.

Send to multiple recipients

Pass multiple addresses in the to list:

Emails.SendEmailParams params = new Emails.SendEmailParams(
    "[email protected]",
    List.of("[email protected]", "[email protected]"),
    "[ALERT] Database connection pool exhausted"
).text("Connection pool hit 100% at 14:32 UTC. Auto-scaling triggered.");

sendkit.emails().send(params);

You can also add CC and BCC recipients:

Emails.SendEmailParams params = new Emails.SendEmailParams(
    "[email protected]",
    List.of("[email protected]"),
    "Weekly report"
)
.text("Report attached.")
.cc(List.of("[email protected]"))
.bcc(List.of("[email protected]"));

For batch sends where each person gets different content, loop and send individually. The API docs cover template syntax if you need server-side personalization.

Before sending to a list of addresses, validate them first. Sending to dead mailboxes hurts your sender reputation, and once that's damaged it affects every email you send. Sendkit has built-in email validation that catches typos, disposable addresses, and nonexistent mailboxes. There's a separate guide on validating email addresses before sending if you want the details.

Error handling

APIs fail. Networks drop. Rate limits exist. If you only write the happy path, you'll find out about these problems when your password reset flow stops working at the worst possible time.

import dev.sendkit.Sendkit;
import dev.sendkit.Emails;
import dev.sendkit.SendkitException;

public class EmailService {

    private final Sendkit sendkit;

    public EmailService(String apiKey) {
        this.sendkit = new Sendkit(apiKey);
    }

    public record SendResult(boolean success, String messageId, String error,
                             boolean retryable) {}

    public SendResult sendEmail(Emails.SendEmailParams params) {
        try {
            Emails.SendEmailResponse response = sendkit.emails().send(params);
            return new SendResult(true, response.getId(), null, false);

        } catch (SendkitException e) {
            if (e.getStatusCode() == 422) {
                // Bad input: invalid address, missing required field
                System.err.println("Validation error: " + e.getMessage());
                return new SendResult(false, null, e.getMessage(), false);
            }

            if (e.getStatusCode() == 429) {
                // Rate limited
                System.err.println("Rate limited: " + e.getMessage());
                return new SendResult(false, null, "rate_limited", true);
            }

            if (e.getStatusCode() >= 500) {
                // Something broke on Sendkit's side
                System.err.println("Server error: " + e.getStatusCode());
                return new SendResult(false, null, "server_error", true);
            }

            // Other API errors
            System.err.println("API error (" + e.getName() + "): " + e.getMessage());
            return new SendResult(false, null, e.getMessage(), false);

        } catch (Exception e) {
            // Network timeout, DNS failure, etc.
            System.err.println("Unexpected error: " + e.getMessage());
            return new SendResult(false, null, "network_error", true);
        }
    }
}

For production systems, add retry logic with exponential backoff:

public SendResult sendWithRetry(Emails.SendEmailParams params, int maxRetries) {
    for (int attempt = 1; attempt <= maxRetries; attempt++) {
        SendResult result = sendEmail(params);

        if (result.success() || !result.retryable()) {
            return result;
        }

        long delay = Math.min((long) Math.pow(2, attempt) * 1000, 30_000);
        System.out.println("Attempt " + attempt + " failed. Retrying in "
                           + delay + "ms...");
        try {
            Thread.sleep(delay);
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
            return new SendResult(false, null, "interrupted", false);
        }
    }

    return new SendResult(false, null, "max_retries_exceeded", false);
}

Three retries handles most transient failures. If things are still broken after that, push the email into a message queue (RabbitMQ, SQS, or whatever your stack already uses) rather than blocking the request.

Close-up of a laptop keyboard

Using SMTP instead of the API

If your codebase already uses Jakarta Mail (formerly JavaMail), you don't need to rip it out. Sendkit provides an SMTP relay that works as a drop-in replacement:

import jakarta.mail.*;
import jakarta.mail.internet.*;
import java.util.Properties;

Properties props = new Properties();
props.put("mail.smtp.host", "smtp.sendkit.com");
props.put("mail.smtp.port", "587");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");

Session session = Session.getInstance(props, new Authenticator() {
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication("sendkit", "sk_live_your_api_key");
    }
});

MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress("[email protected]"));
message.setRecipient(Message.RecipientType.TO,
    new InternetAddress("[email protected]"));
message.setSubject("Password reset");
message.setText("Click here to reset your password: "
    + "https://app.yoursite.com/reset?token=abc123");

Transport.send(message);

Your API key doubles as your SMTP password. No separate credentials needed.

The API is still the better option for new code. It's faster (no SMTP handshake overhead), gives you a message ID in the response immediately, and returns structured error responses instead of SMTP reply codes you have to parse. But SMTP works fine when you're plugging into an existing codebase.

Spring Boot integration

If you're running Spring Boot, point the mail properties at Sendkit's SMTP:

# application.yml
spring:
  mail:
    host: smtp.sendkit.com
    port: 587
    username: sendkit
    password: ${SENDKIT_API_KEY}
    properties:
      mail.smtp.auth: true
      mail.smtp.starttls.enable: true

Then JavaMailSender works as usual and all your email goes through Sendkit with full tracking (opens, clicks, bounces).

After the email leaves your server

Getting send() to work is the straightforward part. Whether the email actually lands in the inbox depends on your domain setup and sending habits.

Set up domain authentication. DKIM, SPF, and DMARC records are non-negotiable. Without them, the major providers will either spam-folder your messages or reject them outright. The DMARC/DKIM/SPF setup guide walks through the DNS records.

Watch your bounces. Sending repeatedly to addresses that don't exist will damage your sender reputation, and that damage affects every email you send from that domain. Sendkit suppresses bounced addresses automatically, but keep an eye on your bounce rate in your own monitoring too.

Validate addresses before sending. If you accept user-submitted email addresses (signup forms, contact forms), a validation call catches typos like gmial.com and disposable addresses that will never engage.

The Sendkit docs cover webhooks, templates, and batch sending if you need to go deeper.

Wrapping up

That covers it. Add the Maven or Gradle dependency, call sendkit.emails().send(), handle errors properly, and you have working email in your Java app.

The ongoing work is deliverability. Authenticate your domain, don't ignore bounces, validate addresses before sending. The code itself is a few lines. Keeping a clean sender reputation is the part that takes real effort.

Share this article