LoftBox — AI 에이전트를 위한 API-first 이메일 인프라 — 의 공식 Java SDK 및 Spring Boot starter 입니다.
loftbox-sdk— 순수 Java 클라이언트. HTTP 는 JDKjava.net.http.HttpClient(외부 의존 0), JSON 직렬화만 Jackson 에 의존합니다.loftbox-spring-boot-starter—application.yml설정만으로LoftBoxClient빈을 자동 등록하는 Spring Boot starter.
Java 17+ 를 요구합니다.
<dependency>
<groupId>net.loftbox</groupId>
<artifactId>loftbox-sdk</artifactId>
<version>0.1.0</version>
</dependency>Spring Boot 를 쓴다면 core 대신 starter 만 추가하면 됩니다(starter 가 core 를 전이 의존):
<dependency>
<groupId>net.loftbox</groupId>
<artifactId>loftbox-spring-boot-starter</artifactId>
<version>0.1.0</version>
</dependency>implementation("net.loftbox:loftbox-sdk:0.1.0")
// 또는 Spring Boot:
implementation("net.loftbox:loftbox-spring-boot-starter:0.1.0")import net.loftbox.sdk.*;
import java.util.List;
LoftBoxClient client = LoftBoxClient.builder()
.apiKey(System.getenv("LOFTBOX_API_KEY"))
// .baseUrl("https://api.loftbox.net") // 기본값
// .timeout(Duration.ofSeconds(30)) // 기본값
.build();
// 에이전트 + 메일박스
Agent agent = client.agents().create(
AgentCreateParams.builder().name("Support Bot").slug("support").build());
Mailbox mailbox = client.mailboxes().create(agent.id(),
MailboxCreateParams.builder().localPart("support").build());
// 메시지 발송 (멱등 키로 중복 방지)
Message sent = client.messages().send(SendMessageParams.builder()
.mailboxId(mailbox.id())
.to(List.of("customer@example.com"))
.subject("환영합니다")
.bodyText("LoftBox 로 발송된 첫 메일입니다.")
.idempotencyKey("welcome-001")
.build());
// 수신함 페이지네이션
Page<Message> inbox = client.mailboxes().listInbox(mailbox.id(), 50, null);
String next = inbox.nextCursor(); // null 이면 마지막 페이지application.yml:
loftbox:
api-key: ${LOFTBOX_API_KEY}
# base-url: https://api.loftbox.net # 선택
# timeout: 30s # 선택LoftBoxClient 빈이 자동 등록됩니다 — 그대로 주입해서 쓰면 됩니다:
@Service
public class MailService {
private final LoftBoxClient client;
public MailService(LoftBoxClient client) {
this.client = client;
}
public void notify(String mailboxId, String to) {
client.messages().send(SendMessageParams.builder()
.mailboxId(mailboxId).to(List.of(to))
.subject("알림").bodyText("내용").build());
}
}
loftbox.api-key가 없으면 빈은 등록되지 않습니다. 직접LoftBoxClient빈을 정의하면 자동 구성은 물러납니다(@ConditionalOnMissingBean).
client.<resource>().<method>(...) 형태로 호출합니다.
| 리소스 | 주요 메서드 |
|---|---|
auth() |
signup, verifySignup |
agents() |
create, get, list |
mailboxes() |
create, listByAgent, listInbox, ackInbox |
messages() |
send, get, list, addLabels, removeLabel, approve, reject |
threads() |
list, listMessages |
webhooks() |
create |
domains() |
create, list, status |
suppressions() |
list, create, remove |
inboundRules() |
list, create, remove |
attachments() |
listForMessage, presignedUrl |
목록 메서드는 모두 Page<T> 를 반환합니다(data() + nextCursor()). 서버가 배열을 직접 반환해도 nextCursor=null 페이지로 정규화됩니다.
LoftBox 는 에이전트가 받는 메일에 대한 두 가지 안전 장치를 노출합니다.
수신 Message 에는 휴리스틱 신호가 실립니다 — 차단이 아니라 신호 전용입니다. LLM 에 본문을 넘기기 전에 게이트로 쓰세요.
for (Message m : client.mailboxes().listInbox(mailboxId).data()) {
Double score = m.injectionScore(); // 0~1, 높을수록 의심 (null 가능)
if (score != null && score > 0.5) {
// m.injectionCategories() 예: ["instruction_override", "data_exfil"]
// → 사람 검토로 보내거나 LLM 컨텍스트에서 제외
}
String clean = m.extractedText(); // #229 인용 제거 본문
}발신자 단위 allow/block 리스트로 수신 자체를 통제합니다.
// 특정 도메인 차단 (org 전체 — mailboxId 미지정)
client.inboundRules().create(InboundRuleCreateParams.builder()
.ruleType("block").patternType("domain").pattern("spam.example").build());
// 특정 메일박스만 allow 리스트로 운영
client.inboundRules().create(InboundRuleCreateParams.builder()
.ruleType("allow").patternType("address").pattern("ceo@partner.com")
.mailboxId(mailboxId).build());ruleType 은 allow|block, patternType 은 address(정확 주소)|domain 입니다. mailboxId 가 없으면 org 전체에 적용됩니다. block 매치 또는 allow 리스트 미매치 발신자는 수신이 거부됩니다.
모든 API 에러는 LoftBoxException(statusCode() / body() / requestId()) 의 서브클래스로 던져집니다.
| 예외 | HTTP |
|---|---|
ValidationException |
400, 422 |
AuthenticationException |
401 |
PermissionException |
403 |
NotFoundException |
404 |
ConflictException |
409 |
RateLimitException |
429 (retryAfterSecs() 제공) |
LoftBoxException |
그 외 / 전송·직렬화 실패 |
try {
client.messages().send(params);
} catch (RateLimitException e) {
Thread.sleep(e.retryAfterSecs() * 1000L); // 재시도
} catch (ValidationException e) {
log.warn("잘못된 요청: {} (req {})", e.getMessage(), e.requestId());
}에러 메시지는 응답 바디 {"error":{"message":...,"retry_after":...}} 에서 추출하며, Retry-After 헤더가 있으면 그 값을 우선합니다. x-request-id 응답 헤더는 requestId() 로 보존됩니다.
mvn -B verify게시(메인테이너): mvn -Prelease deploy (source/javadoc JAR + GPG 서명 활성).
MIT