---
title: "Send an email"
description: "One message now or later, with attachments, or a hundred in one request."
url: "https://openemail.uk/docs/java/emails/send"
area: "Java"
category: "Guides"
---

# Send an email

One message now or later, with attachments, or a hundred in one request.

## Send a message

**Send.java**

```
Map<String, Object> email = client.emails().send(Body.of(
    "from", "Acme Billing <billing@acme.com>",
    "to", List.of("ada@example.com", Body.of("email", "grace@example.com", "name", "Grace")),
    "replyTo", "accounts@acme.com",
    "subject", "Your September invoice",
    "html", "<p>Invoice attached.</p>",
    "text", "Invoice attached."
), RequestOptions.create().idempotencyKey("invoice-2026-09-ada"));

System.out.println(email.get("id") + " " + email.get("status"));
```

`to`, `cc` and `bcc` take one address or a list, and an address is text such as `Ada <ada@example.com>` or a map with `email` and `name`. The body comes from `html` and `text`, from a stored `template`, or from a `draftId`.

Every send carries an idempotency key. The client makes one for each call and reuses it when it retries, so a retried request replays the first message. Pass `idempotencyKey` in a `RequestOptions` to make that hold across processes and restarts.

## Attachments

**Attachments.java**

```
byte[] report = "month,total".getBytes(StandardCharsets.UTF_8);

client.emails().send(Body.of(
    "from", "billing@acme.com",
    "to", "ada@example.com",
    "subject", "Your September report",
    "text", "The report is attached.",
    "attachments", List.of(
        Body.of("filename", "report.csv", "content", report, "contentType", "text/csv"),
        Body.of("fileId", "b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f")
    )
));
```

`content` takes a `byte[]` or a `Path`, which the client encodes as base64, or text that is base64 already. `fileId` names a file uploaded with `client.files().upload`, which is how a file larger than 5 MB is sent.

## Send later

**Schedule.java**

```
Map<String, Object> scheduled = client.emails().send(Body.of(
    "from", "billing@acme.com",
    "to", "ada@example.com",
    "subject", "Your invoice is due tomorrow",
    "text", "A reminder that your invoice is due tomorrow.",
    "scheduledAt", Instant.now().plus(Duration.ofHours(1))
));

String id = (String) scheduled.get("id");

client.emails().reschedule(id, Instant.now().plus(Duration.ofHours(2)));
client.emails().cancel(id);
```

`scheduledAt` takes an `Instant`, an ISO 8601 instant as text or a duration such as `PT1H`. An `Instant`, an `OffsetDateTime` or a `ZonedDateTime` anywhere in a body is sent as an ISO 8601 instant in UTC. A scheduled message can be changed or cancelled until it goes.

## Send a batch

**Batch.java**

```
BatchResult batch = client.emails().sendBatch(List.of(
    Body.of("from", "billing@acme.com", "to", "ada@example.com", "subject", "Your invoice", "text", "Invoice attached."),
    Body.of("from", "billing@acme.com", "to", "grace@example.com", "subject", "Your invoice", "text", "Invoice attached.")
));

for (Map<String, Object> item : batch) {
    if ("error".equals(item.get("status"))) {
        System.err.println(item.get("index") + " " + item.get("error"));
    }
}

System.out.println(batch.sent() + " sent, " + batch.failed() + " failed");
```

A batch holds up to 100 messages and is never all or nothing: a bad address fails its own item and the rest still go. Check `failed()` and the `status` of each item instead of relying on an exception.
