Send emails with Java & Kotlin
Send email from Java or Kotlin with the official com.mailblastr:mailblastr SDK — zero dependencies, Java 11+.
The official Java SDK is published to Maven Central as com.mailblastr:mailblastr. It has zero dependencies — it is built on the JDK java.net.http.HttpClient — and the jar is plain Java 11 bytecode, so the same artifact works unchanged from Kotlin, Scala, Groovy and any other JVM language.
You construct one Mailblastr client and reach every resource through a method on it — mailblastr.emails(), mailblastr.contacts(), mailblastr.campaigns(), and so on. The client is immutable and thread-safe, so build it once and share it.
Prerequisites
- A verified domain for your
fromaddress — publish its SPF/DKIM/DMARC records first. (guide) - An API key (
mb_...), read fromSystem.getenv("MAILBLASTR_API_KEY"). (Authentication) - Java 11 or newer — that is the
maven.compiler.releasethe artifact is built at, and the floor for thejava.net.http.HttpClientthe default transport uses.
Install
<dependency>
<groupId>com.mailblastr</groupId>
<artifactId>mailblastr</artifactId>
<version>5.0.0</version>
</dependency>Send an email
Construct the client with your API key and call mailblastr.emails().send(...). Request bodies are built with a fluent builder — SendEmailRequest.builder() — and build() returns the immutable request. delivered@mailblastr.dev is the delivery simulator, so it is safe to send to while you wire things up; it is intercepted before it reaches a real mailbox. Do not use example.com, which is a blocked recipient domain and comes back 422.
import com.mailblastr.Mailblastr;
import com.mailblastr.MailblastrException;
import com.mailblastr.MailblastrResponse;
import com.mailblastr.requests.SendEmailRequest;
public class Main {
public static void main(String[] args) {
Mailblastr mailblastr = new Mailblastr(System.getenv("MAILBLASTR_API_KEY"));
SendEmailRequest request = SendEmailRequest.builder()
.from("Acme <hello@yourdomain.com>")
.to("delivered@mailblastr.dev")
.subject("Hello from Java")
.html("<p>Sent with the official MailBlastr Java SDK.</p>")
.build();
try {
MailblastrResponse response = mailblastr.emails().send(request);
System.out.println("Sent email " + response.getString("id"));
} catch (MailblastrException e) {
// MailBlastr returns { statusCode, name, message }.
System.err.println(e.getStatusCode() + " " + e.getName() + ": " + e.getMessage());
}
}
}There is no Kotlin-specific artifact and none is needed — the same jar is on the classpath either way. emails() is a plain getter method, so it keeps its parentheses in Kotlin, while getStatusCode() and getName() are reachable as the synthetic properties statusCode and name.
Handling the response
Every method returns a MailblastrResponse (binary downloads return byte[]). It wraps the parsed JSON and exposes typed, dotted-path accessors — getString, getInt, getBoolean, getList, getMap, plus asMap() and the untouched raw(). A successful send carries just the new email id.
{
"id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794"
}MailblastrResponse response = mailblastr.emails().send(request);
String id = response.getString("id");Keep that id to retrieve the email later or to correlate webhook events.
Handling errors
Every non-2xx response throws MailblastrException, which mirrors the API envelope through getStatusCode(), getName() and getMessage(). A transport failure throws the same type with getStatusCode() == 0 and getName() == "network_error", so one catch block covers both. Branch on getName() together with getStatusCode() — never on the message text, which is scrubbed of provider identifiers server-side and is not a stable contract.
try {
mailblastr.emails().send(request);
} catch (MailblastrException e) {
switch (e.getName()) {
case "validation_error":
// 422 — a bad field (and 403 on a raw request with no User-Agent).
break;
case "daily_quota_exceeded":
case "monthly_quota_exceeded":
case "plan_limit_reached":
Map<String, Object> cap = e.getLimit();
if (cap != null) {
System.out.println(cap.get("kind") + " cap hit: "
+ cap.get("used") + "/" + cap.get("limit"));
}
break;
case "missing_api_key":
case "invalid_api_key":
case "restricted_api_key":
// 401/403 — the key is absent, wrong, or lacks the scope for this route.
break;
default:
throw e;
}
}Plan and quota rejections add a limit object (getLimit()), reputation gates add reputation (getReputation()), and a partially-applied batch adds sent / sent_count (getSent() / getSentCount()). Each returns null on an ordinary error. The full parsed body is always available via getBody(), or by dotted path with e.get("limit.next_plan.id").
Retrying a send safely
Pass an idempotency key as the second argument to send and a retry replays the original response instead of sending a second email.
MailblastrResponse response = mailblastr.emails().send(request, "welcome-" + userId);The key must be 1–255 characters after trimming (Mailblastr.IDEMPOTENCY_KEY_MAX_LENGTH); anything else is a 400 invalid_idempotency_key. It is bound to the request body, so reusing it with a different payload is a 409 invalid_idempotent_request. Only POST /emails and POST /emails/batch read the header — every other endpoint ignores it, so a retry there creates a second resource.
Timeouts and retries
The four-argument constructor tunes the HTTP core: a per-request (and connect) timeout, and the retry budget.
Mailblastr mailblastr = new Mailblastr(
System.getenv("MAILBLASTR_API_KEY"),
Mailblastr.DEFAULT_BASE_URL,
Duration.ofSeconds(10), // null or non-positive means "no timeout"
3); // extra attempts on 429/503; 0 disables retriesRetry-After, on HTTP 429 and 503 only — those are the only responses the server guarantees were not applied. No other status, network error, or timeout is retried, so a send is never silently duplicated. The SDK also sets a non-empty User-Agent (mailblastr-java/5.0.0) on every request, which the API requires on every route: a request without one is rejected with 403 validation_error before it is even authenticated.java.net.http.HttpClient, which the Android runtime does not ship. An Android build must supply its own com.mailblastr.http.HttpTransport implementation (a single execute(method, url, headers, body) method, e.g. over OkHttp) and pass it to new Mailblastr(apiKey, baseUrl, transport). Sending directly from a mobile app is a bad idea regardless — see the key warning below.mb_ API key from the environment (or your secrets manager) and keep it server-side. Never ship it inside a mobile or desktop app a user can decompile, and never commit it — anyone with the key can send email as your account.Next steps
- Send up to 100 messages in one request with
mailblastr.batch().sendEmails(List.of(BatchEmailRequest.builder()...)). Batch items rejectattachmentsandscheduled_at—BatchEmailRequestenforces that at compile time, so send those one at a time withemails().send(...). - See the full Send Email API reference for every body field (
cc,bcc,replyTo,attachment,scheduledAt). - Explore the SDKs reference for the other resources the SDK exposes — contacts, segments, campaigns, templates, automations, webhooks and events.
- Verify inbound webhooks with
mailblastr.webhooks().verify(...)— pass the raw request body, never re-serialized JSON. See Webhooks. - Read Authentication for key scopes and permissions.
- Prefer no dependency at all? Send with Java over raw HTTP uses only
java.net.http.