Skip to content

Latest commit

 

History

History
220 lines (169 loc) · 6.04 KB

File metadata and controls

220 lines (169 loc) · 6.04 KB

nuntly-java

Maven Central License: MIT

The official Java 21 SDK for Nuntly, the developer-first email platform. Send transactional emails, manage domains, handle webhooks, and receive inbound mail with records, sealed interfaces, and pattern matching.

Documentation | API Reference | Website | Get your API key

Requires Java 21+. Uses records, sealed interfaces, pattern matching switch, and java.net.http.HttpClient.

Installation

Gradle (Kotlin DSL)

dependencies {
    implementation("com.nuntly:nuntly-java:0.0.1")
}

Gradle (Groovy)

dependencies {
    implementation 'com.nuntly:nuntly-java:0.0.1'
}

Maven

<dependency>
    <groupId>com.nuntly</groupId>
    <artifactId>nuntly-java</artifactId>
    <version>0.0.1</version>
</dependency>

Quick start

import com.nuntly.sdk.*;
import com.nuntly.sdk.models.*;
import java.util.List;

var nuntly = Nuntly.create(ClientOptions.builder()
    .apiKey(System.getenv("NUNTLY_API_KEY"))
    .build());

var email = nuntly.emails().send(CreateEmailRequest.builder()
    .from("hello@yourcompany.com")
    .to(List.of("recipient@example.com"))
    .subject("Welcome to Nuntly")
    .html("<h1>Welcome!</h1><p>Your account is ready.</p>")
    .build());

System.out.println(email.id());

Configuration

var nuntly = Nuntly.create(ClientOptions.builder()
    .apiKey(System.getenv("NUNTLY_API_KEY"))
    .baseUrl("https://api.nuntly.com")       // optional
    .timeout(Duration.ofSeconds(30))          // optional, default: 60s
    .maxRetries(3)                            // optional, default: 2
    .backoff(BackoffStrategy.builder()        // optional
        .initialIntervalMs(200)
        .maxIntervalMs(30000)
        .exponent(1.5)
        .build())
    .debug(true)                              // optional
    .build());

Pagination

// Auto-paginate all items
for (var item : nuntly.emails().list()) {
    System.out.println(item.id() + " - " + item.status());
}

// Stream API
nuntly.emails().list().stream()
    .filter(e -> e.status() == EmailStatus.DELIVERED)
    .forEach(e -> System.out.println(e.id()));

// Manual pagination
var page = nuntly.emails().list();
System.out.println(page.data());
if (page.hasNextPage()) {
    var next = page.nextPage();
}

Error handling

import com.nuntly.sdk.ApiError.*;

try {
    nuntly.emails().retrieve("em_invalid");
} catch (ApiError e) {
    switch (e) {
        case NotFoundError nf -> System.out.println("Not found: " + nf.requestId().orElse("?"));
        case RateLimitError rl -> System.out.println("Retry in: " + rl.retryAfter().orElse(0L) + "ms");
        case AuthenticationError ae -> System.out.println("Invalid API key");
        default -> System.out.println("HTTP " + e.status() + ": " + e.getMessage());
    }

    // Raw response access
    System.out.println(e.rawResponse().statusCode());
}

Lifecycle hooks

var nuntly = Nuntly.create(ClientOptions.builder()
    .apiKey(System.getenv("NUNTLY_API_KEY"))
    .hooks(Hooks.builder()
        .onRequest(req -> System.err.println("-> " + req.method() + " " + req.uri()))
        .onResponse((res, req) -> System.err.println("<- " + res.statusCode()))
        .onRetry(ctx -> System.err.println("Retry #" + ctx.attempt()))
        .onError((err, req) -> logger.error("Failed: " + req.uri(), err))
        .build())
    .build());

Raw response access

Inspect the underlying HTTP response on errors via ApiError.rawResponse():

try {
    nuntly.emails().retrieve("em_invalid");
} catch (ApiError e) {
    System.out.println(e.rawResponse().statusCode());
    System.out.println(e.rawResponse().headers().firstValue("x-request-id").orElse(""));
}

Spring Boot

A separate auto-configuration starter wires Nuntly as a bean and binds settings from application.yml / application.properties:

dependencies {
    implementation("com.nuntly:nuntly-java-spring:0.0.1")
}
# application.yml
nuntly:
  api-key: ${NUNTLY_API_KEY}
  base-url: https://api.nuntly.com  # optional
  timeout: 30s                       # optional
  max-retries: 3                     # optional
  debug: false                       # optional
@Service
public class MailService {
  private final Nuntly nuntly;

  public MailService(Nuntly nuntly) { this.nuntly = nuntly; }
}

Override any extensibility point by declaring a single @Bean. The starter wires it into the underlying ClientOptions:

@Bean Hooks hooks() {
  return Hooks.builder()
      .onRequest(req -> log.info("-> {} {}", req.method(), req.uri()))
      .build();
}

@Bean BackoffStrategy backoff() {
  return BackoffStrategy.builder().initialIntervalMs(200).build();
}

@Bean AppInfo appInfo() {
  return AppInfo.of("my-app", "1.4.2");
}

Webhook verification

Verify a webhook signature and parse the payload into a typed event:

import com.nuntly.sdk.lib.Webhook;
import com.nuntly.sdk.models.WebhookEvent;
import com.nuntly.sdk.models.WebhookEvent.*;

WebhookEvent event = Webhook.verify(payload, signatureHeader, secret);

switch (event) {
  case EmailBouncedEvent e -> handleBounce(e);
  case EmailDeliveredEvent e -> handleDelivered(e);
  case MessageReceivedEvent e -> handleReceive(e);
  default -> log.warn("Unhandled event type: {}", event.type());
}

WebhookEvent is a sealed interface; the compiler enforces exhaustive matching once every permitted subtype has a case.

Error types

NotFoundError, RateLimitError, AuthenticationError, PermissionDeniedError, ConflictError, BadRequestError, UnprocessableEntityError, InternalServerError. All extend ApiError. See ApiError.java.

License

MIT. See LICENSE.