> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lettr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced

> Advanced Lettr Java SDK usage covering multiple recipients, reply-to, attachments, templates, batch sending, and Spring Boot.

This guide covers advanced features of the Lettr Java SDK including attachments, templates, batch sending, error handling, Spring Boot integration, and best practices for production applications.

<Note>
  New to the Java SDK? Start with the [Java Quickstart](/quickstart/java/quickstart) to learn the basics first.
</Note>

## Advanced Features

### Multiple Recipients

Add multiple To, CC, and BCC recipients:

```java theme={null}
import java.util.List;

CreateEmailOptions email = CreateEmailOptions.builder()
    .from("notifications@yourdomain.com")
    .to(List.of("user1@example.com", "user2@example.com"))
    .cc(List.of("cc@example.com", "cc2@example.com"))
    .bcc(List.of("bcc@example.com"))
    .subject("Team notification")
    .html("<p>This email has multiple recipients.</p>")
    .build();

lettr.emails().send(email);
```

<Note>
  BCC recipients are hidden from all other recipients. They receive the email but their addresses are not visible in the headers.
</Note>

### Reply-To Address

Specify a different reply-to address:

```java theme={null}
CreateEmailOptions email = CreateEmailOptions.builder()
    .from("notifications@yourdomain.com")
    .replyTo("support@yourdomain.com")
    .to("user@example.com")
    .subject("Support notification")
    .html("<p>Click reply to contact our support team.</p>")
    .build();

lettr.emails().send(email);
```

### Attachments

Add file attachments to your emails:

```java theme={null}
import com.lettr.services.emails.model.Attachment;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.List;

// Read file and encode to base64
byte[] fileData = Files.readAllBytes(Paths.get("invoice.pdf"));
String encodedContent = Base64.getEncoder().encodeToString(fileData);

Attachment attachment = Attachment.builder()
    .name("invoice.pdf")
    .data(encodedContent)
    .type("application/pdf")
    .build();

CreateEmailOptions email = CreateEmailOptions.builder()
    .from("billing@yourdomain.com")
    .to("customer@example.com")
    .subject("Your invoice")
    .html("<p>Please find your invoice attached.</p>")
    .attachments(List.of(attachment))
    .build();

lettr.emails().send(email);
```

Attach multiple files:

```java theme={null}
byte[] pdfData = Files.readAllBytes(Paths.get("document.pdf"));
byte[] imgData = Files.readAllBytes(Paths.get("logo.png"));

List<Attachment> attachments = List.of(
    Attachment.builder()
        .name("document.pdf")
        .data(Base64.getEncoder().encodeToString(pdfData))
        .type("application/pdf")
        .build(),
    Attachment.builder()
        .name("logo.png")
        .data(Base64.getEncoder().encodeToString(imgData))
        .type("image/png")
        .build()
);

CreateEmailOptions email = CreateEmailOptions.builder()
    .from("sender@yourdomain.com")
    .to("recipient@example.com")
    .subject("Files attached")
    .html("<p>See attached files.</p>")
    .attachments(attachments)
    .build();

lettr.emails().send(email);
```

<Warning>
  Attachments must be base64-encoded. The total size of all attachments should not exceed 10MB. Larger files should be hosted and linked instead.
</Warning>

### Templates

Send emails using Lettr-managed templates:

```java theme={null}
import java.util.Map;

CreateEmailOptions email = CreateEmailOptions.builder()
    .from("notifications@yourdomain.com")
    .to("user@example.com")
    .templateSlug("welcome-email")
    .substitutionData(Map.of(
        "name", "John Doe",
        "company", "Acme Corp",
        "verify_url", "https://example.com/verify/abc123",
        "support_email", "support@yourdomain.com"
    ))
    .build();

lettr.emails().send(email);
```

<Tip>
  Templates are managed in the [Lettr dashboard](https://app.lettr.com/templates). Use merge tags to personalize content without rebuilding HTML in your code.
</Tip>

### Custom Headers

Add custom email headers:

```java theme={null}
import java.util.Map;

CreateEmailOptions email = CreateEmailOptions.builder()
    .from("notifications@yourdomain.com")
    .to("user@example.com")
    .subject("Custom headers example")
    .html("<p>This email has custom headers.</p>")
    .headers(Map.of(
        "X-Campaign-ID", "summer-2024",
        "X-Priority", "high",
        "X-Mailer", "Acme Mailer v1.0"
    ))
    .build();

lettr.emails().send(email);
```

### Tracking

Enable open and click tracking:

```java theme={null}
import com.lettr.services.emails.model.EmailOptions;

CreateEmailOptions email = CreateEmailOptions.builder()
    .from("marketing@yourdomain.com")
    .to("user@example.com")
    .subject("Newsletter")
    .html("<p>Check out <a href='https://example.com'>our website</a>!</p>")
    .options(EmailOptions.builder()
        .clickTracking(true)
        .openTracking(true)
        .build())
    .build();

lettr.emails().send(email);
```

<Info>
  Open tracking works by embedding a transparent pixel image. Click tracking rewrites links to go through Lettr's tracking domain. Both features respect user privacy and comply with email regulations.
</Info>

### Metadata

Attach custom metadata for tracking and filtering:

```java theme={null}
import java.util.Map;

CreateEmailOptions email = CreateEmailOptions.builder()
    .from("notifications@yourdomain.com")
    .to("user@example.com")
    .subject("Order confirmation")
    .html("<p>Your order has been confirmed.</p>")
    .metadata(Map.of(
        "user_id", "12345",
        "order_id", "ORD-98765",
        "campaign", "abandoned-cart",
        "environment", "production"
    ))
    .build();

lettr.emails().send(email);
```

Metadata is returned in webhook events and can be used to correlate emails with your application data.

## Error Handling

The SDK provides structured exception types for different error scenarios:

```java theme={null}
import com.lettr.core.exception.LettrException;
import com.lettr.core.exception.LettrApiException;
import com.lettr.core.exception.LettrValidationException;

CreateEmailOptions email = CreateEmailOptions.builder()
    .from("invalid@unverified-domain.com")
    .to("user@example.com")
    .subject("Test")
    .html("<p>Test</p>")
    .build();

try {
    CreateEmailResponse response = lettr.emails().send(email);
    System.out.println("Email sent: " + response.getRequestId());
} catch (LettrValidationException e) {
    // 422 validation errors with field-level details
    System.err.println("Validation failed: " + e.getMessage());
    e.getErrors().forEach((field, messages) ->
        System.err.println("  " + field + ": " + messages));
} catch (LettrApiException e) {
    // Any non-2xx response from the API. Branch on status code.
    switch (e.getStatusCode()) {
        case 401 -> System.err.println("Authentication failed: " + e.getMessage());
        case 403 -> System.err.println("Forbidden: " + e.getMessage());
        case 404 -> System.err.println("Not found: " + e.getMessage());
        case 429 -> System.err.println("Rate limited: " + e.getMessage());
        default  -> System.err.println("API error " + e.getStatusCode() + ": " + e.getMessage());
    }
} catch (LettrException e) {
    // Base SDK exception (network, parsing, etc.)
    System.err.println("Request failed: " + e.getMessage());
}
```

<Note>
  The Java SDK exposes three exception types: `LettrException` (base), `LettrApiException` (any non-2xx response — branch on `getStatusCode()` and `getErrorCode()`), and `LettrValidationException` (422 with `getErrors()`).
</Note>

### Common Error Scenarios

**Unverified domain:**

* Exception: `LettrValidationException`
* Message: "The from address domain is not verified"
* Solution: Verify your domain in the dashboard

**Invalid API key:**

* Exception: `LettrApiException` with `getStatusCode() == 401`
* Message: "Invalid API key"
* Solution: Check your API key is correct and active

**Rate limit exceeded:**

* Exception: `LettrApiException` with `getStatusCode() == 429`
* Message: "Too many requests"
* Solution: Implement exponential backoff retry logic

## Batch Sending

Send multiple emails concurrently using CompletableFuture:

```java theme={null}
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class BatchSender {
    public static void main(String[] args) {
        Lettr lettr = new Lettr(System.getenv("LETTR_API_KEY"));

        List<String> recipients = List.of(
            "user1@example.com",
            "user2@example.com",
            "user3@example.com"
        );

        List<CompletableFuture<Void>> futures = recipients.stream()
            .map(recipient -> CompletableFuture.runAsync(() -> {
                CreateEmailOptions email = CreateEmailOptions.builder()
                    .from("notifications@yourdomain.com")
                    .to(recipient)
                    .subject("Batch notification")
                    .html("<p>This is a batch email.</p>")
                    .build();

                try {
                    CreateEmailResponse response = lettr.emails().send(email);
                    System.out.println("Sent to " + recipient + ": " + response.getRequestId());
                } catch (Exception e) {
                    System.err.println("Failed to send to " + recipient + ": " + e.getMessage());
                }
            }))
            .collect(Collectors.toList());

        // Wait for all to complete
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        System.out.println("All emails sent");
    }
}
```

### With Concurrency Limiting

Use an ExecutorService to limit concurrent requests:

```java theme={null}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class BatchSender {
    public static void sendBatch(Lettr lettr, List<String> recipients, int maxConcurrent) {
        ExecutorService executor = Executors.newFixedThreadPool(maxConcurrent);

        for (String recipient : recipients) {
            executor.submit(() -> {
                CreateEmailOptions email = CreateEmailOptions.builder()
                    .from("notifications@yourdomain.com")
                    .to(recipient)
                    .subject("Batch email")
                    .html("<p>Hello!</p>")
                    .build();

                try {
                    CreateEmailResponse response = lettr.emails().send(email);
                    System.out.println("Sent to " + recipient);
                } catch (Exception e) {
                    System.err.println("Failed: " + e.getMessage());
                }
            });
        }

        executor.shutdown();
        try {
            executor.awaitTermination(5, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) {
        Lettr lettr = new Lettr(System.getenv("LETTR_API_KEY"));
        List<String> recipients = List.of("user1@example.com", "user2@example.com");
        sendBatch(lettr, recipients, 10); // Max 10 concurrent requests
    }
}
```

<Tip>
  For large batches in production, consider using a message queue or job processing framework like Spring Batch or Quartz to manage concurrency and handle failures gracefully.
</Tip>

## Spring Boot Integration

### Configuration

Configure the Lettr client as a Spring bean:

```java theme={null}
import com.lettr.Lettr;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LettrConfig {

    @Value("${lettr.api.key}")
    private String apiKey;

    @Bean
    public Lettr lettrClient() {
        return new Lettr(apiKey);
    }
}
```

```properties theme={null}
# application.properties
lettr.api.key=${LETTR_API_KEY}
```

### Email Service

Create an email service that uses dependency injection:

```java theme={null}
import com.lettr.Lettr;
import com.lettr.services.emails.model.CreateEmailOptions;
import com.lettr.services.emails.model.CreateEmailResponse;
import org.springframework.stereotype.Service;

@Service
public class EmailService {

    private final Lettr lettr;

    public EmailService(Lettr lettr) {
        this.lettr = lettr;
    }

    public String sendWelcomeEmail(String recipient, String name) {
        CreateEmailOptions email = CreateEmailOptions.builder()
            .from("notifications@yourdomain.com")
            .to(recipient)
            .subject("Welcome to Acme")
            .html("<h1>Welcome, " + name + "!</h1>")
            .build();

        try {
            CreateEmailResponse response = lettr.emails().send(email);
            return response.getRequestId();
        } catch (Exception e) {
            throw new RuntimeException("Failed to send email", e);
        }
    }
}
```

### REST Controller

Use the email service in a REST controller:

```java theme={null}
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/emails")
public class EmailController {

    private final EmailService emailService;

    public EmailController(EmailService emailService) {
        this.emailService = emailService;
    }

    @PostMapping("/welcome")
    public ResponseEntity<String> sendWelcome(@RequestParam String email, @RequestParam String name) {
        try {
            String requestId = emailService.sendWelcomeEmail(email, name);
            return ResponseEntity.ok("Email sent: " + requestId);
        } catch (Exception e) {
            return ResponseEntity.internalServerError().body("Failed: " + e.getMessage());
        }
    }
}
```

## Best Practices

### Use Environment Variables

Never hardcode API keys. Use environment variables or a configuration management system:

```java theme={null}
String apiKey = System.getenv("LETTR_API_KEY");
if (apiKey == null || apiKey.isEmpty()) {
    throw new IllegalStateException("LETTR_API_KEY is required");
}

Lettr lettr = new Lettr(apiKey);
```

### Validate Before Sending

Validate email addresses before making API calls:

```java theme={null}
import java.util.regex.Pattern;

public class EmailValidator {
    private static final Pattern EMAIL_PATTERN =
        Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}$");

    public static boolean isValidEmail(String email) {
        return EMAIL_PATTERN.matcher(email).matches();
    }
}

if (!EmailValidator.isValidEmail(recipient)) {
    System.err.println("Invalid email address: " + recipient);
    return;
}
```

### Log Request IDs

Always log the `requestId` from successful sends for tracking and debugging:

```java theme={null}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(Main.class);

CreateEmailResponse response = lettr.emails().send(email);
logger.info("Email sent successfully - Request ID: {}, Accepted: {}",
    response.getRequestId(), response.getAccepted());
```

### Handle Errors Gracefully

Implement retry logic for transient errors:

```java theme={null}
public class EmailSender {
    private static final int MAX_RETRIES = 3;

    public String sendWithRetry(Lettr lettr, CreateEmailOptions email) throws Exception {
        for (int attempt = 0; attempt <= MAX_RETRIES; attempt++) {
            try {
                CreateEmailResponse response = lettr.emails().send(email);
                System.out.println("Email sent: " + response.getRequestId());
                return response.getRequestId();
            } catch (LettrValidationException e) {
                // Don't retry validation errors
                throw e;
            } catch (Exception e) {
                if (attempt < MAX_RETRIES) {
                    long backoff = (attempt + 1) * 1000L;
                    System.err.println("Attempt " + (attempt + 1) + " failed, retrying in " + backoff + "ms: " + e.getMessage());
                    Thread.sleep(backoff);
                } else {
                    throw e;
                }
            }
        }
        throw new RuntimeException("Max retries exceeded");
    }
}
```

### Reuse the Client

Create a single client instance and reuse it across requests:

```java theme={null}
// Good: Create once, reuse
public class EmailService {
    private final Lettr lettr;

    public EmailService(String apiKey) {
        this.lettr = new Lettr(apiKey);
    }

    public void sendEmail(String to, String subject, String html) {
        CreateEmailOptions email = CreateEmailOptions.builder()
            .from("notifications@yourdomain.com")
            .to(to)
            .subject(subject)
            .html(html)
            .build();

        lettr.emails().send(email);
    }
}
```

```java theme={null}
// Bad: Creating new client for each request
public void sendEmail(String to, String subject, String html) {
    Lettr lettr = new Lettr(System.getenv("LETTR_API_KEY")); // Don't do this
    // ...
}
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Unverified domain error">
    If you see "The from address domain is not verified":

    * Verify your domain in the [Lettr dashboard](https://app.lettr.com/domains)
    * Ensure the `from` address uses the verified domain
    * Wait for DNS propagation (can take up to 48 hours)
    * See [Domain Verification](/knowledge-base/troubleshooting/domain-verification) for help
  </Accordion>

  <Accordion title="Authentication failed (401 error)">
    If you see authentication errors:

    * Check your API key is correct and starts with `lttr_`
    * Verify the key is 68 characters total (prefix + 64 hex chars)
    * Ensure the key hasn't been revoked in the [dashboard](https://app.lettr.com/api-keys)
    * Confirm you're reading from the correct environment variable or config
  </Accordion>

  <Accordion title="Connection timeout">
    If requests timeout:

    * Increase the HTTP client timeout (default may be too short)
    * Check your network connectivity and firewall settings
    * Verify `app.lettr.com` is reachable
    * Use a custom HttpClient with longer timeout settings
  </Accordion>

  <Accordion title="Dependency or build errors">
    If you see Maven/Gradle errors:

    * Run `mvn clean install` or `gradle clean build` to refresh dependencies
    * Check your Java version is 11 or later: `java -version`
    * Verify the artifact ID is correct: `com.lettr:lettr-java`
    * Clear your local Maven repository: `~/.m2/repository`
  </Accordion>

  <Accordion title="Rate limit exceeded (429 error)">
    If you're hitting rate limits:

    * Implement exponential backoff retry logic (see Best Practices)
    * Use ExecutorService for controlled concurrent sending
    * Consider upgrading your Lettr plan for higher limits
    * Spread requests over time instead of bursts
  </Accordion>

  <Accordion title="Serialization or JSON errors">
    If you encounter JSON parsing errors:

    * Ensure all required builder fields are provided
    * Check that email addresses are properly formatted
    * Verify attachment content is properly base64-encoded
    * Use proper character encoding (UTF-8) for email content
  </Accordion>
</AccordionGroup>

## What's Next

<CardGroup cols={2}>
  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Complete API documentation
  </Card>

  <Card title="Templates" icon="file-code" href="/learn/templates/introduction">
    Use Lettr-managed templates
  </Card>

  <Card title="Webhooks" icon="webhook" href="/learn/webhooks/introduction">
    Track delivery events
  </Card>

  <Card title="Best Practices" icon="shield-check" href="/knowledge-base/best-practices/deliverability">
    Email deliverability tips
  </Card>
</CardGroup>
