AI 에이전트를 위한 API-first 이메일 인프라 LoftBox 의 공식 .NET SDK.
System.Net.Http.HttpClient + System.Text.Json 만 사용하며 ASP.NET Core DI 통합을 제공합니다.
dotnet add package LoftBox.Sdk대상 프레임워크: net8.0.
using LoftBox.Sdk;
using var client = new LoftBoxClient("lb_live_xxx");
// 에이전트 + 메일박스
var agent = await client.Agents.CreateAsync(new AgentCreateParams
{
Name = "Support Bot",
Slug = "support-bot",
});
var mailbox = await client.Mailboxes.CreateAsync(agent.Id, new MailboxCreateParams
{
LocalPart = "support",
});
// 발송 (멱등 키로 중복 방지)
var msg = await client.Messages.SendAsync(new SendMessageParams
{
MailboxId = mailbox.Id,
To = new[] { "recipient@example.com" },
Subject = "Hello",
BodyText = "World",
IdempotencyKey = "welcome-42",
});
// 수신 폴링 → ack
var inbox = await client.Mailboxes.ListInboxAsync(mailbox.Id);
var ids = inbox.Data.Select(m => m.Id).ToArray();
await client.Mailboxes.AckInboxAsync(mailbox.Id, ids);모든 요청 메서드는 마지막 인자로 CancellationToken 을 받습니다(취소·타임아웃 제어).
AddLoftBox 는 IHttpClientFactory 가 관리하는 HttpClient 를 사용해
LoftBoxClient 를 싱글톤으로 등록합니다(소켓 고갈 없이 안전 재사용).
// Program.cs
builder.Services.AddLoftBox(options =>
{
options.ApiKey = builder.Configuration["LoftBox:ApiKey"]!;
// options.BaseUrl = "https://api.loftbox.net"; // 기본값
// options.Timeout = TimeSpan.FromSeconds(30); // 기본값
});// 컨트롤러/서비스에 주입
public sealed class NotifyService(LoftBoxClient loftbox)
{
public Task SendAsync(string mailboxId, string to, CancellationToken ct) =>
loftbox.Messages.SendAsync(new SendMessageParams
{
MailboxId = mailboxId,
To = new[] { to },
Subject = "안녕하세요",
BodyMarkdown = "**LoftBox** 에서 보냅니다.",
}, ct);
}// API 키만
using var c1 = new LoftBoxClient("lb_live_xxx");
// baseUrl / timeout 지정
using var c2 = new LoftBoxClient("lb_live_xxx",
baseUrl: "https://api.loftbox.net", // 기본값
timeout: TimeSpan.FromSeconds(30)); // 기본값
// 커스텀 HttpClient 주입(프록시/테스트) — 타임아웃은 그 클라이언트가 통제
using var c3 = new LoftBoxClient(
new LoftBoxClientOptions { ApiKey = "lb_live_xxx" },
httpClient: myHttpClient);ApiKey 는 필수입니다.
- 발송
Messages.SendAsync(...)— 텍스트/HTML/Markdown, 첨부, cc, 답장 헤더 - 예약 발송
SendAsync(new SendMessageParams { ..., SendAt = "2030-01-01T09:00:00Z" }) - 멱등 발송
SendAsync(new SendMessageParams { ..., IdempotencyKey = "..." }) - 수신
Mailboxes.ListInboxAsync()+AckInboxAsync(),Message.ExtractedText(인용 제거 본문) - 라벨
Messages.AddLabelsAsync(),RemoveLabelAsync(),ListAsync(new ListMessagesParams { Label = ... }) - 전문검색
Messages.ListAsync(new ListMessagesParams { Q }),Threads.ListAsync(new ListThreadsParams { Q }) - 스레드
Threads.ListAsync(),ListMessagesAsync() - 승인
Messages.ApproveAsync(id, reason),RejectAsync(...) - 웹훅
Webhooks.CreateAsync(agentId, url, eventTypes) - 도메인 / suppression
Domains.*,Suppressions.* - 인바운드 안전 (#369/#370)
Message.InjectionScore/InjectionCategories(프롬프트-인젝션 신호, 차단 아님) +InboundRules.*(발신자 allow/block) - 첨부
Attachments.ListForMessageAsync(),PresignedUrlAsync()
수신 메일은 임의 외부 발신자가 보낸 untrusted 입력입니다. 두 가지 통제를 제공합니다.
// #369: 수신 메시지마다 프롬프트-인젝션 휴리스틱 점수(0~1) + 발화 카테고리.
// 신호 전용 — LoftBox 는 차단하지 않으며, 에이전트가 판단합니다.
var inbox = await client.Mailboxes.ListInboxAsync("mb_xxx");
foreach (var msg in inbox.Data)
{
if (msg.InjectionScore is >= 0.7)
{
// 예: 사람 승인 후에만 메일 내 지시를 따른다.
RequireHumanReview(msg);
}
// msg.InjectionCategories: ["instruction_override", ...]
}
// #370: 발신자 allow/block 리스트로 수신 자체를 통제(SMTP 거부).
await client.InboundRules.CreateAsync(new InboundRuleCreateParams
{
RuleType = "block", PatternType = "domain", Pattern = "evil.com",
});
await client.InboundRules.CreateAsync(new InboundRuleCreateParams
{
RuleType = "allow", PatternType = "address", Pattern = "partner@trusted.com",
MailboxId = "mb_xxx", // 미지정 시 org 전체
});
var rules = await client.InboundRules.ListAsync(new ListInboundRulesParams { MailboxId = "mb_xxx" });
await client.InboundRules.RemoveAsync("rule_id");allow 리스트가 하나라도 있으면 미매치 발신자는 거부됩니다(화이트리스트). 평가는 위조 가능한
From 헤더가 아니라 SMTP envelope sender 로 합니다.
모든 API 에러는 LoftBoxException 을 상속합니다. 상태별 서브클래스로 분기하세요.
try
{
await client.Messages.SendAsync(/* ... */);
}
catch (RateLimitException ex)
{
if (ex.RetryAfterSecs is { } secs)
Console.WriteLine($"{secs}s 후 재시도");
}
catch (NotFoundException ex)
{
Console.WriteLine($"{ex.StatusCode} {ex.Message}");
}
catch (LoftBoxException ex)
{
// 공통 필드: StatusCode / Message / RequestId / Body
Console.WriteLine($"request={ex.RequestId} status={ex.StatusCode}");
}에러 타입 매핑: 400/422 → ValidationException, 401 → AuthenticationException,
403 → PermissionException, 404 → NotFoundException, 409 → ConflictException,
429 → RateLimitException(RetryAfterSecs), 그 외 → LoftBoxException.
목록 메서드는 Page<T>{ Data, NextCursor } 를 반환합니다(NextCursor 는 string?).
var page = await client.Messages.ListAsync(new ListMessagesParams { MailboxId = mb.Id, Limit = 50 });
while (true)
{
foreach (var m in page.Data) { /* ... */ }
if (page.NextCursor is null) break;
page = await client.Messages.ListAsync(new ListMessagesParams
{
MailboxId = mb.Id, Limit = 50, Cursor = page.NextCursor,
});
}응답 record 는 [JsonExtensionData] 를 두어 서버가 추가한 미지정 필드도 Extra 사전에 보존됩니다.
SDK 업그레이드 없이 신규 필드를 읽을 수 있습니다.
examples/Quickstart 참고(발송 + 수신 폴링).
MIT