# 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

1. A **verified domain** for your `from` address — publish its SPF/DKIM/DMARC records first. ([guide](https://www.mailblastr.com/docs/domains/managing))
2. An **API key** (`mb_...`), read from `System.getenv("MAILBLASTR_API_KEY")`. ([Authentication](https://www.mailblastr.com/docs/authentication))
3. **Java 11 or newer** — that is the `maven.compiler.release` the artifact is built at, and the floor for the `java.net.http.HttpClient` the default transport uses.

## Install

**Maven**

```xml
<dependency>
  <groupId>com.mailblastr</groupId>
  <artifactId>mailblastr</artifactId>
  <version>5.0.0</version>
</dependency>
```

**Gradle (Kotlin DSL)**

```kotlin
implementation("com.mailblastr:mailblastr:5.0.0")
```

**Gradle (Groovy)**

```groovy
implementation 'com.mailblastr:mailblastr:5.0.0'
```

## 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`.

**Java**

```java
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());
        }
    }
}
```

**Kotlin**

```kotlin
import com.mailblastr.Mailblastr
import com.mailblastr.MailblastrException
import com.mailblastr.requests.SendEmailRequest

fun main() {
    val mailblastr = Mailblastr(System.getenv("MAILBLASTR_API_KEY"))

    val request = SendEmailRequest.builder()
        .from("Acme <hello@yourdomain.com>")
        .to("delivered@mailblastr.dev")
        .subject("Hello from Kotlin")
        .html("<p>Sent with the official MailBlastr Java SDK.</p>")
        .build()

    try {
        val response = mailblastr.emails().send(request)
        println("Sent email " + response.getString("id"))
    } catch (e: MailblastrException) {
        System.err.println(e.statusCode.toString() + " " + e.name + ": " + e.message)
    }
}
```

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.

```json
{
  "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794"
}
```

```java
MailblastrResponse response = mailblastr.emails().send(request);
String id = response.getString("id");
```

Keep that id to [retrieve the email](https://www.mailblastr.com/docs/api/emails-get) later or to correlate [webhook](https://www.mailblastr.com/docs/webhooks/overview) 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.

```java
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.

```java
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.

```java
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 retries
```

> **Note:** The default transport allows 30s per attempt and retries up to 2 extra times, honouring `Retry-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.

> **Warning:** **Android is not supported by the default transport.** It uses `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.

> **Warning:** Read the `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 reject `attachments` and `scheduled_at` — `BatchEmailRequest` enforces that at compile time, so send those one at a time with `emails().send(...)`.
- See the full [Send Email API](https://www.mailblastr.com/docs/api/emails-send) reference for every body field (`cc`, `bcc`, `replyTo`, `attachment`, `scheduledAt`).
- Explore [the SDKs reference](https://www.mailblastr.com/docs/resources/sdks) 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](https://www.mailblastr.com/docs/webhooks/overview).
- Read [Authentication](https://www.mailblastr.com/docs/authentication) for key scopes and permissions.
- Prefer no dependency at all? [Send with Java over raw HTTP](https://www.mailblastr.com/docs/send-with/java) uses only `java.net.http`.
