Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
1d933f5
docs: expand CLAUDE.md with package structure, commands, and version …
Palbahngmiyine Jan 27, 2026
8eb0072
chore: upgrade Gradle 9.3.0, Kotlin 2.3.0, and dependencies
Palbahngmiyine Jan 27, 2026
4befdba
docs: add Tidy First principles to CLAUDE.md
Palbahngmiyine Jan 27, 2026
207d272
refactor: migrate from java.time to kotlin.time for date/time handling
Palbahngmiyine Jan 27, 2026
618f129
test: add unit tests for kotlin.time migration
Palbahngmiyine Jan 27, 2026
9a1498f
docs: add AGENTS.md knowledge base for SDK navigation and patterns
Palbahngmiyine Jan 27, 2026
f3c2528
feat(kakao): add BMS_FREE message type and BMS storage types
Palbahngmiyine Jan 27, 2026
0ed40eb
feat(kakao): add BMS Free enum types (ChatBubbleType, ButtonType)
Palbahngmiyine Jan 27, 2026
97c2c4a
feat(kakao): add BMS Free data classes (Button, Commerce, Coupon, Video)
Palbahngmiyine Jan 27, 2026
f5d2c73
feat(kakao): add BMS Free WideItem and Carousel models
Palbahngmiyine Jan 27, 2026
f733370
feat(kakao): expand KakaoBmsOption with all BMS Free fields
Palbahngmiyine Jan 27, 2026
f37b7a6
test(kakao): add BMS Free serialization tests
Palbahngmiyine Jan 27, 2026
54cdec3
Update gitignore rule
Palbahngmiyine Jan 27, 2026
7175035
test(kakao): add BMS Free E2E test suite
Palbahngmiyine Jan 28, 2026
df8e0e3
test(e2e): improve error output to display failedMessageList details
Palbahngmiyine Jan 28, 2026
3ae6ba5
fix(e2e): correct BMS Free test field usage and StorageType mapping
Palbahngmiyine Jan 28, 2026
d7c974b
feat(dto): add LocalDateTime support for date fields
Palbahngmiyine Jan 28, 2026
e2889f6
build: add kotlinx.serialization to shadow JAR relocate list
Palbahngmiyine Jan 28, 2026
7078b20
Remove NurigoApp.kt
Palbahngmiyine Jan 29, 2026
d74385d
refactor: extract BaseE2ETest and E2ETestUtils for E2E test infrastru…
Palbahngmiyine Jan 29, 2026
756741a
test(e2e): add comprehensive E2E tests for solapi-kotlin SDK
Palbahngmiyine Jan 29, 2026
b20f05c
refactor: use stdlib toKotlinInstant extension for LocalDateTime conv…
Palbahngmiyine Jan 29, 2026
510b0ad
refactor: extract uploadImage utility to BaseE2ETest
Palbahngmiyine Jan 29, 2026
1c8f412
refactor: make BmsFreeE2ETest extend BaseE2ETest
Palbahngmiyine Jan 29, 2026
c170e1e
refactor: make ScheduledMessageE2ETest extend BaseE2ETest
Palbahngmiyine Jan 29, 2026
6e1937a
refactor: add multi-module auto-discovery to settings.gradle.kts
Palbahngmiyine Jan 29, 2026
31a8369
feat: add Java and Kotlin example modules
Palbahngmiyine Jan 29, 2026
3b1ce5d
docs: rewrite README with comprehensive SDK documentation
Palbahngmiyine Jan 30, 2026
c21dbb8
refactor: remove FriendTalk (CTA/CTI) support
Palbahngmiyine Jan 30, 2026
f365063
test: restore FriendTalk E2E tests for backward compatibility
Palbahngmiyine Jan 30, 2026
514528e
docs: prioritize Java examples in README
Palbahngmiyine Jan 30, 2026
b260cca
docs: add Java to README title
Palbahngmiyine Jan 30, 2026
13cd593
docs: update environment variables table formatting and description
Palbahngmiyine Jan 30, 2026
9cddef5
docs: add JDK 8 user guide and improve code examples
Palbahngmiyine Jan 30, 2026
83d6ce8
docs: add AI quick start guide section to README
Palbahngmiyine Jan 30, 2026
ce638aa
Update README.md
Palbahngmiyine Jan 30, 2026
f263440
Update README.md
Palbahngmiyine Jan 30, 2026
c5aa9f8
docs: add LLM guide and version sync hook
Palbahngmiyine Jan 30, 2026
82abfa1
docs: add JDK 8 compatible Java examples to LLM guide
Palbahngmiyine Jan 30, 2026
1fcf092
refactor: remove explicit ATA type from Kakao Alimtalk examples
Palbahngmiyine Jan 30, 2026
6614de8
docs: add Agent Workflow section and translate LLM guide to English
Palbahngmiyine Jan 30, 2026
ad1ace1
refactor: consolidate Kotlin compile tasks and update version require…
Palbahngmiyine Jan 30, 2026
e7d6838
build: auto-exclude example modules during publish tasks
Palbahngmiyine Jan 30, 2026
137f7e3
ci: add CI and Maven Central publish workflows
Palbahngmiyine Feb 1, 2026
be5b4b8
ci: update JDK version to 21 for Gradle 9.x compatibility
Palbahngmiyine Feb 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# SOLAPI API Credentials
SOLAPI_API_KEY=
SOLAPI_API_SECRET=

# Kakao Business Channel
KAKAO_PF_ID=

# Phone Numbers (Optional)
SENDER_NUMBER=
TEST_PHONE_NUMBER=
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ signing.gpg
*.gpg
*.asc
secret.key
.env
.env.local

# Generated files
/docs/
manual/
manual/

# OMO, OMC
.sisyphus/
.omc/
187 changes: 187 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# SOLAPI Kotlin SDK - Knowledge Base

**Generated:** 2026-01-27 | **Commit:** 618f129 | **Branch:** main

## CRITICAL: Development Principles

**MUST follow `CLAUDE.md` development principles:**

| Principle | Rule |
|-----------|------|
| **Tidy First** | NEVER mix structural and behavioral changes in a single commit |
| **Commit Separation** | `refactor:` (structural) vs `feat:`/`fix:` (behavioral) in separate commits |
| **TDD** | Write tests first (Red → Green → Refactor) |
| **Single Responsibility** | Classes/methods have single responsibility only |
| **Tidy Code First** | Clean up target area code before adding features |

```bash
# Correct commit order
git commit -m "refactor: extract validation logic to separate method"
git commit -m "feat: add phone number format validation"

# Forbidden (mixed commit)
git commit -m "feat: add validation and refactor code" # ❌ FORBIDDEN
```

---

## OVERVIEW

Kotlin/Java SDK for SOLAPI messaging platform. Supports SMS, LMS, MMS, Kakao Alimtalk/Brand Message, Naver Smart Notification, RCS, Fax, and Voice messaging.

## STRUCTURE

```
src/main/java/com/solapi/sdk/
├── SolapiClient.kt # Entry point (use this)
├── NurigoApp.kt # DEPRECATED - do not use
└── message/
├── service/ # API operations (send, query, templates)
├── model/ # Domain models (Message, options)
│ └── kakao/ # 19 files - Kakao templates, buttons, options
├── dto/ # Request/Response DTOs
├── exception/ # Exception hierarchy (8 types)
└── lib/ # Internal utilities (auth, helpers)
```

## WHERE TO LOOK

| Task | Location | Notes |
|------|----------|-------|
| **Initialize SDK** | `SolapiClient.kt` | `createInstance(apiKey, secretKey)` |
| **Send messages** | `service/DefaultMessageService.kt` | `send(message)` or `send(messages)` |
| **Query messages** | `service/DefaultMessageService.kt` | `getMessageList(params)` |
| **Upload files** | `service/DefaultMessageService.kt` | `uploadFile(file, type)` for MMS/Fax |
| **Kakao Alimtalk** | `service/DefaultMessageService.kt` | 11 template methods |
| **Create Message** | `model/Message.kt` | Data class with all message options |
| **Kakao options** | `model/kakao/KakaoOption.kt` | Alimtalk/FriendTalk config |
| **Handle errors** | `exception/` | Catch specific `Solapi*Exception` types |
| **HTTP layer** | `service/MessageHttpService.kt` | Retrofit interface (internal) |
| **Auth** | `lib/Authenticator.kt` | HMAC-SHA256 (internal, auto-injected) |

## CODE PATTERNS

### Serialization
```kotlin
@Serializable
data class Message(
var to: String? = null,
var from: String? = null,
// All fields nullable with defaults for flexibility
)
```
- **ALWAYS** use `@Serializable` annotation
- **ALWAYS** use `kotlinx.serialization` (not Jackson/Gson)
- **ALWAYS** provide nullable fields with defaults

### Service Methods
```kotlin
@JvmOverloads // Java interop
@Throws(SolapiMessageNotReceivedException::class, ...)
fun send(messages: List<Message>, config: SendRequestConfig? = null): MultipleDetailMessageSentResponse
```
- **ALWAYS** annotate with `@JvmOverloads` for optional params
- **ALWAYS** declare `@Throws` for checked exceptions

### Exception Handling
```kotlin
// Internal: Map error codes to exceptions
when (errorResponse.errorCode) {
"ValidationError" -> throw SolapiBadRequestException(msg)
"InvalidApiKey" -> throw SolapiInvalidApiKeyException(msg)
else -> throw SolapiUnknownException(msg)
}
```
- Exceptions are `sealed interface` based (SolapiException)
- 8 specific exception types

### Phone Number Normalization
```kotlin
init {
from = from?.replace("-", "")
to = to?.replace("-", "")
}
```
- Dashes auto-stripped from phone numbers in `Message.init`

### Test Conventions
```kotlin
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.assertFailsWith

class AuthenticatorTest {
@Test
fun `generateAuthInfo returns HMAC-SHA256 format`() {
// Given
val authenticator = Authenticator("api-key", "secret")
// When
val result = authenticator.generateAuthInfo()
// Then
assertTrue(result.startsWith("HMAC-SHA256 "))
}
}
```
- **ALWAYS** use `kotlin.test` (NOT JUnit directly)
- **ALWAYS** use Given-When-Then comment structure
- **ALWAYS** use backtick method names for readability

## ANTI-PATTERNS

| Forbidden | Required |
|-----------|----------|
| `NurigoApp.initialize()` | `SolapiClient.createInstance()` |
| Direct `DefaultMessageService()` | Use factory via `SolapiClient` |
| Catch generic `Exception` | Catch specific `Solapi*Exception` |
| `net.nurigo.sdk` imports | `com.solapi.sdk` package only |
| Jackson/Gson serialization | `kotlinx.serialization` only |
| Mixed structural+behavioral commits | Separate commits per Tidy First |

## INTERNAL CLASSES (Do Not Use Directly)

- `Authenticator` - HMAC auth (auto-injected via interceptor)
- `ErrorResponse` - Internal error DTO
- `MessageHttpService` - Retrofit interface
- `JsonSupport` - Serialization config
- `MapHelper`, `Criterion` - Internal utilities

## EXCEPTION TYPES

| Exception | When Thrown |
|-----------|-------------|
| `SolapiApiKeyException` | Empty/missing API key |
| `SolapiInvalidApiKeyException` | Invalid credentials |
| `SolapiBadRequestException` | Validation error, bad input |
| `SolapiEmptyResponseException` | Server returned empty body |
| `SolapiFileUploadException` | File upload failed |
| `SolapiMessageNotReceivedException` | All messages failed (has `failedMessageList`) |
| `SolapiUnknownException` | Unclassified server error |

## KAKAO INTEGRATION

19 model files in `model/kakao/`:
- `KakaoOption` - Main config (pfId, templateId, variables)
- `KakaoAlimtalkTemplate*` - Template CRUD models
- `KakaoBrandMessageTemplate` - Brand message with carousels
- `KakaoButton`, `KakaoButtonType` - Button configurations

Template workflow: `getKakaoAlimtalkTemplateCategories()` → `createKakaoAlimtalkTemplate()` → `requestKakaoAlimtalkTemplateInspection()`

## BUILD & TEST

```bash
./gradlew clean build test # Full build
./gradlew test # Tests only
./gradlew shadowJar # Fat JAR with relocated deps
```

**Shadow JAR**: Dependencies relocated to `com.solapi.shadow.*` to prevent conflicts.

## NOTES

- **Java 8 target**: Code must work on JVM 1.8
- **Source location**: Kotlin in `src/main/java/` (unconventional but intentional)
- **Tests**: `src/test/kotlin/`, `kotlin.test` (Kotlin native), Given-When-Then style
- **Version**: Auto-generated at `build/generated/source/kotlin/com/solapi/sdk/Version.kt`
- **Docs**: Dokka output to `./docs/`, run `./gradlew dokkaGeneratePublicationHtml`
134 changes: 128 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,84 @@

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Development Principles

### Tidy First (Kent Beck)

코드 수정 및 기능 추가 시 반드시 Kent Beck의 **Tidy First** 원칙을 적용합니다.

> References:
> - https://tidyfirst.substack.com/p/augmented-coding-beyond-the-vibes
> - https://tidyfirst.substack.com/p/taming-the-genie-like-kent-beck

#### 핵심 원칙: 구조적 변경과 행위적 변경의 분리

모든 코드 변경은 두 가지 유형으로 분류됩니다:

| 유형 | 설명 | 예시 |
|------|------|------|
| **구조적 변경 (Structural)** | 동작 변경 없이 코드 구조만 개선 | 리네이밍, 메서드 추출, 파일 재구성, 중복 제거 |
| **행위적 변경 (Behavioral)** | 실제 기능 추가 또는 수정 | 새 API 추가, 버그 수정, 로직 변경 |

**절대 규칙**: 구조적 변경과 행위적 변경을 **하나의 커밋에 혼합하지 않습니다**.

#### 작업 순서

1. **Tidy First**: 기능 추가 전, 해당 영역의 코드를 먼저 정리
- 변수/함수명 명확하게 변경
- 복잡한 메서드 분리 (작고 특화된 클래스, 단일 책임)
- 중복 코드 제거
- 테스트 실행하여 동작 보존 확인
- **별도 커밋으로 분리** (예: `refactor: ...`)

2. **Behavioral Change**: 정리된 코드 위에 기능 구현
- TDD 사이클 적용: Red → Green → Refactor
- 가장 단순한 실패 테스트 먼저 작성
- 테스트 통과를 위한 최소한의 코드 구현
- **별도 커밋으로 분리** (예: `feat: ...`, `fix: ...`)

#### AI(Claude) 코딩 지침

Kent Beck 스타일로 코드를 작성합니다:

1. **페르소나 적용**: "code like Kent Beck"
- 모듈형 단위 테스트 작성 (모놀리식 테스트 스크립트 지양)
- 명확한 변수/함수 명명
- TDD 스타일 개발 습관

2. **아키텍처 명시**
- 적절한 디자인 패턴 선택 및 적용
- 작고 특화된 클래스로 행위 분리
- 단일 책임 원칙 준수

3. **변경 분리 필수**
- 구조적 변경 요청 시: 동작 변경 없이 리팩토링만 수행
- 기능 추가 요청 시: 필요한 경우 먼저 tidy 커밋을 분리하여 제안

#### 커밋 전략

```bash
# 좋은 예 - 분리된 커밋
git commit -m "refactor: extract validation logic to separate method"
git commit -m "feat: add phone number format validation"

# 나쁜 예 - 혼합된 커밋 (금지)
git commit -m "feat: add validation and refactor code"
```

#### 체크리스트

**기능 추가 또는 코드 수정 전:**
- [ ] 수정할 영역에 정리가 필요한 코드가 있는가?
- [ ] 있다면, 구조적 변경을 먼저 별도 커밋으로 완료했는가?
- [ ] 구조적 변경 후 테스트가 통과하는가?

**기능 구현 시:**
- [ ] 테스트를 먼저 작성했는가? (TDD)
- [ ] 최소한의 코드로 테스트를 통과시켰는가?
- [ ] 행위적 변경만 포함된 커밋인가?
- [ ] 클래스/메서드가 단일 책임을 가지는가?

## Development Commands

### Build and Test
Expand All @@ -12,6 +90,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
# Run tests only
./gradlew test

# Run a single test
./gradlew test --tests "ClassName.methodName"

# Build without tests
./gradlew build -x test
```
Expand All @@ -20,6 +101,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
```bash
# Generate Dokka documentation (outputs to ./docs/)
./gradlew dokkaGeneratePublicationHtml

# Generate Javadoc JAR
./gradlew dokkaJavadocJar

# Generate sources JAR
./gradlew sourcesJar
```

### Publishing
Expand All @@ -33,6 +120,30 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Architecture

### Package Structure
```
com.solapi.sdk/
├── SolapiClient.kt # Main entry point, factory methods
├── NurigoApp.kt # Application configuration
└── message/
├── dto/
│ ├── request/ # API request DTOs
│ │ └── kakao/ # Kakao-specific requests
│ └── response/ # API response DTOs
│ ├── common/ # Shared response types
│ └── kakao/ # Kakao-specific responses
├── exception/ # Custom exception hierarchy
├── lib/ # Utility classes (Authenticator, helpers)
├── model/ # Core domain models
│ ├── fax/ # Fax options
│ ├── group/ # Group messaging models
│ ├── kakao/ # Kakao templates and options
│ ├── naver/ # Naver options
│ ├── rcs/ # RCS options
│ └── voice/ # Voice message options
└── service/ # Service layer implementations
```

### Core Structure
- **Package Migration**: Recently migrated from `net.nurigo.sdk` to `com.solapi.sdk`
- **Main Entry Point**: `SolapiClient` object provides factory methods for creating service instances
Expand All @@ -50,6 +161,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- **Authenticator**: `Authenticator.kt` handles HMAC-based API authentication
- **Auto-injection**: Authentication headers are automatically added via OkHttp interceptor

#### Exception Hierarchy
- `SolapiException` - Base exception class
- `SolapiApiKeyException` - API key related errors
- `SolapiInvalidApiKeyException` - Invalid API key
- `SolapiBadRequestException` - Bad request errors
- `SolapiEmptyResponseException` - Empty response from server
- `SolapiFileUploadException` - File upload failures
- `SolapiMessageNotReceivedException` - Message delivery failures
- `SolapiUnknownException` - Unclassified errors

#### Specialized Features
- **Kakao Integration**: Full support for Alimtalk and Brand Message templates
- **File Upload**: Base64 encoding for MMS, Fax, and other file-based messages
Expand All @@ -64,16 +185,17 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Build Configuration
- **Target**: Java 8 compatibility
- **Kotlin**: Version 2.2.0 with coroutines and serialization
- **Dependencies**: OkHttp, Retrofit, Kotlinx Serialization, Apache Commons Codec
- **Kotlin**: Version 2.2.10 with kotlinx-serialization
- **Gradle**: Version 8.14.3 (via wrapper)
- **Dependencies**: OkHttp 5.1.0, Retrofit 3.0.0, Kotlinx Serialization 1.9.0, Apache Commons Codec 1.18.0
- **Shadow JAR**: Dependencies relocated to `com.solapi.shadow` namespace
- **Version Generation**: Build script auto-generates `Version.kt` with current version
- **Version Generation**: Build script auto-generates `Version.kt` at `build/generated/source/kotlin/com/solapi/sdk/Version.kt`

## Testing
- **Framework**: JUnit 5 Jupiter
- **Framework**: JUnit 5 Jupiter (configured but no tests written yet)
- **Run Command**: `./gradlew test`
- **Test Location**: `src/test/java/`
- **Test Location**: `src/test/java/` (to be created)

## Documentation
- **API Docs**: Generated with Dokka to `./docs/` directory
- **Examples**: Referenced external repository for usage examples
- **Examples**: Referenced external repository for usage examples
Loading