# 샌드고 (Sendgo) — 모든 SDK 가이드 전문
> 샌드고는 카카오 알림톡·친구톡과 SMS/LMS/MMS를 발송하는 한국 메시지 발송 플랫폼입니다. 20개 언어·프레임워크용 공식 SDK와 REST API를 제공합니다.
---
> **PHP에서 카카오 알림톡, 브랜드메시지, SMS를 가장 쉽게 발송하는 순수 PHP SDK**
`sendgo/php`는 [Sendgo](https://sendgo.io) 알림 API를 위한 **순수 PHP SDK**입니다.
Laravel 등 특정 프레임워크에 의존하지 않으며, `ext-curl`과 `ext-json`만으로 동작합니다.
---
## 설치
```bash
composer require sendgo/php
```
---
## 빠른 시작
```php
$_ENV['SENDGO_ACCESS_KEY'],
'secret_key' => $_ENV['SENDGO_SECRET_KEY'],
'kakao_sender_key' => $_ENV['SENDGO_KAKAO_SENDER_KEY'],
'sms_sender_key' => $_ENV['SENDGO_SMS_SENDER_KEY'],
'api_version' => 'v2',
]);
// 알림톡 발송
$sendgo->alimtalk->send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [
['contact' => '01012345678', 'name' => '홍길동', 'var1' => 'ORD-001', 'var2' => '29,000원'],
],
]);
// SMS 발송
$sendgo->sms->sendSms([
'content' => '[Sendgo] 인증번호: 123456 (5분 이내 입력)',
'contacts' => [['contact' => '01012345678']],
]);
```
---
## 알림톡 상세 사용법
```php
alimtalk->send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [
['contact' => '01011111111', 'name' => '홍길동', 'var1' => 'ORD-001', 'var2' => '29,000원'],
['contact' => '01022222222', 'name' => '김철수', 'var1' => 'ORD-002', 'var2' => '15,000원'],
['contact' => '01033333333', 'name' => '이영희', 'var1' => 'ORD-003', 'var2' => '52,000원'],
],
]);
// 예약 발송
$sendgo->alimtalk->send([
'templateCode' => 'PROMO_SUMMER_2026',
'scheduleType' => 'SCHEDULED',
'at' => '2026-07-28 09:00:00',
'contacts' => [['contact' => '01012345678', 'var1' => '여름 한정 50% 할인']],
]);
// 알림톡 실패 시 SMS 자동 대체 발송
$sendgo->alimtalk->send([
'templateCode' => 'DELIVERY_START_001',
'replaceSms' => 'Y',
'smsSubject' => '[배송 시작 안내]',
'smsContent' => "주문하신 상품이 출고되었습니다.\n송장번호: #{var2}",
'contacts' => [['contact' => '01012345678', 'var1' => 'ORD-001', 'var2' => '1234567890']],
]);
```
---
## 친구톡 사용법
> ⚠️ **Deprecated — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었습니다.**
> 2026-01-01 부터 친구톡 발송 요청은 카카오 측에서 **브랜드메시지(자유형)** 로 자동 대체 발송됩니다.
> 호출은 계속 성공하며, 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보내는 경로는
> 현재 이것뿐이므로 기존 코드를 당장 바꿀 필요는 없습니다.
>
> 다음의 경우에는 **브랜드메시지**를 사용하세요.
> - 템플릿 기반 리치 타입 (`FL`/`FC`/`FM`/`FP`/`FA`)
> - 채널 친구가 **아닌** 수신자 (`targeting` = `N` / `I`)
> - 수신 동의한 전체 채널 친구 동보 (`targeting` = `F`)
>
> 메시지 타입은 1:1 대응되며 변환은 서버가 처리합니다 — `FT`→`BT`, `FI`→`BI`, `FW`→`BW`,
> `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`.
```php
friendtalk->send([
'content' => '안녕하세요! 7월 한정 특가 이벤트를 확인해보세요.',
'contacts' => [['contact' => '01012345678']],
]);
// 이미지형
$sendgo->friendtalk->send([
'messageType' => 'FI',
'content' => '이번 주 특가 상품을 확인하세요!',
'imageUrl' => 'https://cdn.example.com/banner.jpg',
'imageLink' => 'https://example.com/event',
'contacts' => [['contact' => '01012345678']],
]);
// 버튼 포함
$sendgo->friendtalk->send([
'content' => '7월 쿠폰이 도착했습니다! 지금 바로 사용하세요.',
'buttons' => [
['name' => '쿠폰 받기', 'type' => 'WL', 'linkMo' => 'https://example.com/coupon'],
],
'contacts' => [['contact' => '01012345678']],
]);
```
---
## 브랜드메시지 사용법
브랜드메시지는 친구톡의 후속 채널입니다. 메시지 타입이 친구톡과 1:1 대응되며
(`FT`→`BT`, `FI`→`BI`, `FW`→`BW`, `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`),
요청에는 **친구톡 코드를 그대로** 넘기고 변환은 서버가 처리합니다.
친구톡과 달리 다음이 가능합니다.
- 채널 친구가 **아닌** 수신자에게 발송 (`targeting: N`)
- 수신 동의한 **전체 채널 친구 동보** 발송 (`targeting: F`, 수신자 목록 불필요)
- 리스트·캐러셀·커머스·동영상 등 **템플릿 기반 리치 메시지**
> v2 전용입니다. 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보낼 때는 여전히 친구톡 API 를 쓰세요 — 이 엔드포인트는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다. 친구톡 요청은 카카오 측에서 브랜드메시지(자유형)로 대체 발송됩니다.
```php
brandMessage->send([
'targeting' => 'M',
'messageType' => 'FL',
'friendTemplateUuid' => '9cd5460b-6458-4edc-9b11-c26d3013c340',
'contacts' => [['contact' => '01012345678', 'var1' => '29,000원']],
]);
// 동보 발송 — 수신 동의한 전체 채널 친구 (contacts 불필요)
$sendgo->brandMessage->broadcast([
'messageType' => 'FW',
'friendTemplateUuid' => '9cd5460b-6458-4edc-9b11-c26d3013c340',
]);
// 캠페인 조회
$list = $sendgo->brandMessage->campaigns(['count' => 10]);
$one = $sendgo->brandMessage->campaign('1f0a6d0e-6b3b-4f0f-9b2f-2f6f6a1b7c11');
```
---
## SMS / LMS / MMS 사용법
```php
sms->sendSms([
'content' => '[Sendgo] 인증번호: 123456 (5분 이내 입력)',
'contacts' => [['contact' => '01012345678']],
]);
// LMS (장문, 2,000자 이하)
$sendgo->sms->sendLms([
'subject' => '[중요] 서비스 점검 안내',
'content' => "안녕하세요. 서비스 점검이 예정되어 있습니다.\n\n■ 일시: 2026-07-25 02:00 ~ 06:00\n■ 영향: 전체 서비스",
'contacts' => [['contact' => '01012345678']],
]);
// MMS (이미지 포함)
$sendgo->sms->sendMms([
'subject' => '[이벤트] 7월 특가',
'content' => '이번 달 특가 상품을 확인하세요!',
'contacts' => [['contact' => '01012345678']],
]);
// 예약 문자
$sendgo->sms->sendSms([
'content' => '[알림] 예약 미팅을 확인해주세요.',
'scheduleType' => 'SCHEDULED',
'at' => '2026-07-23 08:00:00',
'contacts' => [['contact' => '01012345678']],
]);
```
---
## 프레임워크 통합
### Symfony
```php
sendgo->alimtalk->send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [['contact' => $phone, 'var1' => $orderNo]],
]);
}
public function sendShippingAlert(string $phone, string $trackingNo): void
{
$this->sendgo->alimtalk->send([
'templateCode' => 'SHIPPING_001',
'replaceSms' => 'Y',
'smsContent' => "배송이 시작되었습니다.\n송장번호: {$trackingNo}",
'contacts' => [['contact' => $phone, 'var1' => $trackingNo]],
]);
}
}
```
### Slim Framework
```php
set(Sendgo::class, fn() => new Sendgo([
'access_key' => $_ENV['SENDGO_ACCESS_KEY'],
'secret_key' => $_ENV['SENDGO_SECRET_KEY'],
'kakao_sender_key' => $_ENV['SENDGO_KAKAO_KEY'],
'api_version' => 'v2',
]));
```
### WordPress / WooCommerce
```php
get_option('sendgo_access_key'),
'secret_key' => get_option('sendgo_secret_key'),
'kakao_sender_key' => get_option('sendgo_kakao_key'),
'api_version' => 'v2',
]);
}
return $instance;
}
// WooCommerce 주문 완료 시 알림톡 발송
add_action('woocommerce_order_status_completed', function (int $orderId) {
$order = wc_get_order($orderId);
try {
get_sendgo()->alimtalk->send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [
['contact' => $order->get_billing_phone(), 'var1' => $order->get_order_number()],
],
]);
} catch (SendgoException $e) {
error_log("Sendgo 알림 실패: {$e->getMessage()}");
}
});
```
---
## 예외 처리
```php
alimtalk->send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [['contact' => '01012345678']],
]);
} catch (SendgoException $e) {
echo "발송 실패: HTTP {$e->getStatusCode()} [{$e->getErrorCode()}]" . PHP_EOL;
match ($e->getErrorCode()) {
'INVALID_ACCESS_KEY',
'INVALID_SECRET_KEY' => alertOps('Sendgo 인증키를 확인하세요.'),
'INVALID_TEMPLATE_CODE' => logger('존재하지 않는 템플릿'),
'PAYMENT_REQUIRED' => alertOps('Sendgo 크레딧이 부족합니다.'),
'IP_NOT_ALLOWED' => alertOps('허용되지 않은 IP'),
default => logger('알 수 없는 오류: ' . $e->getMessage()),
};
}
```
---
## 설정 옵션
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---------|------|------|--------|------|
| `access_key` | `string` | **필수** | — | Sendgo 액세스 키 |
| `secret_key` | `string` | **필수** | — | Sendgo 시크릿 키 |
| `kakao_sender_key` | `string\|null` | 선택 | `null` | 카카오 발신프로필 키 |
| `sms_sender_key` | `string\|null` | 선택 | `null` | SMS 발신자 키 |
| `api_version` | `string` | 선택 | `'v1'` | API 버전 (`v1` \| `v2`) |
| `url` | `string` | 선택 | `'https://sendgo.io'` | API 기본 URL |
---
## 짧은 URL
짧은 URL 은 메시지 본문의 링크를 줄이고, 그 링크가 실제로 눌렸는지 집계합니다.
문자는 바이트 수가 요금과 직결되므로 링크를 줄이면 그만큼 본문을 더 쓸 수 있습니다.
같은 원본 URL 을 다시 줄이면 **기존 링크가 그대로 반환**됩니다. 캠페인별로 반응을
따로 집계하려면 `forceNew` 로 새 코드를 만드세요.
`deactivate` 는 링크를 삭제하지 않고 리다이렉트만 중지합니다. 이미 발송한 메시지의
링크를 무효화할 때 쓰며, 누적 통계는 남고 이후 접속은 `410 Gone` 이 됩니다.
```php
// 짧은 URL 생성 (v2 전용)
$short = $sendgo->shortUrl->create([
'targetUrl' => 'https://example.com/promotions/summer-sale',
'title' => '여름 세일 랜딩',
]);
$link = $short['data']['shortUrl']; // 문자/알림톡 본문에 넣을 짧은 링크
$code = $short['data']['code'];
// 반응 통계 — 일별 추이 + 디바이스/유입경로/국가별 분해
$stats = $sendgo->shortUrl->stats($code, ['from' => '2026-08-01']);
$sendgo->shortUrl->list(['count' => 10]);
$sendgo->shortUrl->show($code);
$sendgo->shortUrl->deactivate($code); // 리다이렉트만 중지, 통계는 남는다
```
`stats` 는 일별 추이(`daily`)와 디바이스(`byDevice`)·유입경로(`byReferer`)·국가(`byCountry`)별
분해를 반환합니다. 일별 추이는 사전 집계 표에서 읽으므로 클릭이 많아도 응답 시간이 일정합니다.
## 변경 사항
### 1.2.1 (2026-08-14)
- 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
npm/PyPI/Packagist/Maven/NuGet/RubyGems 검색 결과에 그대로 찍히는 문자열이라
종료된 채널을 계속 홍보하고 있었습니다.
- 검색 키워드에 `brand-message` 를 추가했습니다 (`friendtalk` 은 유입 검색어라 유지).
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
### 1.1.0 (2026-08-11)
- 짧은 URL 추가 — `$sendgo->shortUrl` (생성/목록/상세/반응통계/중지)
- `HttpClient::delete()` 추가
- `Sendgo::__call` 에 `shortUrl`/`short_url` 포워딩 추가 (Laravel 파사드 대응)
## 라이선스
MIT License © 2026 [Sendgo](https://sendgo.io)
---
## 패키지 정보
- **패키지**: `sendgo/php` (Packagist)
- **저장소**: [send-go/php](https://github.com/send-go/php)
- **레지스트리**: https://packagist.org/packages/sendgo/php
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **Laravel에서 카카오 알림톡, 브랜드메시지, SMS를 가장 쉽게 발송하는 공식 Laravel 패키지**
`sendgo/laravel`은 [`sendgo/php`](https://github.com/send-go/php) 코어를 확장한 **Laravel 전용 패키지**입니다.
ServiceProvider 자동 등록, Facade, Config 게시 등 Laravel 통합을 완벽하게 제공합니다.
---
## 설치
```bash
composer require sendgo/laravel
```
Laravel의 패키지 자동 검색(Package Auto-Discovery)으로 ServiceProvider와 Facade가 자동 등록됩니다.
---
## 빠른 시작
### 1단계 — 환경변수 설정 (`.env`)
```env
SENDGO_ACCESS_KEY=your_access_key
SENDGO_SECRET_KEY=your_secret_key
SENDGO_KAKAO_SENDER_KEY=your_kakao_key
SENDGO_SMS_SENDER_KEY=your_sms_key
SENDGO_API_VERSION=v2
```
### 2단계 (선택) — 설정 파일 게시
```bash
php artisan vendor:publish --tag=sendgo-config
```
### 3단계 — 알림톡 전송
```php
sendgo->alimtalk->send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [
[
'contact' => $order->user->phone,
'name' => $order->user->name,
'var1' => $order->number,
'var2' => number_format($order->total) . '원',
],
],
]);
return response()->json(['success' => true]);
}
}
```
---
## Facade 사용법
```php
send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [['contact' => '01012345678', 'var1' => 'ORD-001']],
]);
// SMS 발송
Sendgo::sms()->sendSms([
'content' => '[인증] 인증번호: 123456',
'contacts' => [['contact' => '01012345678']],
]);
```
---
## 상세 사용법
### 알림톡
```php
alimtalk->send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [
['contact' => '01011111111', 'name' => '홍길동', 'var1' => 'ORD-001', 'var2' => '29,000원'],
['contact' => '01022222222', 'name' => '김철수', 'var1' => 'ORD-002', 'var2' => '15,000원'],
['contact' => '01033333333', 'name' => '이영희', 'var1' => 'ORD-003', 'var2' => '52,000원'],
],
]);
// 예약 발송
app(Sendgo::class)->alimtalk->send([
'templateCode' => 'PROMO_SUMMER_2026',
'scheduleType' => 'SCHEDULED',
'at' => '2026-07-28 09:00:00',
'contacts' => [['contact' => '01012345678', 'var1' => '여름 한정 50% 할인']],
]);
// SMS 자동 대체 발송
app(Sendgo::class)->alimtalk->send([
'templateCode' => 'DELIVERY_START_001',
'replaceSms' => 'Y',
'smsSubject' => '[배송 시작 안내]',
'smsContent' => "주문하신 상품이 출고되었습니다.\n송장번호: #{var2}",
'contacts' => [['contact' => '01012345678', 'var1' => 'ORD-001', 'var2' => '1234567890']],
]);
```
### 친구톡
> ⚠️ **Deprecated — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었습니다.**
> 2026-01-01 부터 친구톡 발송 요청은 카카오 측에서 **브랜드메시지(자유형)** 로 자동 대체 발송됩니다.
> 호출은 계속 성공하며, 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보내는 경로는
> 현재 이것뿐이므로 기존 코드를 당장 바꿀 필요는 없습니다.
>
> 다음의 경우에는 **브랜드메시지**를 사용하세요.
> - 템플릿 기반 리치 타입 (`FL`/`FC`/`FM`/`FP`/`FA`)
> - 채널 친구가 **아닌** 수신자 (`targeting` = `N` / `I`)
> - 수신 동의한 전체 채널 친구 동보 (`targeting` = `F`)
>
> 메시지 타입은 1:1 대응되며 변환은 서버가 처리합니다 — `FT`→`BT`, `FI`→`BI`, `FW`→`BW`,
> `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`.
```php
friendtalk->send([
'content' => '안녕하세요! 7월 한정 특가 이벤트를 확인해보세요.',
'contacts' => [['contact' => '01012345678']],
]);
// 이미지형
app(Sendgo::class)->friendtalk->send([
'messageType' => 'FI',
'content' => '이번 주 특가 상품을 확인하세요!',
'imageUrl' => 'https://cdn.example.com/banner.jpg',
'imageLink' => 'https://example.com/event',
'contacts' => [['contact' => '01012345678']],
]);
// 버튼 포함
app(Sendgo::class)->friendtalk->send([
'content' => '7월 쿠폰이 도착했습니다! 지금 바로 사용하세요.',
'buttons' => [
['name' => '쿠폰 받기', 'type' => 'WL', 'linkMo' => 'https://example.com/coupon'],
['name' => '고객센터', 'type' => 'WL', 'linkMo' => 'https://example.com/cs'],
],
'contacts' => [['contact' => '01012345678']],
]);
```
### SMS / LMS / MMS
```php
sms->sendSms([
'content' => '[Sendgo] 인증번호: 123456 (5분 이내 입력)',
'contacts' => [['contact' => '01012345678']],
]);
// LMS (장문, 2,000자 이하)
app(Sendgo::class)->sms->sendLms([
'subject' => '[중요] 서비스 점검 안내',
'content' => "안녕하세요. 서비스 점검이 예정되어 있습니다.\n\n■ 일시: 2026-07-25 02:00 ~ 06:00\n■ 영향: 전체 서비스",
'contacts' => [['contact' => '01012345678']],
]);
// MMS (이미지 포함)
app(Sendgo::class)->sms->sendMms([
'subject' => '[이벤트] 7월 특가',
'content' => '이번 달 특가 상품을 확인하세요!',
'contacts' => [['contact' => '01011111111'], ['contact' => '01022222222']],
]);
```
---
## 서비스 클래스 패턴
```php
sendgo->alimtalk->send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [
['contact' => $phone, 'var1' => $orderNo, 'var2' => number_format($amount) . '원'],
],
]);
}
public function sendShippingAlert(string $phone, string $trackingNo): void
{
$this->sendgo->alimtalk->send([
'templateCode' => 'SHIPPING_001',
'replaceSms' => 'Y',
'smsContent' => "배송이 시작되었습니다.\n송장번호: {$trackingNo}",
'contacts' => [['contact' => $phone, 'var1' => $trackingNo]],
]);
}
public function sendVerificationCode(string $phone, string $code): void
{
try {
// 알림톡 우선, 실패 시 SMS 대체
$this->sendgo->alimtalk->send([
'templateCode' => 'VERIFY_CODE_001',
'replaceSms' => 'Y',
'smsContent' => "[인증] 인증번호: {$code} (5분 이내 입력)",
'contacts' => [['contact' => $phone, 'var1' => $code]],
]);
} catch (SendgoException $e) {
Log::error('Sendgo 인증번호 발송 실패', [
'phone' => $phone,
'error_code' => $e->getErrorCode(),
'status' => $e->getStatusCode(),
]);
throw $e;
}
}
}
```
```php
app->bind(NotificationService::class, function ($app) {
return new NotificationService($app->make(Sendgo::class));
});
```
---
## Notification Channel
```php
'ORDER_CONFIRM_001',
'contacts' => [
[
'contact' => $notifiable->phone,
'var1' => $this->orderNo,
'var2' => number_format($this->amount) . '원',
],
],
];
}
}
```
---
## Queue / Job 비동기 발송
```php
alimtalk->send([
'templateCode' => $this->templateCode,
'contacts' => $this->contacts,
]);
}
public function failed(SendgoException $e): void
{
Log::error('알림톡 발송 실패', [
'templateCode' => $this->templateCode,
'error_code' => $e->getErrorCode(),
]);
}
}
// 디스패치 예시
SendAlimtalkJob::dispatch('ORDER_CONFIRM_001', [
['contact' => '01012345678', 'var1' => 'ORD-001'],
])->onQueue('notifications');
```
---
## 예외 처리
```php
alimtalk->send([...]);
} catch (SendgoException $e) {
Log::error('Sendgo 발송 실패', [
'status' => $e->getStatusCode(),
'error_code' => $e->getErrorCode(),
'endpoint' => $e->getEndpoint(),
]);
match ($e->getErrorCode()) {
'INVALID_ACCESS_KEY',
'INVALID_SECRET_KEY' => alertOps('Sendgo 인증키 오류'),
'INVALID_TEMPLATE_CODE' => logger()->warning('존재하지 않는 템플릿'),
'PAYMENT_REQUIRED' => alertOps('Sendgo 크레딧 부족'),
'IP_NOT_ALLOWED' => alertOps('허용되지 않은 IP'),
default => null,
};
}
```
---
## 설정 옵션
`config/sendgo.php` (vendor:publish 후 커스터마이징 가능):
| 키 | 환경변수 | 기본값 | 설명 |
|----|---------|--------|------|
| `access_key` | `SENDGO_ACCESS_KEY` | — | Sendgo 액세스 키 |
| `secret_key` | `SENDGO_SECRET_KEY` | — | Sendgo 시크릿 키 |
| `kakao_sender_key` | `SENDGO_KAKAO_SENDER_KEY` | `null` | 카카오 발신프로필 키 |
| `sms_sender_key` | `SENDGO_SMS_SENDER_KEY` | `null` | SMS 발신자 키 |
| `api_version` | `SENDGO_API_VERSION` | `'v2'` | API 버전 |
| `url` | `SENDGO_URL` | `'https://sendgo.io'` | API 기본 URL |
---
## 자주 묻는 질문 (FAQ)
**Q. `sendgo/php`와의 차이는 무엇인가요?**
A. `sendgo/php`는 프레임워크 독립적인 순수 PHP 코어 패키지입니다. `sendgo/laravel`은 이를 확장해 ServiceProvider 자동 등록, Facade, .env 설정 바인딩 등 Laravel 통합을 추가합니다.
**Q. Laravel 10, 11, 12 모두 지원하나요?**
A. 네, `illuminate/support` `^10.0|^11.0|^12.0`을 지원합니다.
**Q. Facade를 사용하지 않고 DI로만 쓸 수 있나요?**
A. 네, `Sendgo\Php\Sendgo`를 생성자에서 타입힌트로 주입받아 사용할 수 있습니다.
**Q. 테스트 시 Sendgo를 Mock 처리하려면?**
A. `Sendgo\Php\Sendgo`를 Mockery나 PHPUnit Mock으로 교체하면 됩니다.
---
## 브랜드메시지 · 짧은 URL
이 패키지는 코어(`sendgo/php`)의 클라이언트를 그대로 노출하므로, 코어에 있는 채널이
모두 그대로 쓸 수 있습니다. 두 기능 모두 **v2 전용**입니다.
| 기능 | 접근 |
|------|------|
| 카카오 브랜드메시지 (친구톡의 후속 채널) | `Sendgo::brandMessage()` |
| 짧은 URL (단축 + 클릭 반응 분석) | `Sendgo::shortUrl()` |
브랜드메시지는 채널 친구가 아닌 수신자에게도 보낼 수 있고(`targeting` = `N`),
수신 동의한 전체 채널 친구에게 동보 발송할 수도 있습니다(`targeting` = `F`).
짧은 URL 은 메시지 본문의 링크를 줄이고 클릭 반응(일별 추이·디바이스·유입경로·국가)을
집계합니다.
사용 예시와 파라미터는 [코어 README](https://github.com/send-go) 와
[SDK 가이드](https://sendgo.io/ko/sdk) 를 참고하세요.
## 변경 사항
### 1.2.1 (2026-08-14)
- 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
npm/PyPI/Packagist/Maven/NuGet/RubyGems 검색 결과에 그대로 찍히는 문자열이라
종료된 채널을 계속 홍보하고 있었습니다.
- 검색 키워드에 `brand-message` 를 추가했습니다 (`friendtalk` 은 유입 검색어라 유지).
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
### 1.1.0 (2026-08-11)
- 파사드에 `shortUrl()` 문서화 (`@method` 주석) — IDE 자동완성 대응
## 라이선스
MIT License © 2026 [Sendgo](https://sendgo.io)
---
## 패키지 정보
- **패키지**: `sendgo/laravel` (Packagist)
- **저장소**: [send-go/laravel](https://github.com/send-go/laravel)
- **레지스트리**: https://packagist.org/packages/sendgo/laravel
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **Symfony에서 카카오 알림톡, 브랜드메시지, SMS를 가장 쉽게 발송하는 공식 Symfony 번들**
`sendgo/symfony`는 [`sendgo/php`](https://github.com/send-go/php) 코어를 확장한 **Symfony 전용 번들**입니다.
DI 컨테이너 서비스 자동 등록, 설정(config) 통합, 오토와이어링을 완벽하게 제공합니다.
---
## 설치
```bash
composer require sendgo/symfony
```
### 번들 등록
[Symfony Flex](https://symfony.com/doc/current/setup/flex.html)를 사용하는 경우 번들이 자동으로 등록됩니다.
Flex를 사용하지 않는다면 `config/bundles.php`에 직접 추가하세요.
```php
['all' => true],
];
```
---
## 빠른 시작
### 1단계 — 환경변수 설정 (`.env`)
```env
SENDGO_ACCESS_KEY=your_access_key
SENDGO_SECRET_KEY=your_secret_key
SENDGO_KAKAO_SENDER_KEY=your_kakao_key
SENDGO_SMS_SENDER_KEY=your_sms_key
SENDGO_API_VERSION=v2
```
### 2단계 — 번들 설정 파일 (`config/packages/sendgo.yaml`)
```yaml
# config/packages/sendgo.yaml
sendgo:
access_key: '%env(SENDGO_ACCESS_KEY)%'
secret_key: '%env(SENDGO_SECRET_KEY)%'
kakao_sender_key: '%env(SENDGO_KAKAO_SENDER_KEY)%'
sms_sender_key: '%env(SENDGO_SMS_SENDER_KEY)%'
api_version: '%env(SENDGO_API_VERSION)%'
url: 'https://sendgo.io'
```
### 3단계 — 알림톡 전송
```php
sendgo->alimtalk->send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [
[
'contact' => $order->getUser()->getPhone(),
'name' => $order->getUser()->getName(),
'var1' => $order->getNumber(),
'var2' => number_format($order->getTotal()) . '원',
],
],
]);
return $this->json(['success' => true]);
}
}
```
---
## 오토와이어링 사용법
번들이 `Sendgo\Php\Sendgo` 서비스를 컨테이너에 등록하므로, 생성자에 타입힌트만 하면
자동으로 주입됩니다. 별도의 서비스 정의는 필요하지 않습니다.
```php
get('sendgo'); // Sendgo\Php\Sendgo 인스턴스
```
---
## 상세 사용법
### 알림톡
```php
alimtalk->send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [
['contact' => '01011111111', 'name' => '홍길동', 'var1' => 'ORD-001', 'var2' => '29,000원'],
['contact' => '01022222222', 'name' => '김철수', 'var1' => 'ORD-002', 'var2' => '15,000원'],
['contact' => '01033333333', 'name' => '이영희', 'var1' => 'ORD-003', 'var2' => '52,000원'],
],
]);
// 예약 발송
$sendgo->alimtalk->send([
'templateCode' => 'PROMO_SUMMER_2026',
'scheduleType' => 'SCHEDULED',
'at' => '2026-07-28 09:00:00',
'contacts' => [['contact' => '01012345678', 'var1' => '여름 한정 50% 할인']],
]);
// SMS 자동 대체 발송
$sendgo->alimtalk->send([
'templateCode' => 'DELIVERY_START_001',
'replaceSms' => 'Y',
'smsSubject' => '[배송 시작 안내]',
'smsContent' => "주문하신 상품이 출고되었습니다.\n송장번호: #{var2}",
'contacts' => [['contact' => '01012345678', 'var1' => 'ORD-001', 'var2' => '1234567890']],
]);
```
### 친구톡
> ⚠️ **Deprecated — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었습니다.**
> 2026-01-01 부터 친구톡 발송 요청은 카카오 측에서 **브랜드메시지(자유형)** 로 자동 대체 발송됩니다.
> 호출은 계속 성공하며, 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보내는 경로는
> 현재 이것뿐이므로 기존 코드를 당장 바꿀 필요는 없습니다.
>
> 다음의 경우에는 **브랜드메시지**를 사용하세요.
> - 템플릿 기반 리치 타입 (`FL`/`FC`/`FM`/`FP`/`FA`)
> - 채널 친구가 **아닌** 수신자 (`targeting` = `N` / `I`)
> - 수신 동의한 전체 채널 친구 동보 (`targeting` = `F`)
>
> 메시지 타입은 1:1 대응되며 변환은 서버가 처리합니다 — `FT`→`BT`, `FI`→`BI`, `FW`→`BW`,
> `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`.
```php
friendtalk->send([
'content' => '안녕하세요! 7월 한정 특가 이벤트를 확인해보세요.',
'contacts' => [['contact' => '01012345678']],
]);
// 이미지형
$sendgo->friendtalk->send([
'messageType' => 'FI',
'content' => '이번 주 특가 상품을 확인하세요!',
'imageUrl' => 'https://cdn.example.com/banner.jpg',
'imageLink' => 'https://example.com/event',
'contacts' => [['contact' => '01012345678']],
]);
// 버튼 포함
$sendgo->friendtalk->send([
'content' => '7월 쿠폰이 도착했습니다! 지금 바로 사용하세요.',
'buttons' => [
['name' => '쿠폰 받기', 'type' => 'WL', 'linkMo' => 'https://example.com/coupon'],
['name' => '고객센터', 'type' => 'WL', 'linkMo' => 'https://example.com/cs'],
],
'contacts' => [['contact' => '01012345678']],
]);
```
### SMS / LMS / MMS
```php
sms->sendSms([
'content' => '[Sendgo] 인증번호: 123456 (5분 이내 입력)',
'contacts' => [['contact' => '01012345678']],
]);
// LMS (장문, 2,000자 이하)
$sendgo->sms->sendLms([
'subject' => '[중요] 서비스 점검 안내',
'content' => "안녕하세요. 서비스 점검이 예정되어 있습니다.\n\n■ 일시: 2026-07-25 02:00 ~ 06:00\n■ 영향: 전체 서비스",
'contacts' => [['contact' => '01012345678']],
]);
// MMS (이미지 포함)
$sendgo->sms->sendMms([
'subject' => '[이벤트] 7월 특가',
'content' => '이번 달 특가 상품을 확인하세요!',
'contacts' => [['contact' => '01011111111'], ['contact' => '01022222222']],
]);
```
---
## 서비스 클래스 패턴
```php
sendgo->alimtalk->send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [
['contact' => $phone, 'var1' => $orderNo, 'var2' => number_format($amount) . '원'],
],
]);
}
public function sendVerificationCode(string $phone, string $code): void
{
try {
// 알림톡 우선, 실패 시 SMS 대체
$this->sendgo->alimtalk->send([
'templateCode' => 'VERIFY_CODE_001',
'replaceSms' => 'Y',
'smsContent' => "[인증] 인증번호: {$code} (5분 이내 입력)",
'contacts' => [['contact' => $phone, 'var1' => $code]],
]);
} catch (SendgoException $e) {
$this->logger->error('Sendgo 인증번호 발송 실패', [
'phone' => $phone,
'error_code' => $e->getErrorCode(),
'status' => $e->getStatusCode(),
]);
throw $e;
}
}
}
```
`services.yaml`에서 `autowire: true`가 설정되어 있으면 `Sendgo\Php\Sendgo`가 자동 주입됩니다.
---
## Messenger 비동기 발송
[Symfony Messenger](https://symfony.com/doc/current/messenger.html)로 발송을 비동기 처리할 수 있습니다.
```php
sendgo->alimtalk->send([
'templateCode' => $message->templateCode,
'contacts' => $message->contacts,
]);
}
}
```
```php
// 디스패치 예시
$bus->dispatch(new SendAlimtalkMessage('ORDER_CONFIRM_001', [
['contact' => '01012345678', 'var1' => 'ORD-001'],
]));
```
---
## 예외 처리
```php
alimtalk->send([...]);
} catch (SendgoException $e) {
$logger->error('Sendgo 발송 실패', [
'status' => $e->getStatusCode(),
'error_code' => $e->getErrorCode(),
'endpoint' => $e->getEndpoint(),
]);
match ($e->getErrorCode()) {
'INVALID_ACCESS_KEY',
'INVALID_SECRET_KEY' => $this->alertOps('Sendgo 인증키 오류'),
'INVALID_TEMPLATE_CODE' => $logger->warning('존재하지 않는 템플릿'),
'PAYMENT_REQUIRED' => $this->alertOps('Sendgo 크레딧 부족'),
'IP_NOT_ALLOWED' => $this->alertOps('허용되지 않은 IP'),
default => null,
};
}
```
---
## 설정 옵션
`config/packages/sendgo.yaml` 에서 설정합니다:
| 키 | 환경변수 | 기본값 | 설명 |
|----|---------|--------|------|
| `access_key` | `SENDGO_ACCESS_KEY` | — (필수) | Sendgo 액세스 키 |
| `secret_key` | `SENDGO_SECRET_KEY` | — (필수) | Sendgo 시크릿 키 |
| `kakao_sender_key` | `SENDGO_KAKAO_SENDER_KEY` | `null` | 카카오 발신프로필 키 |
| `sms_sender_key` | `SENDGO_SMS_SENDER_KEY` | `null` | SMS 발신자 키 |
| `api_version` | `SENDGO_API_VERSION` | `'v2'` | API 버전 |
| `url` | `SENDGO_URL` | `'https://sendgo.io'` | API 기본 URL |
---
## 자주 묻는 질문 (FAQ)
**Q. `sendgo/php`와의 차이는 무엇인가요?**
A. `sendgo/php`는 프레임워크 독립적인 순수 PHP 코어 패키지입니다. `sendgo/symfony`는 이를 확장해 번들 자동 등록, DI 컨테이너 서비스 등록, config 바인딩, 오토와이어링 등 Symfony 통합을 추가합니다.
**Q. Symfony 6.4, 7 모두 지원하나요?**
A. 네, `symfony/config`, `symfony/dependency-injection`, `symfony/http-kernel` 모두 `^6.4|^7.0`을 지원합니다.
**Q. 오토와이어링 없이 서비스 ID로 접근할 수 있나요?**
A. 네, `sendgo` 별칭이 등록되어 있어 `$container->get('sendgo')`로 접근할 수 있습니다.
**Q. 테스트 시 Sendgo를 Mock 처리하려면?**
A. 테스트 컨테이너에서 `Sendgo\Php\Sendgo` 서비스를 PHPUnit Mock으로 교체하면 됩니다.
---
## 브랜드메시지 · 짧은 URL
이 패키지는 코어(`sendgo/php`)의 클라이언트를 그대로 노출하므로, 코어에 있는 채널이
모두 그대로 쓸 수 있습니다. 두 기능 모두 **v2 전용**입니다.
| 기능 | 접근 |
|------|------|
| 카카오 브랜드메시지 (친구톡의 후속 채널) | `$sendgo->brandMessage` |
| 짧은 URL (단축 + 클릭 반응 분석) | `$sendgo->shortUrl` |
브랜드메시지는 채널 친구가 아닌 수신자에게도 보낼 수 있고(`targeting` = `N`),
수신 동의한 전체 채널 친구에게 동보 발송할 수도 있습니다(`targeting` = `F`).
짧은 URL 은 메시지 본문의 링크를 줄이고 클릭 반응(일별 추이·디바이스·유입경로·국가)을
집계합니다.
사용 예시와 파라미터는 [코어 README](https://github.com/send-go) 와
[SDK 가이드](https://sendgo.io/ko/sdk) 를 참고하세요.
## 변경 사항
### 1.2.1 (2026-08-14)
- 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
npm/PyPI/Packagist/Maven/NuGet/RubyGems 검색 결과에 그대로 찍히는 문자열이라
종료된 채널을 계속 홍보하고 있었습니다.
- 검색 키워드에 `brand-message` 를 추가했습니다 (`friendtalk` 은 유입 검색어라 유지).
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
- 짧은 URL 지원 (1.1.0 릴리스 누락분 포함).
### 1.1.0 (2026-08-11)
- 브랜드메시지·짧은 URL 접근 방법 문서화 (코어를 그대로 노출)
## 라이선스
MIT License © 2026 [Sendgo](https://sendgo.io)
---
## 패키지 정보
- **패키지**: `sendgo/symfony` (Packagist)
- **저장소**: [send-go/symfony](https://github.com/send-go/symfony)
- **레지스트리**: https://packagist.org/packages/sendgo/symfony
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **카카오 알림톡·브랜드메시지와 SMS/LMS/MMS 를 발송하고, WooCommerce 주문 상태에 따라 구매자에게 자동으로 알리는 공식 WordPress 플러그인**
Sendgo 코어 SDK([`sendgo/php`](https://github.com/send-go/php))를 번들해 WordPress 에 연결합니다.
관리자 설정 화면에서 키를 넣고, WooCommerce 훅으로 주문 상태가 바뀔 때 구매자에게 알립니다.
PHP 8.2 이상이 필요합니다.
## 설치
### 1. wordpress.org / zip 으로 설치 (권장)
배포 zip 에는 코어 SDK 가 `vendor/` 에 포함되어 있습니다. Composer 없이 그대로 동작합니다.
**관리자 > 플러그인 > 새로 추가**에서 zip 을 업로드하거나 `wp-content/plugins/sendgo` 에 풀어 넣으세요.
### 2. Composer 로 설치
```bash
composer require sendgo/wordpress
```
또는 플러그인 디렉터리에서 직접:
```bash
cd wp-content/plugins/sendgo
composer install
```
소스에서 직접 받은 경우에는 `vendor/autoload.php` 가 없으므로 `composer install` 을 실행해야 합니다.
이 파일이 없으면 플러그인은 발송을 하지 못하고 관리자 화면에 원인을 알리는 오류 알림을 띄웁니다.
### 3. 플러그인 활성화
WordPress 관리자 > 플러그인 화면에서 **Sendgo** 를 활성화합니다.
## 설정
관리자 화면의 **Settings > Sendgo** 메뉴에서 다음 값을 입력합니다.
관리자 화면 문자열은 영어를 원본으로 하며, 한국어는 translate.wordpress.org 의 번역을 통해 표시됩니다.
| 항목 | 설명 |
| --- | --- |
| `Access Key` | 샌드고 콘솔에서 발급받은 액세스 키 |
| `Secret Key` | 샌드고 콘솔에서 발급받은 시크릿 키 |
| `Kakao Sender Key` | 알림톡·브랜드메시지 발신프로필 키 |
| `SMS Sender Key` | SMS/LMS/MMS 발신번호 키 |
| `API Version` | `v1` 또는 `v2` 선택 |
Access Key 와 Secret Key 가 모두 입력되어야 발송 기능이 활성화됩니다. 키는 `sendgo_options`
옵션에 서버 사이드로만 저장되며 프런트엔드에 노출되지 않습니다.
API 버전 기본값은 **`v1`** 입니다. 브랜드메시지와 짧은 URL 은 `v2` 에서만 동작하므로 해당
채널을 쓰려면 바꿔야 합니다.
> API 베이스 URL 은 `url` 옵션에서 읽지만 설정 화면에 필드가 없습니다. 다른 호스트를
> 가리키려면 코드로 지정하세요.
> ```php
> $options = get_option('sendgo_options', []);
> $options['url'] = 'https://staging.sendgo.io';
> update_option('sendgo_options', $options);
> ```
> 1.2.4 이전에는 설정 화면을 한 번 저장하면 이 값이 사라졌습니다. 이제는 저장해도 유지됩니다.
## WooCommerce 연동
WooCommerce가 활성화되어 있으면 다음 주문 상태 변경 시 구매자 청구 연락처로 알림을 자동 발송합니다.
- `주문 완료` (`woocommerce_order_status_completed`)
- `처리 중` (`woocommerce_order_status_processing`)
**Settings > Sendgo > WooCommerce Order Notifications** 섹션에서 **상태별로** 다음을 설정합니다.
| 필드 | 설명 |
| --- | --- |
| `[Order completed] Alimtalk template code` | 완료 상태에서 보낼 템플릿 코드. 첫 번째 변수(`#{var1}`)로 주문 번호가 전달됩니다. |
| `[Order completed] SMS text used if Alimtalk fails` | 알림톡이 실패하거나 템플릿 코드가 비어 있을 때 보낼 SMS 본문. `{order_number}` 플레이스홀더 사용 가능. |
| `[Processing] Alimtalk template code` | 처리 중 상태에서 보낼 템플릿 코드. |
| `[Processing] SMS text used if Alimtalk fails` | 처리 중 상태의 SMS 대체 본문. |
**비워둔 상태는 아무것도 발송하지 않습니다.** 두 상태 모두 채우면 주문마다
알림이 두 번(처리 중 + 완료) 발송되므로, 실제로 알려야 하는 상태만 채우세요.
같은 주문·같은 상태로는 한 번만 발송됩니다. 발송에 성공하면 주문 메타
(`_sendgo_notified_completed` / `_sendgo_notified_processing`)에 기록해두기 때문에,
관리자가 주문을 다시 저장하거나 다른 플러그인이 상태를 재설정해도 중복
발송되지 않습니다. 발송이 실패하면 기록하지 않으므로 다음 상태 변경에서 재시도됩니다.
발송 실패는 결제/주문 흐름을 절대 중단시키지 않으며, WooCommerce 로그(source: `sendgo`)에 기록됩니다.
> **1.0.x에서 올라오는 경우** — 예전 버전은 두 상태가 `order_template_code`
> 하나를 공유했기 때문에 처리 중 → 완료로 넘어가는 주문에 같은 알림이 두 번
> 발송됐습니다. 이제 완료 상태만 기존 키를 그대로 쓰고, 처리 중은 새 필드를
> 채워야 발송됩니다. 기존 설정은 그대로 동작하며 중복 발송만 사라집니다.
### 전화번호 처리
청구 연락처에서 숫자만 남겨 발송합니다(`preg_replace('/[^0-9]/', ...)`). `010-1234-5678` 과
`+82 10 1234 5678` 은 구두점만 제거되며 **국가번호를 변환하지는 않으므로**, API 가 받는
형식의 국내 번호여야 합니다. 청구 연락처가 비어 있는 주문은 조용히 건너뜁니다.
## 사용법 (프로그래밍 방식)
플러그인이 로드된 이후에는 코어 클라이언트를 직접 사용할 수 있습니다.
```php
$client = Sendgo_Plugin::instance()->client();
if ($client) {
// 알림톡 발송
$client->alimtalk->send([
'templateCode' => 'ORDER_CONFIRM_001',
'contacts' => [['contact' => '01012345678', 'var1' => 'ORD-001']],
]);
// SMS 발송
$client->sms->sendSms([
'content' => '인증번호: 123456',
'contacts' => [['contact' => '01012345678']],
]);
}
```
**항상 `$client` 를 확인하세요.** 키가 비어 있거나 `vendor/autoload.php` 가 없으면 `null` 이고,
`null` 에 메서드를 호출하면 그 훅이 걸린 페이지가 백지가 됩니다.
채널은 클라이언트의 속성으로 접근합니다.
| 속성 | 채널 |
| --- | --- |
| `$client->alimtalk` | 카카오 알림톡 |
| `$client->friendtalk` | 카카오 친구톡 |
| `$client->brandMessage` | 카카오 브랜드메시지 (v2 전용) |
| `$client->sms` | SMS / LMS / MMS |
클라이언트는 요청 단위로 메모이즈되므로 `Sendgo_Plugin::instance()->client()` 를 여러 번 불러도 비용이 없습니다.
### 내 이벤트에 붙이기
```php
add_action('user_register', function (int $user_id): void {
$client = Sendgo_Plugin::instance()->client();
if (!$client) {
return;
}
$user = get_userdata($user_id);
$phone = preg_replace('/[^0-9]/', '', (string) get_user_meta($user_id, 'billing_phone', true));
if ('' === $phone) {
return;
}
try {
$client->alimtalk->send([
'templateCode' => 'WELCOME_001',
'contacts' => [['contact' => $phone, 'var1' => $user->display_name]],
]);
} catch (\Throwable $e) {
// 발송 실패가 회원가입을 깨뜨리지 않게 한다.
error_log('Sendgo welcome message failed: ' . $e->getMessage());
}
}, 10, 1);
```
`try`/`catch` 로 감싸는 것이 핵심입니다. WordPress 훅 안에서 처리되지 않은
`SendgoException` 은 그 훅을 실행한 페이지의 치명적 오류로 드러납니다.
### 브랜드메시지 (친구톡의 후속 채널)
> **친구톡은 2025-12-31 종료되었습니다.** 2026-01-01 부터 친구톡 발송 요청은 카카오 측에서
> 브랜드메시지(자유형)로 자동 대체 발송됩니다. `friendtalk` 은 여전히 동작하며, 개별 수신자에게
> 보내는 자유 본문 `FT`/`FI`/`FW` 의 유일한 경로이기도 합니다. 템플릿 기반 리치 타입
> (`FL`/`FC`/`FM`/`FP`/`FA`), 친구가 아닌 대상(`N`/`I`), 동보(`F`)에는 브랜드메시지를 쓰세요.
브랜드메시지는 채널 친구가 아닌 수신자에게도 보낼 수 있고(`targeting` = `N`),
수신 동의한 전체 채널 친구에게 동보 발송할 수도 있습니다(`targeting` = `F`).
메시지 타입은 친구톡과 1:1로 대응합니다(`FT`→`BT`, `FI`→`BI`, `FW`→`BW`,
`FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`).
> **v2 전용입니다.** **Settings > Sendgo > API Version** 을 `v2` 로 바꿔야 동작합니다.
> 플러그인 기본값은 `v1` 입니다.
```php
$client = Sendgo_Plugin::instance()->client();
// 단건 발송 — targeting 이 M/N/I 이면 contacts 가 필요합니다.
$client->brandMessage->send([
'targeting' => 'M',
'messageType' => 'FL',
'friendTemplateUuid' => '9cd5460b-6458-4edc-9b11-c26d3013c340',
'contacts' => [['contact' => '01012345678', 'var1' => '29,000원']],
]);
// 동보 발송 — 수신 동의한 전체 채널 친구 (수신자 목록 없음)
$result = $client->brandMessage->broadcast([
'messageType' => 'FW',
'friendTemplateUuid' => '9cd5460b-6458-4edc-9b11-c26d3013c340',
]);
// 동보 발송은 업스트림에서 비동기 처리되므로 진행 상황을 조회합니다.
$client->brandMessage->campaign($result['data']['campaignId']);
$client->brandMessage->campaigns(['count' => 10]);
```
동보 발송은 친구 목록 전체에 닿을 수 있으므로, 공개 훅이 아니라 의도적인 관리자 동작
(WP-CLI 명령이나 권한 검사를 붙인 admin-post 핸들러)에서만 호출하세요.
### 짧은 URL (클릭 반응 분석)
메시지 본문에 넣는 링크를 줄이고, 실제로 눌렸는지 집계합니다. 문자는 한 통에
들어가는 바이트가 정해져 있어 긴 링크가 그대로 들어가면 LMS 로 넘어가 단가가 올라갑니다.
> **v2 전용입니다.**
같은 원본 URL 을 다시 줄이면 **기존 링크를 그대로 반환합니다.** 캠페인별로 반응 수치를
따로 보려면 `forceNew` 를 넘겨 새 코드를 만드세요.
`deactivate` 는 링크를 지우지 않고 리다이렉트만 멈춥니다. 이미 발송한 메시지의 링크를
막아야 할 때 쓰며, 쌓인 통계는 남고 방문자는 `410 Gone` 을 받습니다.
```php
$client = Sendgo_Plugin::instance()->client();
// 생성 — 같은 원본 URL 이면 기존 코드를 재사용합니다(forceNew 로 강제 신규 생성).
$link = $client->shortUrl->create([
'targetUrl' => 'https://shop.example.com/orders/1024',
'title' => '10월 주문 안내',
]);
$link['data']['shortUrl']; // https://sendgo.io/s/aB3xY7z
// 클릭 반응 — 일별 추이 · 디바이스 · 유입경로 · 국가
$client->shortUrl->stats($link['data']['uuid'], ['from' => '2026-08-01', 'to' => '2026-08-11']);
$client->shortUrl->list(['count' => 20]);
$client->shortUrl->show($link['data']['uuid']);
// 중지 — 리다이렉트만 멈추고(410 Gone) 통계는 남습니다.
$client->shortUrl->deactivate($link['data']['uuid']);
```
## 오류 처리
```php
use Sendgo\Php\Exception\SendgoException;
try {
$client->alimtalk->send([...]);
} catch (SendgoException $e) {
// 발송 실패는 구매자에게 드러내지 않고 로그로 남긴다.
if (function_exists('wc_get_logger')) {
wc_get_logger()->error(
sprintf('Sendgo %d [%s]: %s', $e->getStatusCode(), $e->getErrorCode(), $e->getMessage()),
['source' => 'sendgo']
);
}
}
```
메시지 문자열이 아니라 `getErrorCode()` 로 분기하세요. 메시지는 바뀔 수 있고 코드가 계약입니다.
`TOKEN_EXPIRED` 와 `TOKEN_MISMATCH` 는 코어 SDK 안에서 처리됩니다 — 토큰을 재발급하고 요청을 한 번 재시도합니다.
## FAQ
**Q. WooCommerce 없이도 사용할 수 있나요?**
네. 코어 클라이언트(`Sendgo_Plugin::instance()->client()`)를 통해 알림톡/SMS를 직접 발송할 수 있습니다. 주문 자동 알림만 WooCommerce에 의존합니다.
**Q. 클라이언트가 `null`을 반환합니다.**
Access Key 또는 Secret Key 가 설정되지 않았거나, 소스에서 직접 받아 `composer install` 을
실행하지 않아 `vendor/autoload.php` 가 없는 경우입니다. 후자는 관리자 화면에 오류 알림으로 표시됩니다.
배포 zip 에는 `vendor/` 가 포함되어 있으므로 이 경우가 생기지 않습니다.
**Q. 인증 키는 어디에 저장되나요?**
`sendgo_options` 옵션에 서버 사이드로만 저장됩니다. 프런트엔드에 노출되지 않으며, 시크릿 키는
관리자 화면에서 password 필드로 렌더링됩니다.
**Q. 발송이 안 되는데 오류도 없습니다.**
콘솔의 **연동하기 > 연동 정보**에서 호출을 허용할 IP 를 등록했는지 확인하세요. 등록되지 않은
주소에서 온 요청은 거부됩니다. WordPress 사이트가 나가는 IP 를 등록해야 하며, 공유 호스팅이나
CDN 뒤에 있으면 브라우저에 보이는 IP 와 다를 수 있습니다.
**Q. 삭제하면 정리가 되나요?**
`uninstall.php` 가 `sendgo_options` 옵션을 지웁니다. `_sendgo_notified_*` 주문 메타는 남깁니다 —
무해하고, 지우려면 전체 주문에 대량 쓰기를 해야 합니다.
## 변경 사항
### 1.2.4 (2026-08-20)
- readme 와 플러그인 헤더, 번역 가능한 모든 문자열을 영어로 교체했습니다. msgid 가 한국어라
translate.wordpress.org 에서 번역이 불가능했습니다.
- 연동하는 외부 서비스와 전송되는 데이터를 readme 에 명시했습니다(약관·개인정보처리방침 링크 포함).
- **버그 수정: `url` 옵션 유실** — 설정 화면에 필드가 없는데 정제 목록에 들어 있어서,
설정을 한 번 저장하면 값이 항상 사라졌습니다. API 베이스 URL 을 재정의해 둔 사이트는
관리자가 저장 버튼을 누르는 순간 기본값으로 되돌아갔습니다.
### 1.2.3 (2026-08-16)
- 플러그인 헤더의 Plugin URI 와 Author URI 가 같아 wordpress.org 업로드가 거부되던 문제 수정.
### 1.2.2 (2026-08-16)
- wordpress.org 배포 준비 — 코어 SDK(`sendgo/php`)를 `vendor/` 에 번들해 Composer 없이 동작합니다.
- 플러그인 헤더(1.2.1)와 `SENDGO_VERSION` 상수(1.1.0)의 버전 불일치 수정.
- **코어 SDK 를 찾지 못하면 조용히 아무것도 하지 않던 문제 수정** — 설치·활성화·설정까지 정상으로
보이는데 알림만 안 나가는 상태였습니다. 이제 관리자 알림으로 원인을 표시합니다.
### 1.2.1 (2026-08-14)
- 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
npm/PyPI/Packagist/Maven/NuGet/RubyGems 검색 결과에 그대로 찍히는 문자열이라
종료된 채널을 계속 홍보하고 있었습니다.
- 검색 키워드에 `brand-message` 를 추가했습니다 (`friendtalk` 은 유입 검색어라 유지).
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
### 1.1.0 (2026-08-11)
- **버그 수정: WooCommerce 중복 알림 발송** — 주문 상태가 같은 값으로 다시 저장되면
알림이 다시 나갔습니다. `_sendgo_notified_{status}` 주문 메타로 상태별 1회만
발송하도록 막았습니다. 발송이 실패하면 메타를 남기지 않아 다음 상태 변경에서 재시도됩니다.
- 주문 상태별로 템플릿을 따로 지정할 수 있게 옵션을 분리했습니다.
- 브랜드메시지(친구톡 후속 채널) 사용법 추가
- 짧은 URL 사용법 추가
## 연동하는 외부 서비스
이 플러그인은 카카오 알림톡·브랜드메시지와 SMS/LMS/MMS 를 발송하기 위해 샌드고 API
(https://sendgo.io)에 접속합니다. 발송에는 샌드고 계정이 필요하며, 발송은 유료입니다.
- 발송 직전 액세스 키와 시크릿 키로 인증 요청을 보내 단기 토큰을 받습니다.
- WooCommerce 주문이 `처리 중`·`주문 완료` 로 바뀌고 해당 상태에 템플릿 코드나 SMS 대체
내용이 설정돼 있으면, 구매자 청구 연락처와 주문 번호를 발신 키·템플릿 코드와 함께 보냅니다.
- 코드에서 직접 클라이언트를 호출하면 넘긴 수신 번호와 본문이 그대로 전송됩니다.
설치·활성화만 한 상태나 템플릿 코드·SMS 대체 내용이 모두 비어 있는 상태에서는 아무 데이터도 전송되지 않습니다.
- 이용약관: https://sendgo.io/terms-of-service
- 개인정보처리방침: https://sendgo.io/privacy-policy
## 라이선스
MIT © amuz — https://sendgo.io
---
## 패키지 정보
- **패키지**: `sendgo/wordpress` (Packagist)
- **저장소**: [send-go/wordpress](https://github.com/send-go/wordpress)
- **레지스트리**: https://packagist.org/packages/sendgo/wordpress
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **Node.js / TypeScript에서 카카오 알림톡, 브랜드메시지, SMS를 가장 쉽게 발송하는 SDK**
`@sendgo/node`는 [Sendgo](https://sendgo.io) 알림 API를 위한 공식 Node.js / TypeScript SDK입니다.
**외부 런타임 의존성 없이** Node.js 내장 `fetch`만을 사용하며, 완전한 TypeScript 타입 정의를 제공합니다.
Next.js, Express, Fastify, NestJS 등 모든 Node.js 프레임워크에서 사용할 수 있습니다.
---
## Sendgo란?
[Sendgo](https://sendgo.io)는 대한민국 기업과 개발자를 위한 **통합 알림 발송 플랫폼**입니다.
- **카카오 알림톡**: 카카오톡 채널을 통한 정보성 메시지 (주문 확인, 배송 안내, 인증번호, 예약 확인 등)
- **카카오 친구톡**: 카카오톡 채널 친구에게 마케팅/정보성 메시지 (이벤트, 쿠폰, 프로모션)
- **SMS / LMS / MMS**: 전통적인 문자 메시지
- **자동 대체 발송**: 알림톡 전송 실패 시 SMS로 자동 전환
---
## 주요 기능
| 기능 | 설명 |
|------|------|
| **Zero 런타임 의존성** | Node.js 18+ 내장 `fetch` 사용, 외부 패키지 불필요 |
| **완전한 TypeScript 지원** | 모든 요청/응답에 타입 정의 제공 |
| **토큰 자동 관리** | 발급·갱신·캐시(50분) 자동 처리 |
| **동시 요청 중복 방지** | 여러 요청이 동시에 들어와도 토큰 발급은 1회만 수행 |
| **401/403 자동 재시도** | 토큰 만료 시 자동 갱신 후 재발송 |
| **다건 동시 발송** | 수신자 배열로 대량 발송 |
| **예약 발송** | 원하는 시각에 예약 발송 |
| **SMS 자동 대체 발송** | 알림톡 실패 시 SMS로 자동 전환 |
| **v1 / v2 API 지원** | 설정 한 줄로 API 버전 전환 |
---
## 지원 메시지 유형
### 카카오 알림톡 (Alimtalk)
- 사전 승인된 템플릿 기반 발송
- 템플릿 변수 `#{var1}` ~ `#{var8}` 지원
- 즉시/예약 발송, SMS 대체 발송
### 카카오 친구톡 (Friendtalk)
> ⚠️ **Deprecated — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었습니다.**
> 2026-01-01 부터 친구톡 발송 요청은 카카오 측에서 **브랜드메시지(자유형)** 로 자동 대체 발송됩니다.
> 호출은 계속 성공하며, 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보내는 경로는
> 현재 이것뿐이므로 기존 코드를 당장 바꿀 필요는 없습니다.
>
> 다음의 경우에는 **브랜드메시지**를 사용하세요.
> - 템플릿 기반 리치 타입 (`FL`/`FC`/`FM`/`FP`/`FA`)
> - 채널 친구가 **아닌** 수신자 (`targeting` = `N` / `I`)
> - 수신 동의한 전체 채널 친구 동보 (`targeting` = `F`)
>
> 메시지 타입은 1:1 대응되며 변환은 서버가 처리합니다 — `FT`→`BT`, `FI`→`BI`, `FW`→`BW`,
> `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`.
- 자유 형식 메시지 발송
- 텍스트(FT), 이미지(FI), 와이드이미지(FW), 리스트(FL), 복합(FM), 커머스(FC) 등 8종
- 버튼, 이미지, 링크 첨부
### SMS / LMS / MMS
- SMS: 단문 (90바이트), LMS: 장문 (2,000바이트), MMS: 이미지 첨부
- 일반/광고/선거 캠페인 유형
---
## 설치
```bash
# npm
npm install @sendgo/node
# pnpm
pnpm add @sendgo/node
# yarn
yarn add @sendgo/node
# bun
bun add @sendgo/node
```
**요구사항:** Node.js 18 이상 (내장 `fetch` 필요)
---
## 빠른 시작
### 1단계 — 환경변수 설정
```bash
# .env
SENDGO_ACCESS_KEY=your_access_key
SENDGO_SECRET_KEY=your_secret_key
SENDGO_KAKAO_SENDER_KEY=your_kakao_sender_key
SENDGO_SMS_SENDER_KEY=your_sms_sender_key
SENDGO_API_VERSION=v2
```
> **카카오 발신프로필 키 발급**: [Sendgo 콘솔](https://sendgo.io) → 카카오 발신프로필 → 등록
### 2단계 — 클라이언트 초기화
```typescript
import Sendgo from '@sendgo/node';
const sendgo = new Sendgo({
accessKey: process.env.SENDGO_ACCESS_KEY!,
secretKey: process.env.SENDGO_SECRET_KEY!,
kakaoSenderKey: process.env.SENDGO_KAKAO_SENDER_KEY,
smsSenderKey: process.env.SENDGO_SMS_SENDER_KEY,
apiVersion: 'v2', // 'v1' | 'v2'
});
```
### 3단계 — 알림톡 전송
```typescript
await sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001', // Sendgo 승인 템플릿 코드
contacts: [
{
contact: '01012345678', // 수신자 전화번호 (필수)
name: '홍길동', // 수신자 이름 (선택)
var1: 'ORD-20260723-001', // 템플릿 변수 #{var1}
var2: '스프링 부트 가이드', // 템플릿 변수 #{var2}
var3: '29,000원', // 템플릿 변수 #{var3}
},
],
});
```
---
## 상세 사용법
### 카카오 알림톡
#### 단건 발송
```typescript
await sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [{
contact: '01012345678',
name: '홍길동',
var1: 'ORD-001', // 주문번호
var2: '맥북 프로', // 상품명
var3: '3,490,000원', // 결제금액
var4: '2026-07-25', // 배송 예정일
}],
});
```
#### 다건 발송 (대량 발송)
```typescript
await sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [
{ contact: '01011111111', var1: 'ORD-001' },
{ contact: '01022222222', var1: 'ORD-002' },
{ contact: '01033333333', var1: 'ORD-003' },
],
});
```
#### 예약 발송
```typescript
await sendgo.alimtalk.send({
templateCode: 'PROMO_SUMMER_2026',
scheduleType: 'SCHEDULED',
at: '2026-07-28 09:00:00', // 발송 예약 시각 (Y-m-d H:i:s)
contacts: [{ contact: '01012345678', var1: '여름 한정 50% 할인' }],
});
```
#### 알림톡 실패 시 SMS 자동 대체 발송
```typescript
await sendgo.alimtalk.send({
templateCode: 'DELIVERY_START_001',
replaceSms: 'Y',
smsSubject: '[배송 시작 안내]',
smsContent: '주문하신 상품이 출고되었습니다.\n송장번호: #{var2}',
contacts: [{
contact: '01012345678',
var1: 'ORD-001',
var2: '1234567890', // 송장번호
}],
});
```
---
### 카카오 친구톡
> ⚠️ **Deprecated — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었습니다.**
> 2026-01-01 부터 친구톡 발송 요청은 카카오 측에서 **브랜드메시지(자유형)** 로 자동 대체 발송됩니다.
> 호출은 계속 성공하며, 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보내는 경로는
> 현재 이것뿐이므로 기존 코드를 당장 바꿀 필요는 없습니다.
>
> 다음의 경우에는 **브랜드메시지**를 사용하세요.
> - 템플릿 기반 리치 타입 (`FL`/`FC`/`FM`/`FP`/`FA`)
> - 채널 친구가 **아닌** 수신자 (`targeting` = `N` / `I`)
> - 수신 동의한 전체 채널 친구 동보 (`targeting` = `F`)
>
> 메시지 타입은 1:1 대응되며 변환은 서버가 처리합니다 — `FT`→`BT`, `FI`→`BI`, `FW`→`BW`,
> `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`.
```typescript
// 기본 텍스트
await sendgo.friendtalk.send({
content: '안녕하세요! 7월 한정 특가 이벤트를 확인해보세요. 최대 50% 할인!',
contacts: [{ contact: '01012345678' }],
});
// 이미지 + 버튼
await sendgo.friendtalk.send({
messageType: 'FI',
content: '이번 주 특가 상품을 확인하세요!',
imageUrl: 'https://cdn.example.com/banner.jpg',
imageLink: 'https://example.com/event',
buttons: [{
name: '이벤트 보기',
type: 'WL',
linkMo: 'https://example.com/event',
linkPc: 'https://example.com/event',
}],
contacts: [{ contact: '01012345678' }],
});
```
---
### SMS / LMS / MMS
```typescript
// SMS — 단문 (90자 이하)
await sendgo.sms.sendSms({
content: '[Sendgo] 인증번호: 123456 (5분 이내 입력)',
contacts: [{ contact: '01012345678' }],
});
// LMS — 장문 (제목 포함)
await sendgo.sms.sendLms({
subject: '[중요] 서비스 점검 안내',
content: `안녕하세요.
서비스 점검이 예정되어 있습니다.
■ 점검 일시: 2026-07-25 02:00 ~ 06:00
■ 영향 범위: 전체 서비스
이용에 불편을 드려 죄송합니다.`,
contacts: [{ contact: '01012345678' }],
});
// MMS — 멀티미디어
await sendgo.sms.sendMms({
subject: '[이벤트] 7월 특가',
content: '이번 달 특가 상품을 확인하세요!',
contacts: [{ contact: '01012345678' }],
});
```
---
## 브랜드메시지 사용법
브랜드메시지는 친구톡의 후속 채널입니다. 메시지 타입이 친구톡과 1:1 대응되며
(`FT`→`BT`, `FI`→`BI`, `FW`→`BW`, `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`),
요청에는 **친구톡 코드를 그대로** 넘기고 변환은 서버가 처리합니다.
친구톡과 달리 다음이 가능합니다.
- 채널 친구가 **아닌** 수신자에게 발송 (`targeting: N`)
- 수신 동의한 **전체 채널 친구 동보** 발송 (`targeting: F`, 수신자 목록 불필요)
- 리스트·캐러셀·커머스·동영상 등 **템플릿 기반 리치 메시지**
> v2 전용입니다. 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보낼 때는 여전히 친구톡 API 를 쓰세요 — 이 엔드포인트는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다. 친구톡 요청은 카카오 측에서 브랜드메시지(자유형)로 대체 발송됩니다.
```typescript
// 단건 발송 — 채널 친구 대상
await sendgo.brandMessage.send({
targeting: 'M',
messageType: 'FL',
friendTemplateUuid: '9cd5460b-6458-4edc-9b11-c26d3013c340',
contacts: [{ contact: '01012345678', var1: '29,000원' }],
});
// 동보 발송 — 수신 동의한 전체 채널 친구 (contacts 불필요)
await sendgo.brandMessage.broadcast({
messageType: 'FW',
friendTemplateUuid: '9cd5460b-6458-4edc-9b11-c26d3013c340',
});
// 캠페인 조회
const list = await sendgo.brandMessage.campaigns({ count: 10 });
const one = await sendgo.brandMessage.campaign('1f0a6d0e-6b3b-4f0f-9b2f-2f6f6a1b7c11');
```
---
## 프레임워크 통합
### Next.js App Router
```typescript
// app/api/notify/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Sendgo from '@sendgo/node';
// 싱글톤 패턴으로 API Route 간 재사용
const sendgo = new Sendgo({
accessKey: process.env.SENDGO_ACCESS_KEY!,
secretKey: process.env.SENDGO_SECRET_KEY!,
kakaoSenderKey: process.env.SENDGO_KAKAO_SENDER_KEY,
apiVersion: 'v2',
});
export async function POST(request: NextRequest) {
const { phone, orderNumber } = await request.json();
await sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [{ contact: phone, var1: orderNumber }],
});
return NextResponse.json({ success: true });
}
```
```typescript
// app/actions/notification.ts — Server Actions
'use server';
import Sendgo from '@sendgo/node';
const sendgo = new Sendgo({ /* ... */ });
export async function sendOrderConfirm(phone: string, orderNumber: string) {
return sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [{ contact: phone, var1: orderNumber }],
});
}
```
### Express.js
```typescript
import express from 'express';
import Sendgo from '@sendgo/node';
const app = express();
const sendgo = new Sendgo({
accessKey: process.env.SENDGO_ACCESS_KEY!,
secretKey: process.env.SENDGO_SECRET_KEY!,
kakaoSenderKey: process.env.SENDGO_KAKAO_SENDER_KEY,
});
app.use(express.json());
app.post('/api/notify', async (req, res) => {
const { phone, orderNumber } = req.body;
await sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [{ contact: phone, var1: orderNumber }],
});
res.json({ success: true });
});
```
### NestJS
```typescript
// sendgo.module.ts
import { Module, Global } from '@nestjs/common';
import Sendgo from '@sendgo/node';
@Global()
@Module({
providers: [{
provide: 'SENDGO_CLIENT',
useFactory: () => new Sendgo({
accessKey: process.env.SENDGO_ACCESS_KEY!,
secretKey: process.env.SENDGO_SECRET_KEY!,
kakaoSenderKey: process.env.SENDGO_KAKAO_SENDER_KEY,
apiVersion: 'v2',
}),
}],
exports: ['SENDGO_CLIENT'],
})
export class SendgoModule {}
// notification.service.ts
@Injectable()
export class NotificationService {
constructor(@Inject('SENDGO_CLIENT') private readonly sendgo: Sendgo) {}
async sendOrderConfirm(phone: string, orderNumber: string) {
return this.sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [{ contact: phone, var1: orderNumber }],
});
}
}
```
### Fastify
```typescript
import Fastify from 'fastify';
import Sendgo from '@sendgo/node';
const app = Fastify();
const sendgo = new Sendgo({ accessKey: '...', secretKey: '...' });
app.post('/notify', async (request, reply) => {
const { phone, orderNumber } = request.body as any;
await sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [{ contact: phone, var1: orderNumber }],
});
return { success: true };
});
```
---
## TypeScript 타입
```typescript
import type {
SendgoConfig, // 클라이언트 설정
Contact, // 수신자 정보
AlimtalkParams, // 알림톡 발송 파라미터
FriendtalkParams, // 친구톡 발송 파라미터
SmsParams, // SMS 발송 파라미터
SendgoResponse, // API 응답
ScheduleType, // 'DIRECTLY' | 'SCHEDULED'
SmsMessageType, // 'SMS' | 'LMS' | 'MMS'
FriendtalkMessageType, // 'FT' | 'FI' | 'FW' | ...
} from '@sendgo/node';
```
---
## 예외 처리
```typescript
import { SendgoError } from '@sendgo/node';
try {
await sendgo.alimtalk.send({ ... });
} catch (error) {
if (error instanceof SendgoError) {
console.error({
statusCode: error.statusCode, // HTTP 상태 코드
errorCode: error.errorCode, // Sendgo 에러 코드
message: error.message, // 에러 메시지
endpoint: error.endpoint, // 호출된 엔드포인트
});
switch (error.errorCode) {
case 'INVALID_TEMPLATE_CODE': /* 템플릿 코드 확인 */ break;
case 'PAYMENT_REQUIRED': /* 크레딧 충전 알림 */ break;
case 'EMPTY_CONTACTS': /* 수신자 확인 */ break;
}
}
}
```
---
## 설정 옵션
| 옵션 | 타입 | 필수 | 기본값 | 설명 |
|------|------|------|--------|------|
| `accessKey` | `string` | **필수** | — | Sendgo 액세스 키 |
| `secretKey` | `string` | **필수** | — | Sendgo 시크릿 키 |
| `kakaoSenderKey` | `string` | 선택 | — | 카카오 발신프로필 키 |
| `smsSenderKey` | `string` | 선택 | — | SMS 발신자 키 |
| `apiVersion` | `'v1' \| 'v2'` | 선택 | `'v1'` | API 버전 |
| `baseUrl` | `string` | 선택 | `'https://sendgo.io'` | API 기본 URL |
---
## 자주 묻는 질문 (FAQ)
**Q. CommonJS(`require`)를 지원하나요?**
A. 네. `dist/index.js`는 CommonJS 형식으로 빌드되어 `require('@sendgo/node')`로 사용 가능합니다.
**Q. Node.js 16에서 사용할 수 있나요?**
A. 내장 `fetch`가 Node.js 18에서 안정화되었으므로, 18 이상을 권장합니다. Node.js 16에서는 `node-fetch`를 글로벌로 폴리필해야 합니다.
**Q. 토큰은 어떻게 관리되나요?**
A. SDK 내부에서 인메모리로 캐싱(50분)합니다. 서버리스(Lambda, Vercel Functions) 환경에서는 콜드 스타트 시 매번 새 토큰이 발급됩니다.
**Q. 발송 실패 시 자동 재시도가 되나요?**
A. 401/403 응답 시 토큰을 갱신하고 1회 재시도합니다. 그 외 실패는 `SendgoError`로 예외가 발생합니다.
**Q. 알림톡 템플릿은 어디서 만드나요?**
A. [Sendgo 콘솔](https://sendgo.io) → 알림톡 템플릿 → 템플릿 작성 → 카카오 심사 신청
---
## 짧은 URL
짧은 URL 은 메시지 본문의 링크를 줄이고, 그 링크가 실제로 눌렸는지 집계합니다.
문자는 바이트 수가 요금과 직결되므로 링크를 줄이면 그만큼 본문을 더 쓸 수 있습니다.
같은 원본 URL 을 다시 줄이면 **기존 링크가 그대로 반환**됩니다. 캠페인별로 반응을
따로 집계하려면 `forceNew` 로 새 코드를 만드세요.
`deactivate` 는 링크를 삭제하지 않고 리다이렉트만 중지합니다. 이미 발송한 메시지의
링크를 무효화할 때 쓰며, 누적 통계는 남고 이후 접속은 `410 Gone` 이 됩니다.
```typescript
// 짧은 URL 생성 (v2 전용)
const created = await sendgo.shortUrl.create({
targetUrl: 'https://example.com/promotions/summer-sale',
title: '여름 세일 랜딩',
});
const { code, shortUrl } = created.data;
// 반응 통계 — 일별 추이 + 디바이스/유입경로/국가별 분해
const stats = await sendgo.shortUrl.stats(code, { from: '2026-08-01' });
await sendgo.shortUrl.list({ count: 10 });
await sendgo.shortUrl.show(code);
await sendgo.shortUrl.deactivate(code); // 리다이렉트만 중지, 통계는 남는다
```
`stats` 는 일별 추이(`daily`)와 디바이스(`byDevice`)·유입경로(`byReferer`)·국가(`byCountry`)별
분해를 반환합니다. 일별 추이는 사전 집계 표에서 읽으므로 클릭이 많아도 응답 시간이 일정합니다.
## 변경 사항
### 1.2.1 (2026-08-14)
- 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
npm/PyPI/Packagist/Maven/NuGet/RubyGems 검색 결과에 그대로 찍히는 문자열이라
종료된 채널을 계속 홍보하고 있었습니다.
- 검색 키워드에 `brand-message` 를 추가했습니다 (`friendtalk` 은 유입 검색어라 유지).
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
### 1.1.0 (2026-08-11)
- 짧은 URL 추가 — `sendgo.shortUrl`
- `HttpClient.delete()` 추가. `request()` 의 메서드 타입이 `'GET'|'POST'` 로 묶여 DELETE 를 표현할 수 없었다.
- `ShortUrlParams` / `ShortUrlListParams` / `ShortUrlStatsParams` 타입 추가
## 라이선스
MIT License © 2026 [Sendgo](https://sendgo.io)
---
## 패키지 정보
- **패키지**: `@sendgo/node` (npm)
- **저장소**: [send-go/node](https://github.com/send-go/node)
- **레지스트리**: https://www.npmjs.com/package/@sendgo/node
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **React / Next.js에서 카카오 알림톡, 브랜드메시지, SMS를 발송하는 공식 React SDK**
> **중요**: 이 패키지는 **서버사이드 전용**입니다.
> Next.js Server Actions, Route Handlers, API Routes에서만 사용하세요.
> 클라이언트 컴포넌트에서 직접 사용하면 API 키가 브라우저에 노출됩니다.
---
## 설치
```bash
npm install @sendgo/react @sendgo/node
# 또는
pnpm add @sendgo/react @sendgo/node
```
---
## 빠른 시작
### Next.js Server Action
```typescript
// app/actions/notify.ts
'use server'
import { createSendgoClient } from '@sendgo/react';
const sendgo = createSendgoClient({
accessKey: process.env.SENDGO_ACCESS_KEY!,
secretKey: process.env.SENDGO_SECRET_KEY!,
kakaoSenderKey: process.env.SENDGO_KAKAO_SENDER_KEY,
apiVersion: 'v2',
});
export async function sendOrderConfirmAction(phone: string, orderNo: string) {
return sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [{ contact: phone, var1: orderNo }],
});
}
```
### 클라이언트 컴포넌트에서 훅 사용
```tsx
// app/components/OrderButton.tsx
'use client'
import { useAlimtalk } from '@sendgo/react';
import { sendOrderConfirmAction } from '../actions/notify';
export function OrderButton({ phone, orderNo }: { phone: string; orderNo: string }) {
const { send, loading, error } = useAlimtalk(sendOrderConfirmAction);
return (
{error &&
발송 실패: {error.message}
}
);
}
```
---
## 알림톡 상세 사용법
```typescript
// app/actions/alimtalk.ts
'use server'
import { createSendgoClient } from '@sendgo/react';
const sendgo = createSendgoClient({
accessKey: process.env.SENDGO_ACCESS_KEY!,
secretKey: process.env.SENDGO_SECRET_KEY!,
kakaoSenderKey: process.env.SENDGO_KAKAO_SENDER_KEY,
smsSenderKey: process.env.SENDGO_SMS_SENDER_KEY,
apiVersion: 'v2',
});
// 다건 발송
export async function sendBulkAlimtalk() {
return sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [
{ contact: '01011111111', name: '홍길동', var1: 'ORD-001', var2: '29,000원' },
{ contact: '01022222222', name: '김철수', var1: 'ORD-002', var2: '15,000원' },
{ contact: '01033333333', name: '이영희', var1: 'ORD-003', var2: '52,000원' },
],
});
}
// 예약 발송
export async function sendScheduledAlimtalk(phone: string) {
return sendgo.alimtalk.send({
templateCode: 'PROMO_SUMMER_2026',
scheduleType: 'SCHEDULED',
at: '2026-07-28 09:00:00',
contacts: [{ contact: phone, var1: '여름 한정 50% 할인' }],
});
}
// SMS 대체 발송
export async function sendWithFallback(phone: string, trackingNo: string) {
return sendgo.alimtalk.send({
templateCode: 'DELIVERY_START_001',
replaceSms: 'Y',
smsSubject: '[배송 시작 안내]',
smsContent: `주문하신 상품이 출고되었습니다.\n송장번호: ${trackingNo}`,
contacts: [{ contact: phone, var1: 'ORD-001', var2: trackingNo }],
});
}
```
---
## SMS / LMS / MMS 사용법
```typescript
// app/actions/sms.ts
'use server'
import { createSendgoClient } from '@sendgo/react';
const sendgo = createSendgoClient({ accessKey: '...', secretKey: '...' });
// SMS
export async function sendSms(phone: string, code: string) {
return sendgo.sms.sendSms({
content: `[Sendgo] 인증번호: ${code} (5분 이내 입력)`,
contacts: [{ contact: phone }],
});
}
// LMS
export async function sendLms(phone: string) {
return sendgo.sms.sendLms({
subject: '[중요] 서비스 점검 안내',
content: '안녕하세요. 서비스 점검이 예정되어 있습니다.\n■ 일시: 2026-07-25 02:00 ~ 06:00',
contacts: [{ contact: phone }],
});
}
```
---
## Route Handler (App Router)
```typescript
// app/api/notify/order/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createSendgoClient } from '@sendgo/react';
const sendgo = createSendgoClient({
accessKey: process.env.SENDGO_ACCESS_KEY!,
secretKey: process.env.SENDGO_SECRET_KEY!,
kakaoSenderKey: process.env.SENDGO_KAKAO_SENDER_KEY,
apiVersion: 'v2',
});
export async function POST(request: NextRequest) {
const { phone, orderNo, amount } = await request.json();
await sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [{ contact: phone, var1: orderNo, var2: amount }],
});
return NextResponse.json({ success: true });
}
```
---
## useSms 훅
```tsx
'use client'
import { useSms } from '@sendgo/react';
import { sendSmsAction } from '../actions/sms';
export function VerificationForm() {
const [phone, setPhone] = useState('');
const { send, loading, error, data } = useSms(sendSmsAction);
return (
);
}
```
---
## 브랜드메시지 · 짧은 URL
이 패키지는 코어(`@sendgo/node`)의 클라이언트를 그대로 노출하므로, 코어에 있는 채널이
모두 그대로 쓸 수 있습니다. 두 기능 모두 **v2 전용**입니다.
| 기능 | 접근 |
|------|------|
| 카카오 브랜드메시지 (친구톡의 후속 채널) | `sendBrandMessage()` |
| 짧은 URL (단축 + 클릭 반응 분석) | `createShortUrl() / shortUrlStats()` |
브랜드메시지는 채널 친구가 아닌 수신자에게도 보낼 수 있고(`targeting` = `N`),
수신 동의한 전체 채널 친구에게 동보 발송할 수도 있습니다(`targeting` = `F`).
짧은 URL 은 메시지 본문의 링크를 줄이고 클릭 반응(일별 추이·디바이스·유입경로·국가)을
집계합니다.
사용 예시와 파라미터는 [코어 README](https://github.com/send-go) 와
[SDK 가이드](https://sendgo.io/ko/sdk) 를 참고하세요.
## 변경 사항
### 1.2.1 (2026-08-14)
- 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
npm/PyPI/Packagist/Maven/NuGet/RubyGems 검색 결과에 그대로 찍히는 문자열이라
종료된 채널을 계속 홍보하고 있었습니다.
- 검색 키워드에 `brand-message` 를 추가했습니다 (`friendtalk` 은 유입 검색어라 유지).
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
### 1.1.0 (2026-08-11)
- 짧은 URL 서버 액션 추가 — `createShortUrl` / `listShortUrls` / `getShortUrl` / `shortUrlStats` / `deactivateShortUrl`
## 라이선스
MIT License © 2026 [Sendgo](https://sendgo.io)
---
## 패키지 정보
- **패키지**: `@sendgo/react` (npm)
- **저장소**: [send-go/react](https://github.com/send-go/react)
- **레지스트리**: https://www.npmjs.com/package/@sendgo/react
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **Vue.js / Nuxt 3에서 카카오 알림톡, 브랜드메시지, SMS를 발송하는 공식 Vue SDK**
> **중요**: 이 패키지는 **서버사이드 전용**입니다.
> Nuxt 3 Server Routes, API Routes에서만 사용하세요.
> 클라이언트 컴포넌트에서 직접 사용하면 API 키가 브라우저에 노출됩니다.
---
## 설치
```bash
npm install @sendgo/vue @sendgo/node
# 또는
pnpm add @sendgo/vue @sendgo/node
```
---
## 빠른 시작
### Nuxt 3 Server Route에서 알림톡 전송
```typescript
// server/api/notify/order.post.ts
import Sendgo from '@sendgo/node';
const sendgo = new Sendgo({
accessKey: process.env.SENDGO_ACCESS_KEY!,
secretKey: process.env.SENDGO_SECRET_KEY!,
kakaoSenderKey: process.env.SENDGO_KAKAO_SENDER_KEY,
smsSenderKey: process.env.SENDGO_SMS_SENDER_KEY,
apiVersion: 'v2',
});
export default defineEventHandler(async (event) => {
const { phone, orderNo, amount } = await readBody(event);
await sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [{ contact: phone, var1: orderNo, var2: amount }],
});
return { success: true };
});
```
### Vue 3 컴포저블로 호출
```vue
발송 실패: {{ error.message }}
```
---
## 알림톡 상세 사용법
```typescript
// server/api/alimtalk.post.ts
import Sendgo from '@sendgo/node';
const sendgo = new Sendgo({
accessKey: process.env.SENDGO_ACCESS_KEY!,
secretKey: process.env.SENDGO_SECRET_KEY!,
kakaoSenderKey: process.env.SENDGO_KAKAO_SENDER_KEY,
smsSenderKey: process.env.SENDGO_SMS_SENDER_KEY,
apiVersion: 'v2',
});
export default defineEventHandler(async (event) => {
const body = await readBody(event);
switch (body.action) {
case 'bulk':
// 다건 발송
await sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [
{ contact: '01011111111', name: '홍길동', var1: 'ORD-001', var2: '29,000원' },
{ contact: '01022222222', name: '김철수', var1: 'ORD-002', var2: '15,000원' },
{ contact: '01033333333', name: '이영희', var1: 'ORD-003', var2: '52,000원' },
],
});
break;
case 'scheduled':
// 예약 발송
await sendgo.alimtalk.send({
templateCode: 'PROMO_SUMMER_2026',
scheduleType: 'SCHEDULED',
at: '2026-07-28 09:00:00',
contacts: [{ contact: body.phone, var1: '여름 한정 50% 할인' }],
});
break;
case 'with-fallback':
// SMS 자동 대체 발송
await sendgo.alimtalk.send({
templateCode: 'DELIVERY_START_001',
replaceSms: 'Y',
smsSubject: '[배송 시작 안내]',
smsContent: `주문하신 상품이 출고되었습니다.\n송장번호: ${body.trackingNo}`,
contacts: [{ contact: body.phone, var1: 'ORD-001', var2: body.trackingNo }],
});
break;
}
return { success: true };
});
```
---
## SMS / LMS / MMS 사용법
```typescript
// server/api/sms.post.ts
import Sendgo from '@sendgo/node';
const sendgo = new Sendgo({ accessKey: '...', secretKey: '...' });
export default defineEventHandler(async (event) => {
const { type, phone, content, subject } = await readBody(event);
switch (type) {
case 'sms':
return sendgo.sms.sendSms({ content, contacts: [{ contact: phone }] });
case 'lms':
return sendgo.sms.sendLms({ subject, content, contacts: [{ contact: phone }] });
case 'mms':
return sendgo.sms.sendMms({ subject, content, contacts: [{ contact: phone }] });
}
});
```
---
## Nuxt 플러그인으로 전역 등록
```typescript
// plugins/sendgo.server.ts
import { SendgoPlugin } from '@sendgo/vue';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(SendgoPlugin, {
accessKey: process.env.SENDGO_ACCESS_KEY!,
secretKey: process.env.SENDGO_SECRET_KEY!,
kakaoSenderKey: process.env.SENDGO_KAKAO_SENDER_KEY,
apiVersion: 'v2',
});
});
```
---
## useBrandMessage 훅
브랜드메시지는 친구톡의 후속 채널입니다. 친구톡은 카카오 정책에 따라 **2025-12-31 종료**되었고,
2026-01-01 부터 친구톡 발송 요청은 카카오 측에서 브랜드메시지(자유형)로 자동 대체 발송됩니다.
**v2 전용**입니다.
```vue
```
---
## 브랜드메시지 · 짧은 URL
이 패키지는 코어(`@sendgo/node`)의 클라이언트를 그대로 노출하므로, 코어에 있는 채널이
모두 그대로 쓸 수 있습니다. 두 기능 모두 **v2 전용**입니다.
| 기능 | 접근 |
|------|------|
| 카카오 브랜드메시지 (친구톡의 후속 채널) | `sendgo.brandMessage` |
| 짧은 URL (단축 + 클릭 반응 분석) | `sendgo.shortUrl` |
브랜드메시지는 채널 친구가 아닌 수신자에게도 보낼 수 있고(`targeting` = `N`),
수신 동의한 전체 채널 친구에게 동보 발송할 수도 있습니다(`targeting` = `F`).
짧은 URL 은 메시지 본문의 링크를 줄이고 클릭 반응(일별 추이·디바이스·유입경로·국가)을
집계합니다.
사용 예시와 파라미터는 [코어 README](https://github.com/send-go) 와
[SDK 가이드](https://sendgo.io/ko/sdk) 를 참고하세요.
## 변경 사항
### 1.2.1 (2026-08-14)
- 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
npm/PyPI/Packagist/Maven/NuGet/RubyGems 검색 결과에 그대로 찍히는 문자열이라
종료된 채널을 계속 홍보하고 있었습니다.
- 검색 키워드에 `brand-message` 를 추가했습니다 (`friendtalk` 은 유입 검색어라 유지).
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
### 1.1.0 (2026-08-11)
- 짧은 URL 타입 재수출 (`ShortUrlParams` 등)
## 라이선스
MIT License © 2026 [Sendgo](https://sendgo.io)
---
## 패키지 정보
- **패키지**: `@sendgo/vue` (npm)
- **저장소**: [send-go/vue](https://github.com/send-go/vue)
- **레지스트리**: https://www.npmjs.com/package/@sendgo/vue
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **NestJS에서 카카오 알림톡, 브랜드메시지, SMS를 가장 쉽게 발송하는 공식 NestJS 모듈**
`@sendgo/nestjs`는 [`@sendgo/node`](https://github.com/send-go/node) 코어를 확장한 **NestJS 전용 모듈**입니다.
`DynamicModule` 기반의 `forRoot` / `forRootAsync` 등록, 전역(Global) 프로바이더, 생성자 주입(DI)을 완벽하게 제공합니다.
---
## 설치
```bash
npm install @sendgo/nestjs @sendgo/node
```
`@nestjs/common`은 peerDependency이므로 NestJS 프로젝트에 이미 설치되어 있어야 합니다.
---
## 빠른 시작
### 1단계 — 모듈 등록 (`app.module.ts`)
```ts
import { Module } from '@nestjs/common';
import { SendgoModule } from '@sendgo/nestjs';
@Module({
imports: [
SendgoModule.forRoot({
accessKey: process.env.SENDGO_ACCESS_KEY!,
secretKey: process.env.SENDGO_SECRET_KEY!,
kakaoSenderKey: process.env.SENDGO_KAKAO_KEY,
smsSenderKey: process.env.SENDGO_SMS_KEY,
apiVersion: 'v2',
}),
],
})
export class AppModule {}
```
`SendgoModule`은 `@Global()`이므로 한 번만 등록하면 어느 모듈에서든 `SendgoService`를 주입할 수 있습니다.
### 2단계 — 서비스 주입 후 발송
```ts
import { Injectable } from '@nestjs/common';
import { SendgoService } from '@sendgo/nestjs';
@Injectable()
export class OrderService {
constructor(private readonly sendgo: SendgoService) {}
async confirm(phone: string, orderNo: string, amount: number) {
await this.sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [
{ contact: phone, var1: orderNo, var2: `${amount.toLocaleString()}원` },
],
});
}
}
```
---
## 비동기 설정 (ConfigService)
환경변수를 `@nestjs/config`의 `ConfigService`로 주입해 설정을 구성할 수 있습니다.
```ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { SendgoModule } from '@sendgo/nestjs';
@Module({
imports: [
ConfigModule.forRoot(),
SendgoModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
accessKey: config.getOrThrow('SENDGO_ACCESS_KEY'),
secretKey: config.getOrThrow('SENDGO_SECRET_KEY'),
kakaoSenderKey: config.get('SENDGO_KAKAO_KEY'),
smsSenderKey: config.get('SENDGO_SMS_KEY'),
apiVersion: config.get('SENDGO_API_VERSION') ?? 'v1',
}),
}),
],
})
export class AppModule {}
```
---
## 상세 사용법
모든 발송 메서드는 `SendgoService`의 게터(`alimtalk`, `friendtalk`, `sms`)를 통해 코어 클라이언트에 위임됩니다.
### 알림톡
```ts
// 다건 발송
await this.sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [
{ contact: '01011111111', name: '홍길동', var1: 'ORD-001', var2: '29,000원' },
{ contact: '01022222222', name: '김철수', var1: 'ORD-002', var2: '15,000원' },
],
});
// 예약 발송
await this.sendgo.alimtalk.send({
templateCode: 'PROMO_SUMMER_2026',
scheduleType: 'SCHEDULED',
at: '2026-07-28 09:00:00',
contacts: [{ contact: '01012345678', var1: '여름 한정 50% 할인' }],
});
// SMS 자동 대체 발송
await this.sendgo.alimtalk.send({
templateCode: 'DELIVERY_START_001',
replaceSms: 'Y',
smsSubject: '[배송 시작 안내]',
smsContent: '주문하신 상품이 출고되었습니다.\n송장번호: #{var2}',
contacts: [{ contact: '01012345678', var1: 'ORD-001', var2: '1234567890' }],
});
```
### 친구톡
> ⚠️ **Deprecated — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었습니다.**
> 2026-01-01 부터 친구톡 발송 요청은 카카오 측에서 **브랜드메시지(자유형)** 로 자동 대체 발송됩니다.
> 호출은 계속 성공하며, 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보내는 경로는
> 현재 이것뿐이므로 기존 코드를 당장 바꿀 필요는 없습니다.
>
> 다음의 경우에는 **브랜드메시지**를 사용하세요.
> - 템플릿 기반 리치 타입 (`FL`/`FC`/`FM`/`FP`/`FA`)
> - 채널 친구가 **아닌** 수신자 (`targeting` = `N` / `I`)
> - 수신 동의한 전체 채널 친구 동보 (`targeting` = `F`)
>
> 메시지 타입은 1:1 대응되며 변환은 서버가 처리합니다 — `FT`→`BT`, `FI`→`BI`, `FW`→`BW`,
> `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`.
```ts
// 텍스트형
await this.sendgo.friendtalk.send({
content: '안녕하세요! 7월 한정 특가 이벤트를 확인해보세요.',
contacts: [{ contact: '01012345678' }],
});
// 이미지형
await this.sendgo.friendtalk.send({
messageType: 'FI',
content: '이번 주 특가 상품을 확인하세요!',
imageUrl: 'https://cdn.example.com/banner.jpg',
imageLink: 'https://example.com/event',
contacts: [{ contact: '01012345678' }],
});
// 버튼 포함
await this.sendgo.friendtalk.send({
content: '7월 쿠폰이 도착했습니다! 지금 바로 사용하세요.',
buttons: [
{ name: '쿠폰 받기', type: 'WL', linkMo: 'https://example.com/coupon' },
{ name: '고객센터', type: 'WL', linkMo: 'https://example.com/cs' },
],
contacts: [{ contact: '01012345678' }],
});
```
### SMS / LMS / MMS
```ts
// SMS (90자 이하)
await this.sendgo.sms.sendSms({
content: '[Sendgo] 인증번호: 123456 (5분 이내 입력)',
contacts: [{ contact: '01012345678' }],
});
// LMS (장문, 2,000자 이하)
await this.sendgo.sms.sendLms({
subject: '[중요] 서비스 점검 안내',
content: '안녕하세요. 서비스 점검이 예정되어 있습니다.\n\n■ 일시: 2026-07-25 02:00 ~ 06:00',
contacts: [{ contact: '01012345678' }],
});
// MMS (멀티미디어)
await this.sendgo.sms.sendMms({
subject: '[이벤트] 7월 특가',
content: '이번 달 특가 상품을 확인하세요!',
contacts: [{ contact: '01011111111' }, { contact: '01022222222' }],
});
```
---
## 서비스 클래스 패턴
```ts
// notification.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { SendgoService, SendgoError } from '@sendgo/nestjs';
@Injectable()
export class NotificationService {
private readonly logger = new Logger(NotificationService.name);
constructor(private readonly sendgo: SendgoService) {}
async sendVerificationCode(phone: string, code: string): Promise {
try {
// 알림톡 우선, 실패 시 SMS 대체
await this.sendgo.alimtalk.send({
templateCode: 'VERIFY_CODE_001',
replaceSms: 'Y',
smsContent: `[인증] 인증번호: ${code} (5분 이내 입력)`,
contacts: [{ contact: phone, var1: code }],
});
} catch (e) {
if (e instanceof SendgoError) {
this.logger.error(`Sendgo 인증번호 발송 실패: ${e.message}`);
}
throw e;
}
}
}
```
원본 코어 클라이언트가 필요하면 `this.sendgo.client`로 접근할 수 있습니다.
또한 `Sendgo` 클래스 토큰으로 코어 인스턴스를 직접 주입받는 것도 가능합니다.
```ts
import { Injectable } from '@nestjs/common';
import { Sendgo } from '@sendgo/node';
@Injectable()
export class OrderService {
constructor(private readonly sendgo: Sendgo) {}
}
```
---
## 예외 처리
```ts
import { SendgoError } from '@sendgo/nestjs';
try {
await this.sendgo.alimtalk.send({ /* ... */ });
} catch (e) {
if (e instanceof SendgoError) {
// e.errorCode, e.statusCode, e.endpoint 활용 가능
switch (e.errorCode) {
case 'INVALID_ACCESS_KEY':
case 'INVALID_SECRET_KEY':
// 인증키 오류 처리
break;
case 'INVALID_TEMPLATE_CODE':
// 존재하지 않는 템플릿
break;
case 'PAYMENT_REQUIRED':
// 크레딧 부족
break;
default:
break;
}
}
throw e;
}
```
---
## 설정 옵션
`SendgoModule.forRoot()` / `forRootAsync()`에 전달하는 `SendgoConfig` 값입니다.
| 키 | 타입 | 기본값 | 설명 |
|----|------|--------|------|
| `accessKey` | `string` | — | Sendgo 액세스 키 (필수) |
| `secretKey` | `string` | — | Sendgo 시크릿 키 (필수) |
| `kakaoSenderKey` | `string` | `''` | 카카오 발신프로필 키 |
| `smsSenderKey` | `string` | `''` | SMS 발신자 키 |
| `apiVersion` | `'v1' \| 'v2'` | `'v1'` | API 버전 |
| `baseUrl` | `string` | `'https://sendgo.io'` | API 기본 URL |
---
## 자주 묻는 질문 (FAQ)
**Q. `@sendgo/node`와의 차이는 무엇인가요?**
A. `@sendgo/node`는 프레임워크 독립적인 순수 Node.js 코어 SDK입니다. `@sendgo/nestjs`는 이를 확장해 `DynamicModule` 등록, 전역 프로바이더, 생성자 주입 등 NestJS 통합을 추가합니다.
**Q. NestJS 10, 11 모두 지원하나요?**
A. 네, `@nestjs/common` `>=10.0.0`을 peerDependency로 지원합니다.
**Q. `SendgoService` 대신 코어 클라이언트를 직접 주입할 수 있나요?**
A. 네, `Sendgo` 클래스를 토큰으로 주입받으면 코어 인스턴스를 직접 사용할 수 있습니다.
**Q. 테스트 시 Sendgo를 Mock 처리하려면?**
A. 테스트 모듈에서 `SendgoService` 또는 `Sendgo` 프로바이더를 mock 값으로 오버라이드하면 됩니다.
---
## 브랜드메시지 · 짧은 URL
이 패키지는 코어(`@sendgo/node`)의 클라이언트를 그대로 노출하므로, 코어에 있는 채널이
모두 그대로 쓸 수 있습니다. 두 기능 모두 **v2 전용**입니다.
| 기능 | 접근 |
|------|------|
| 카카오 브랜드메시지 (친구톡의 후속 채널) | `sendgo.brandMessage` |
| 짧은 URL (단축 + 클릭 반응 분석) | `sendgo.shortUrl` |
브랜드메시지는 채널 친구가 아닌 수신자에게도 보낼 수 있고(`targeting` = `N`),
수신 동의한 전체 채널 친구에게 동보 발송할 수도 있습니다(`targeting` = `F`).
짧은 URL 은 메시지 본문의 링크를 줄이고 클릭 반응(일별 추이·디바이스·유입경로·국가)을
집계합니다.
사용 예시와 파라미터는 [코어 README](https://github.com/send-go) 와
[SDK 가이드](https://sendgo.io/ko/sdk) 를 참고하세요.
## 변경 사항
### 1.2.1 (2026-08-14)
- 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
npm/PyPI/Packagist/Maven/NuGet/RubyGems 검색 결과에 그대로 찍히는 문자열이라
종료된 채널을 계속 홍보하고 있었습니다.
- 검색 키워드에 `brand-message` 를 추가했습니다 (`friendtalk` 은 유입 검색어라 유지).
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
### 1.1.0 (2026-08-11)
- `SendgoService.shortUrl` 게터 추가
## 라이선스
MIT License © 2026 [Sendgo](https://sendgo.io)
---
## 패키지 정보
- **패키지**: `@sendgo/nestjs` (npm)
- **저장소**: [send-go/nestjs](https://github.com/send-go/nestjs)
- **레지스트리**: https://www.npmjs.com/package/@sendgo/nestjs
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **Python / Django / FastAPI에서 카카오 알림톡, 브랜드메시지, SMS를 가장 쉽게 발송하는 SDK**
`sendgo-python`은 [Sendgo](https://sendgo.io) 알림 API를 위한 공식 Python SDK입니다.
**`requests` 하나만 의존하며**, 완전한 타입 힌트(Type Hints)를 제공합니다.
Django, FastAPI, Flask, Celery 등 모든 Python 환경에서 사용할 수 있습니다.
---
## Sendgo란?
[Sendgo](https://sendgo.io)는 대한민국 기업과 개발자를 위한 **통합 알림 발송 플랫폼**입니다.
- **카카오 알림톡**: 카카오톡 채널을 통한 정보성 메시지 (주문 확인, 배송 안내, 예약 확인 등)
- **카카오 친구톡**: 마케팅/이벤트 메시지 (쿠폰, 프로모션 등)
- **SMS / LMS / MMS**: 전통적인 문자 메시지
- **자동 대체 발송**: 알림톡 실패 시 SMS로 자동 전환
---
## 주요 기능
| 기능 | 설명 |
|------|------|
| **최소 의존성** | `requests` 하나만 필요 |
| **완전한 타입 힌트** | 모든 파라미터와 반환값에 타입 정의 |
| **스레드 안전 토큰 관리** | `threading.Lock` 기반, 멀티스레드 환경 안전 |
| **토큰 자동 캐싱(50분)** | 매 요청마다 토큰을 발급하지 않음 |
| **401/403 자동 재시도** | 토큰 만료 시 자동 갱신 후 재발송 |
| **다건 동시 발송** | 수신자 리스트로 대량 발송 |
| **예약 발송** | 원하는 시각에 발송 예약 |
| **SMS 자동 대체 발송** | 알림톡 실패 시 SMS로 자동 전환 |
| **v1 / v2 API 지원** | 설정 한 줄로 버전 전환 |
---
## 설치
```bash
pip install sendgo-python
```
또는 `pyproject.toml`:
```toml
[project]
dependencies = ["sendgo-python>=1.0.0"]
```
---
## 빠른 시작
### 1단계 — 환경변수 설정
```bash
# .env
SENDGO_ACCESS_KEY=your_access_key
SENDGO_SECRET_KEY=your_secret_key
SENDGO_KAKAO_SENDER_KEY=your_kakao_key
SENDGO_SMS_SENDER_KEY=your_sms_key
SENDGO_API_VERSION=v2
```
### 2단계 — 클라이언트 초기화
```python
import os
from sendgo import Sendgo
client = Sendgo(
access_key=os.environ["SENDGO_ACCESS_KEY"],
secret_key=os.environ["SENDGO_SECRET_KEY"],
kakao_sender_key=os.environ.get("SENDGO_KAKAO_SENDER_KEY"),
sms_sender_key=os.environ.get("SENDGO_SMS_SENDER_KEY"),
api_version="v2",
)
```
### 3단계 — 알림톡 전송
```python
client.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[
{
"contact": "01012345678", # 수신자 전화번호 (필수)
"name": "홍길동", # 수신자 이름 (선택)
"var1": "ORD-20260723-001", # 템플릿 변수 #{var1}
"var2": "스프링 부트 가이드", # 템플릿 변수 #{var2}
"var3": "29,000원", # 템플릿 변수 #{var3}
}
],
)
```
---
## 상세 사용법
### 카카오 알림톡
```python
# 다건 발송
client.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[
{"contact": "01011111111", "name": "홍길동", "var1": "ORD-001"},
{"contact": "01022222222", "name": "김철수", "var1": "ORD-002"},
{"contact": "01033333333", "name": "이영희", "var1": "ORD-003"},
],
)
# 예약 발송
client.alimtalk.send(
template_code="PROMO_SUMMER_2026",
schedule_type="SCHEDULED",
at="2026-07-28 09:00:00",
contacts=[{"contact": "01012345678", "var1": "여름 한정 50% 할인"}],
)
# 알림톡 실패 시 SMS 자동 대체 발송
client.alimtalk.send(
template_code="DELIVERY_START_001",
contacts=[{"contact": "01012345678", "var1": "ORD-001", "var2": "1234567890"}],
replace_sms="Y",
sms_subject="[배송 시작 안내]",
sms_content="주문하신 상품이 출고되었습니다.\n송장번호: #{var2}",
)
```
### 카카오 친구톡
> ⚠️ **Deprecated — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었습니다.**
> 2026-01-01 부터 친구톡 발송 요청은 카카오 측에서 **브랜드메시지(자유형)** 로 자동 대체 발송됩니다.
> 호출은 계속 성공하며, 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보내는 경로는
> 현재 이것뿐이므로 기존 코드를 당장 바꿀 필요는 없습니다.
>
> 다음의 경우에는 **브랜드메시지**를 사용하세요.
> - 템플릿 기반 리치 타입 (`FL`/`FC`/`FM`/`FP`/`FA`)
> - 채널 친구가 **아닌** 수신자 (`targeting` = `N` / `I`)
> - 수신 동의한 전체 채널 친구 동보 (`targeting` = `F`)
>
> 메시지 타입은 1:1 대응되며 변환은 서버가 처리합니다 — `FT`→`BT`, `FI`→`BI`, `FW`→`BW`,
> `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`.
```python
# 텍스트형
client.friendtalk.send(
content="안녕하세요! 7월 한정 특가 이벤트를 확인해보세요.",
contacts=[{"contact": "01012345678"}],
)
# 이미지형
client.friendtalk.send(
message_type="FI",
content="이번 주 특가 상품을 확인하세요!",
image_url="https://cdn.example.com/banner.jpg",
image_link="https://example.com/event",
contacts=[{"contact": "01012345678"}],
)
```
### SMS / LMS / MMS
```python
# SMS
client.sms.send_sms(
content="[Sendgo] 인증번호: 123456 (5분 이내 입력)",
contacts=[{"contact": "01012345678"}],
)
# LMS — 장문 (2,000자 이하)
client.sms.send_lms(
subject="[중요] 서비스 점검 안내",
content="""안녕하세요. 서비스 점검이 예정되어 있습니다.
■ 점검 일시: 2026-07-25 02:00 ~ 06:00
■ 영향 범위: 전체 서비스
이용에 불편을 드려 죄송합니다.""",
contacts=[{"contact": "01012345678"}],
)
# MMS — 이미지 포함
client.sms.send_mms(
subject="[이벤트] 7월 특가",
content="이번 달 특가 상품을 확인하세요!",
contacts=[{"contact": "01012345678"}],
)
```
---
## 브랜드메시지 사용법
브랜드메시지는 친구톡의 후속 채널입니다. 메시지 타입이 친구톡과 1:1 대응되며
(`FT`→`BT`, `FI`→`BI`, `FW`→`BW`, `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`),
요청에는 **친구톡 코드를 그대로** 넘기고 변환은 서버가 처리합니다.
친구톡과 달리 다음이 가능합니다.
- 채널 친구가 **아닌** 수신자에게 발송 (`targeting: N`)
- 수신 동의한 **전체 채널 친구 동보** 발송 (`targeting: F`, 수신자 목록 불필요)
- 리스트·캐러셀·커머스·동영상 등 **템플릿 기반 리치 메시지**
> v2 전용입니다. 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보낼 때는 여전히 친구톡 API 를 쓰세요 — 이 엔드포인트는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다. 친구톡 요청은 카카오 측에서 브랜드메시지(자유형)로 대체 발송됩니다.
```python
# 단건 발송 — 채널 친구 대상
client.brand_message.send(
targeting="M",
message_type="FL",
friend_template_uuid="9cd5460b-6458-4edc-9b11-c26d3013c340",
contacts=[{"contact": "01012345678", "var1": "29,000원"}],
)
# 동보 발송 — 수신 동의한 전체 채널 친구 (contacts 불필요)
client.brand_message.broadcast(
message_type="FW",
friend_template_uuid="9cd5460b-6458-4edc-9b11-c26d3013c340",
)
# 캠페인 조회 (from 은 예약어이므로 from_ 사용)
campaigns = client.brand_message.campaigns(from_="2026-08-01", count=10)
one = client.brand_message.campaign("1f0a6d0e-6b3b-4f0f-9b2f-2f6f6a1b7c11")
```
---
## 프레임워크 통합
### Django
```python
# settings.py
SENDGO = {
"access_key": env("SENDGO_ACCESS_KEY"),
"secret_key": env("SENDGO_SECRET_KEY"),
"kakao_sender_key": env("SENDGO_KAKAO_SENDER_KEY", default=None),
"sms_sender_key": env("SENDGO_SMS_SENDER_KEY", default=None),
"api_version": env("SENDGO_API_VERSION", default="v2"),
}
```
```python
# apps/notifications/services.py
from django.conf import settings
from sendgo import Sendgo
_sendgo: Sendgo | None = None
def get_sendgo() -> Sendgo:
global _sendgo
if _sendgo is None:
_sendgo = Sendgo(**settings.SENDGO)
return _sendgo
def send_order_confirm(phone: str, order_number: str) -> None:
get_sendgo().alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": phone, "var1": order_number}],
)
```
```python
# apps/orders/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Order
from apps.notifications.services import send_order_confirm
@receiver(post_save, sender=Order)
def on_order_created(sender, instance, created, **kwargs):
if created:
send_order_confirm(instance.user.phone, instance.number)
```
### FastAPI
```python
# core/sendgo.py
from functools import lru_cache
from sendgo import Sendgo
from .config import settings
@lru_cache
def get_sendgo() -> Sendgo:
return Sendgo(
access_key=settings.SENDGO_ACCESS_KEY,
secret_key=settings.SENDGO_SECRET_KEY,
kakao_sender_key=settings.SENDGO_KAKAO_SENDER_KEY,
api_version="v2",
)
```
```python
# routers/notify.py
from fastapi import APIRouter, Depends
from sendgo import Sendgo
from core.sendgo import get_sendgo
router = APIRouter(prefix="/api")
@router.post("/notify/order")
async def notify_order(
phone: str,
order_number: str,
sendgo: Sendgo = Depends(get_sendgo),
):
sendgo.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": phone, "var1": order_number}],
)
return {"success": True}
```
### Celery 비동기 발송
```python
# tasks/notifications.py
from celery import shared_task
from sendgo import Sendgo, SendgoError
import logging
logger = logging.getLogger(__name__)
@shared_task(bind=True, max_retries=3, default_retry_delay=10)
def send_alimtalk_task(self, template_code: str, contacts: list[dict]) -> None:
"""카카오 알림톡 비동기 발송 Celery 태스크"""
sendgo = Sendgo(
access_key=settings.SENDGO_ACCESS_KEY,
secret_key=settings.SENDGO_SECRET_KEY,
kakao_sender_key=settings.SENDGO_KAKAO_SENDER_KEY,
)
try:
sendgo.alimtalk.send(template_code=template_code, contacts=contacts)
except SendgoError as e:
logger.error("알림톡 발송 실패: %s [%s]", e, e.error_code)
if e.error_code not in ("INVALID_TEMPLATE_CODE", "PAYMENT_REQUIRED"):
raise self.retry(exc=e)
```
```python
# 사용
send_alimtalk_task.delay("ORDER_CONFIRM_001", [{"contact": "01012345678", "var1": "ORD-001"}])
```
---
## 예외 처리
```python
from sendgo import SendgoError
try:
client.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": "01012345678"}],
)
except SendgoError as e:
print(f"발송 실패: HTTP {e.status_code} [{e.error_code}]")
print(f"엔드포인트: {e.endpoint}, API 버전: {e.api_version}")
match e.error_code:
case "INVALID_ACCESS_KEY" | "INVALID_SECRET_KEY":
alert_ops("Sendgo 인증키를 확인하세요.")
case "INVALID_TEMPLATE_CODE":
logger.warning("존재하지 않는 템플릿: %s", template_code)
case "PAYMENT_REQUIRED":
alert_ops("Sendgo 크레딧이 부족합니다.")
case "IP_NOT_ALLOWED":
alert_ops("허용되지 않은 IP에서 요청이 발생했습니다.")
```
---
## 설정 옵션
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---------|------|------|--------|------|
| `access_key` | `str` | **필수** | — | Sendgo 액세스 키 |
| `secret_key` | `str` | **필수** | — | Sendgo 시크릿 키 |
| `kakao_sender_key` | `str \| None` | 선택 | `None` | 카카오 발신프로필 키 |
| `sms_sender_key` | `str \| None` | 선택 | `None` | SMS 발신자 키 |
| `api_version` | `str` | 선택 | `'v1'` | API 버전 (`v1` \| `v2`) |
| `base_url` | `str` | 선택 | `'https://sendgo.io'` | API 기본 URL |
---
## 자주 묻는 질문 (FAQ)
**Q. 비동기(async/await)를 지원하나요?**
A. 현재 버전은 동기(`requests` 기반)만 지원합니다. FastAPI 등 비동기 환경에서는 `asyncio.get_event_loop().run_in_executor()`로 스레드풀에서 실행하거나, Celery 태스크로 위임하는 방법을 권장합니다. 비동기 버전(`httpx` 기반)은 향후 추가될 예정입니다.
**Q. 멀티스레드 환경에서 안전한가요?**
A. 토큰 관리에 `threading.Lock`을 사용하여 멀티스레드 환경에서도 안전합니다.
**Q. 알림톡 템플릿은 어디서 등록하나요?**
A. [Sendgo 콘솔](https://sendgo.io) → 알림톡 템플릿 → 템플릿 작성 → 카카오 심사 신청 (보통 1~3일 소요)
**Q. 대량 발송 시 rate limit이 있나요?**
A. Sendgo 플랜별로 TPS 제한이 있습니다. [요금 정책](https://sendgo.io/pricing) 참조.
---
## 짧은 URL
짧은 URL 은 메시지 본문의 링크를 줄이고, 그 링크가 실제로 눌렸는지 집계합니다.
문자는 바이트 수가 요금과 직결되므로 링크를 줄이면 그만큼 본문을 더 쓸 수 있습니다.
같은 원본 URL 을 다시 줄이면 **기존 링크가 그대로 반환**됩니다. 캠페인별로 반응을
따로 집계하려면 `forceNew` 로 새 코드를 만드세요.
`deactivate` 는 링크를 삭제하지 않고 리다이렉트만 중지합니다. 이미 발송한 메시지의
링크를 무효화할 때 쓰며, 누적 통계는 남고 이후 접속은 `410 Gone` 이 됩니다.
```python
# 짧은 URL 생성 (v2 전용)
created = sendgo.short_url.create(
target_url="https://example.com/promotions/summer-sale",
title="여름 세일 랜딩",
)
code = created["data"]["code"]
link = created["data"]["shortUrl"]
# 반응 통계 — 일별 추이 + 디바이스/유입경로/국가별 분해
# `from` 은 파이썬 예약어이므로 `from_` 을 쓴다
stats = sendgo.short_url.stats(code, from_="2026-08-01")
sendgo.short_url.list(count=10)
sendgo.short_url.show(code)
sendgo.short_url.deactivate(code) # 리다이렉트만 중지, 통계는 남는다
```
`stats` 는 일별 추이(`daily`)와 디바이스(`byDevice`)·유입경로(`byReferer`)·국가(`byCountry`)별
분해를 반환합니다. 일별 추이는 사전 집계 표에서 읽으므로 클릭이 많아도 응답 시간이 일정합니다.
## 변경 사항
### 1.2.1 (2026-08-14)
- 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
npm/PyPI/Packagist/Maven/NuGet/RubyGems 검색 결과에 그대로 찍히는 문자열이라
종료된 채널을 계속 홍보하고 있었습니다.
- 검색 키워드에 `brand-message` 를 추가했습니다 (`friendtalk` 은 유입 검색어라 유지).
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
### 1.1.0 (2026-08-11)
- 짧은 URL 추가 — `client.short_url`
- `HttpClient.delete()` 추가
- **버전 단일화** — `__init__.py` 에 하드코딩된 `__version__` 을 제거하고 `importlib.metadata` 로 `pyproject.toml` 값을 읽는다. 두 곳이 어긋나 릴리스가 실패한 적이 있다.
## 라이선스
MIT License © 2026 [Sendgo](https://sendgo.io)
---
## 패키지 정보
- **패키지**: `sendgo-python` (PyPI)
- **저장소**: [send-go/python](https://github.com/send-go/python)
- **레지스트리**: https://pypi.org/project/sendgo-python/
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **Django에서 카카오 알림톡, 브랜드메시지, SMS를 가장 쉽게 발송하는 공식 Django 확장 패키지**
`sendgo-django`는 [`sendgo-python`](https://github.com/send-go/python) 코어를 확장한 **Django 전용 패키지**입니다.
`settings.SENDGO` 설정 기반의 클라이언트 생성, 지연 로딩 프록시(`client`), AppConfig 통합을 제공합니다.
---
## 설치
```bash
pip install sendgo-django
```
`sendgo-python` 코어는 의존성으로 자동 설치됩니다.
---
## 빠른 시작
### 1단계 — `INSTALLED_APPS` 등록 (`settings.py`)
```python
INSTALLED_APPS += ["sendgo_django"]
```
### 2단계 — `SENDGO` 설정 추가 (`settings.py`)
```python
import os
SENDGO = {
"ACCESS_KEY": os.environ["SENDGO_ACCESS_KEY"],
"SECRET_KEY": os.environ["SENDGO_SECRET_KEY"],
"KAKAO_SENDER_KEY": os.environ.get("SENDGO_KAKAO_SENDER_KEY"),
"SMS_SENDER_KEY": os.environ.get("SENDGO_SMS_SENDER_KEY"),
"API_VERSION": "v2",
# "BASE_URL": "https://sendgo.io", # 기본값
}
```
### 3단계 — 뷰에서 알림톡 전송
```python
# views.py
from django.http import JsonResponse
from sendgo_django import client
def confirm_order(request, order_id):
order = get_order(order_id)
client.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[
{
"contact": order.phone,
"name": order.name,
"var1": order.number,
"var2": f"{order.total:,}원",
},
],
)
return JsonResponse({"success": True})
```
---
## 프록시 사용법
`sendgo_django.client`는 `SimpleLazyObject` 기반 **지연 로딩 프록시**입니다.
패키지를 임포트하는 시점에는 클라이언트를 생성하지 않고, 실제로 속성에 접근할 때
`get_client()`를 호출합니다. 따라서 설정이 없어도 임포트만으로는 오류가 나지 않습니다.
```python
from sendgo_django import client
# 알림톡 발송
client.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": "01012345678", "var1": "ORD-001"}],
)
# SMS 발송
client.sms.send_sms(
content="[인증] 인증번호: 123456",
contacts=[{"contact": "01012345678"}],
)
```
명시적으로 인스턴스를 얻고 싶다면 `get_client()`를 직접 호출할 수 있습니다.
```python
from sendgo_django import get_client
sendgo = get_client()
sendgo.friendtalk.send(content="안녕하세요!", contacts=[{"contact": "01012345678"}])
```
---
## 상세 사용법
### 알림톡
```python
from sendgo_django import client
# 다건 발송
client.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[
{"contact": "01011111111", "name": "홍길동", "var1": "ORD-001", "var2": "29,000원"},
{"contact": "01022222222", "name": "김철수", "var1": "ORD-002", "var2": "15,000원"},
],
)
```
### 친구톡
> ⚠️ **Deprecated — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었습니다.**
> 2026-01-01 부터 친구톡 발송 요청은 카카오 측에서 **브랜드메시지(자유형)** 로 자동 대체 발송됩니다.
> 호출은 계속 성공하며, 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보내는 경로는
> 현재 이것뿐이므로 기존 코드를 당장 바꿀 필요는 없습니다.
>
> 다음의 경우에는 **브랜드메시지**를 사용하세요.
> - 템플릿 기반 리치 타입 (`FL`/`FC`/`FM`/`FP`/`FA`)
> - 채널 친구가 **아닌** 수신자 (`targeting` = `N` / `I`)
> - 수신 동의한 전체 채널 친구 동보 (`targeting` = `F`)
>
> 메시지 타입은 1:1 대응되며 변환은 서버가 처리합니다 — `FT`→`BT`, `FI`→`BI`, `FW`→`BW`,
> `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`.
```python
from sendgo_django import client
# 텍스트형
client.friendtalk.send(
content="안녕하세요! 7월 한정 특가 이벤트를 확인해보세요.",
contacts=[{"contact": "01012345678"}],
)
```
### SMS / LMS / MMS
```python
from sendgo_django import client
# SMS (90자 이하)
client.sms.send_sms(
content="[Sendgo] 인증번호: 123456 (5분 이내 입력)",
contacts=[{"contact": "01012345678"}],
)
```
---
## 서비스 클래스 패턴
```python
# app/services.py
import logging
from sendgo import SendgoError
from sendgo_django import client
logger = logging.getLogger(__name__)
class NotificationService:
"""알림 발송 로직을 캡슐화한 서비스 클래스."""
def send_order_confirm(self, phone: str, order_no: str, amount: int) -> None:
client.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": phone, "var1": order_no, "var2": f"{amount:,}원"}],
)
def send_verification_code(self, phone: str, code: str) -> None:
try:
client.alimtalk.send(
template_code="VERIFY_CODE_001",
contacts=[{"contact": phone, "var1": code}],
)
except SendgoError:
logger.exception("Sendgo 인증번호 발송 실패")
raise
```
---
## 예외 처리
```python
from sendgo import SendgoError
from sendgo_django import client
try:
client.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": "01012345678", "var1": "ORD-001"}],
)
except SendgoError as e:
logger.error("Sendgo 발송 실패: %s", e)
```
---
## 설정 옵션
`settings.SENDGO` 딕셔너리 키:
| 키 | 필수 | 기본값 | 설명 |
|----|------|--------|------|
| `ACCESS_KEY` | ✅ | — | Sendgo 액세스 키 |
| `SECRET_KEY` | ✅ | — | Sendgo 시크릿 키 |
| `KAKAO_SENDER_KEY` | | `None` | 카카오 발신프로필 키 |
| `SMS_SENDER_KEY` | | `None` | SMS 발신자 키 |
| `API_VERSION` | | `"v2"` | API 버전 |
| `BASE_URL` | | `"https://sendgo.io"` | API 기본 URL |
`ACCESS_KEY` 또는 `SECRET_KEY`가 없으면 클라이언트 생성 시 `django.core.exceptions.ImproperlyConfigured`가 발생합니다.
---
## 자주 묻는 질문 (FAQ)
**Q. `sendgo-python`과의 차이는 무엇인가요?**
A. `sendgo-python`은 프레임워크 독립적인 순수 Python 코어 패키지입니다. `sendgo-django`는 이를 확장해 `settings.SENDGO` 설정 바인딩, 지연 로딩 프록시, AppConfig 통합을 추가합니다.
**Q. 설정 없이 임포트하면 오류가 나나요?**
A. 아니요. `client`는 지연 프록시이므로 임포트만으로는 오류가 없고, 실제 사용(속성 접근) 시점에 설정을 검증합니다.
**Q. 테스트 시 클라이언트를 초기화하려면?**
A. `sendgo_django.conf.reset()`을 호출하면 메모이즈된 클라이언트가 초기화됩니다.
**Q. Django 4.2, 5.x를 지원하나요?**
A. 네, `Django>=4.2`를 지원합니다.
---
## 브랜드메시지 · 짧은 URL
이 패키지는 코어(`sendgo-python`)의 클라이언트를 그대로 노출하므로, 코어에 있는 채널이
모두 그대로 쓸 수 있습니다. 두 기능 모두 **v2 전용**입니다.
| 기능 | 접근 |
|------|------|
| 카카오 브랜드메시지 (친구톡의 후속 채널) | `client.brand_message` |
| 짧은 URL (단축 + 클릭 반응 분석) | `client.short_url` |
브랜드메시지는 채널 친구가 아닌 수신자에게도 보낼 수 있고(`targeting` = `N`),
수신 동의한 전체 채널 친구에게 동보 발송할 수도 있습니다(`targeting` = `F`).
짧은 URL 은 메시지 본문의 링크를 줄이고 클릭 반응(일별 추이·디바이스·유입경로·국가)을
집계합니다.
사용 예시와 파라미터는 [코어 README](https://github.com/send-go) 와
[SDK 가이드](https://sendgo.io/ko/sdk) 를 참고하세요.
## 변경 사항
### 1.2.1 (2026-08-14)
- 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
npm/PyPI/Packagist/Maven/NuGet/RubyGems 검색 결과에 그대로 찍히는 문자열이라
종료된 채널을 계속 홍보하고 있었습니다.
- 검색 키워드에 `brand-message` 를 추가했습니다 (`friendtalk` 은 유입 검색어라 유지).
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
- 짧은 URL 지원 (1.1.0 릴리스 누락분 포함).
### 1.1.0 (2026-08-11)
- 브랜드메시지·짧은 URL 접근 방법 문서화 (코어를 그대로 노출)
## 라이선스
MIT License © 2026 [Sendgo](https://sendgo.io)
---
## 패키지 정보
- **패키지**: `sendgo-django` (PyPI)
- **저장소**: [send-go/django](https://github.com/send-go/django)
- **레지스트리**: https://pypi.org/project/sendgo-django/
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **FastAPI에서 카카오 알림톡, 브랜드메시지, SMS를 가장 쉽게 발송하는 공식 FastAPI 확장 패키지**
`sendgo-fastapi`는 [`sendgo-python`](https://github.com/send-go/python) 코어를 확장한 **FastAPI 전용 패키지**입니다.
환경변수 기반 설정 로딩(pydantic-settings), 의존성 주입(`Depends`), lifespan 초기화 등 FastAPI 통합을 완벽하게 제공합니다.
---
## 설치
```bash
pip install sendgo-fastapi
```
코어 패키지 `sendgo-python`은 의존성으로 자동 설치됩니다.
---
## 빠른 시작
### 1단계 — 환경변수 설정 (`.env` 또는 셸 환경)
모든 환경변수는 `SENDGO_` 접두사를 사용하며, `SendgoSettings`가 자동으로 바인딩합니다.
```env
SENDGO_ACCESS_KEY=your_access_key
SENDGO_SECRET_KEY=your_secret_key
SENDGO_KAKAO_SENDER_KEY=your_kakao_key
SENDGO_SMS_SENDER_KEY=your_sms_key
SENDGO_API_VERSION=v2
SENDGO_BASE_URL=https://sendgo.io
```
### 2단계 — 알림톡 전송
```python
from fastapi import FastAPI
from sendgo_fastapi import SendgoDep
app = FastAPI()
@app.post("/notify")
async def notify(sendgo: SendgoDep):
sendgo.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[
{"contact": "01012345678", "name": "홍길동", "var1": "ORD-001", "var2": "29,000원"},
],
)
return {"success": True}
```
`SendgoDep`은 `Annotated[Sendgo, Depends(get_sendgo)]`의 별칭으로, 라우트 인자에
타입힌트만 추가하면 Sendgo 클라이언트가 자동으로 주입됩니다.
---
## 의존성 주입 사용법
```python
from fastapi import FastAPI
from sendgo_fastapi import SendgoDep
app = FastAPI()
@app.post("/verify")
async def send_verification(sendgo: SendgoDep):
# SMS 발송
sendgo.sms.send_sms(
content="[인증] 인증번호: 123456 (5분 이내 입력)",
contacts=[{"contact": "01012345678"}],
)
return {"success": True}
```
환경변수 기반 클라이언트는 최초 요청 시 한 번만 생성되어 메모이즈됩니다.
---
## lifespan 초기화
애플리케이션 시작 시점에 클라이언트를 미리 생성해 `app.state.sendgo`에
저장하려면 `init_sendgo(app)`을 lifespan에서 호출하세요. 이 경우
`SendgoDep`/`get_sendgo`는 저장된 인스턴스를 우선 사용합니다.
```python
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sendgo_fastapi import SendgoDep, init_sendgo
@asynccontextmanager
async def lifespan(app: FastAPI):
# 시작 시점에 환경변수 기반 클라이언트를 생성해 app.state.sendgo 에 저장
init_sendgo(app)
yield
app = FastAPI(lifespan=lifespan)
@app.post("/notify")
async def notify(sendgo: SendgoDep):
sendgo.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": "01012345678", "var1": "ORD-001"}],
)
return {"success": True}
```
커스텀 설정을 직접 주입할 수도 있습니다.
```python
from sendgo_fastapi import SendgoSettings, init_sendgo
init_sendgo(app, SendgoSettings(access_key="...", secret_key="..."))
```
---
## 상세 사용법
### 알림톡
```python
from sendgo_fastapi import SendgoDep
@app.post("/orders/{order_id}/confirm")
async def confirm(order_id: str, sendgo: SendgoDep):
# 다건 발송
sendgo.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[
{"contact": "01011111111", "name": "홍길동", "var1": "ORD-001", "var2": "29,000원"},
{"contact": "01022222222", "name": "김철수", "var1": "ORD-002", "var2": "15,000원"},
],
)
# 예약 발송
sendgo.alimtalk.send(
template_code="PROMO_SUMMER_2026",
schedule_type="SCHEDULED",
at="2026-07-28 09:00:00",
contacts=[{"contact": "01012345678", "var1": "여름 한정 50% 할인"}],
)
# SMS 자동 대체 발송
sendgo.alimtalk.send(
template_code="DELIVERY_START_001",
replace_sms="Y",
sms_subject="[배송 시작 안내]",
sms_content="주문하신 상품이 출고되었습니다.\n송장번호: 1234567890",
contacts=[{"contact": "01012345678", "var1": "ORD-001", "var2": "1234567890"}],
)
return {"success": True}
```
### 친구톡
> ⚠️ **Deprecated — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었습니다.**
> 2026-01-01 부터 친구톡 발송 요청은 카카오 측에서 **브랜드메시지(자유형)** 로 자동 대체 발송됩니다.
> 호출은 계속 성공하며, 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보내는 경로는
> 현재 이것뿐이므로 기존 코드를 당장 바꿀 필요는 없습니다.
>
> 다음의 경우에는 **브랜드메시지**를 사용하세요.
> - 템플릿 기반 리치 타입 (`FL`/`FC`/`FM`/`FP`/`FA`)
> - 채널 친구가 **아닌** 수신자 (`targeting` = `N` / `I`)
> - 수신 동의한 전체 채널 친구 동보 (`targeting` = `F`)
>
> 메시지 타입은 1:1 대응되며 변환은 서버가 처리합니다 — `FT`→`BT`, `FI`→`BI`, `FW`→`BW`,
> `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`.
```python
# 텍스트형
sendgo.friendtalk.send(
content="안녕하세요! 7월 한정 특가 이벤트를 확인해보세요.",
contacts=[{"contact": "01012345678"}],
)
# 이미지형
sendgo.friendtalk.send(
message_type="FI",
content="이번 주 특가 상품을 확인하세요!",
image_url="https://cdn.example.com/banner.jpg",
image_link="https://example.com/event",
contacts=[{"contact": "01012345678"}],
)
# 버튼 포함
sendgo.friendtalk.send(
content="7월 쿠폰이 도착했습니다! 지금 바로 사용하세요.",
buttons=[
{"name": "쿠폰 받기", "type": "WL", "linkMo": "https://example.com/coupon"},
{"name": "고객센터", "type": "WL", "linkMo": "https://example.com/cs"},
],
contacts=[{"contact": "01012345678"}],
)
```
### SMS / LMS / MMS
```python
# SMS (90자 이하)
sendgo.sms.send_sms(
content="[Sendgo] 인증번호: 123456 (5분 이내 입력)",
contacts=[{"contact": "01012345678"}],
)
# LMS (장문, 2,000자 이하)
sendgo.sms.send_lms(
subject="[중요] 서비스 점검 안내",
content="안녕하세요. 서비스 점검이 예정되어 있습니다.\n\n■ 일시: 2026-07-25 02:00 ~ 06:00",
contacts=[{"contact": "01012345678"}],
)
# MMS (이미지 포함)
sendgo.sms.send_mms(
subject="[이벤트] 7월 특가",
content="이번 달 특가 상품을 확인하세요!",
contacts=[{"contact": "01011111111"}, {"contact": "01022222222"}],
)
```
---
## 서비스 클래스 패턴
라우트에서 직접 발송하는 대신, 재사용 가능한 서비스 클래스로 분리할 수 있습니다.
```python
# app/services/notification.py
from sendgo import Sendgo
class NotificationService:
def __init__(self, sendgo: Sendgo) -> None:
self._sendgo = sendgo
def send_order_confirm(self, phone: str, order_no: str, amount: int) -> None:
self._sendgo.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": phone, "var1": order_no, "var2": f"{amount:,}원"}],
)
```
```python
# app/main.py
from typing import Annotated
from fastapi import Depends, FastAPI
from sendgo_fastapi import SendgoDep
from app.services.notification import NotificationService
app = FastAPI()
def get_notification_service(sendgo: SendgoDep) -> NotificationService:
return NotificationService(sendgo)
NotificationDep = Annotated[NotificationService, Depends(get_notification_service)]
@app.post("/orders/{order_id}/confirm")
async def confirm(order_id: str, service: NotificationDep):
service.send_order_confirm("01012345678", order_id, 29000)
return {"success": True}
```
---
## 백그라운드 태스크 비동기 발송
발송을 요청 응답과 분리하려면 FastAPI의 `BackgroundTasks`를 사용하세요.
```python
from fastapi import BackgroundTasks, FastAPI
from sendgo_fastapi import SendgoDep
app = FastAPI()
@app.post("/notify")
async def notify(sendgo: SendgoDep, background_tasks: BackgroundTasks):
background_tasks.add_task(
sendgo.alimtalk.send,
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": "01012345678", "var1": "ORD-001"}],
)
return {"success": True, "queued": True}
```
---
## 예외 처리
```python
from fastapi import FastAPI, HTTPException
from sendgo import SendgoError
from sendgo_fastapi import SendgoDep
app = FastAPI()
@app.post("/notify")
async def notify(sendgo: SendgoDep):
try:
sendgo.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": "01012345678", "var1": "ORD-001"}],
)
except SendgoError as e:
raise HTTPException(status_code=502, detail=f"Sendgo 발송 실패: {e}")
return {"success": True}
```
전역 예외 핸들러로 처리할 수도 있습니다.
```python
from fastapi import Request
from fastapi.responses import JSONResponse
from sendgo import SendgoError
@app.exception_handler(SendgoError)
async def sendgo_exception_handler(request: Request, exc: SendgoError):
return JSONResponse(status_code=502, content={"detail": str(exc)})
```
---
## 설정 옵션
`SendgoSettings`(pydantic-settings)가 `SENDGO_` 접두사 환경변수를 자동으로 읽습니다.
| 필드 | 환경변수 | 기본값 | 설명 |
|------|---------|--------|------|
| `access_key` | `SENDGO_ACCESS_KEY` | — | Sendgo 액세스 키 (필수) |
| `secret_key` | `SENDGO_SECRET_KEY` | — | Sendgo 시크릿 키 (필수) |
| `kakao_sender_key` | `SENDGO_KAKAO_SENDER_KEY` | `None` | 카카오 발신프로필 키 |
| `sms_sender_key` | `SENDGO_SMS_SENDER_KEY` | `None` | SMS 발신자 키 |
| `api_version` | `SENDGO_API_VERSION` | `"v2"` | API 버전 |
| `base_url` | `SENDGO_BASE_URL` | `"https://sendgo.io"` | API 기본 URL |
---
## 자주 묻는 질문 (FAQ)
**Q. `sendgo-python`과의 차이는 무엇인가요?**
A. `sendgo-python`은 프레임워크 독립적인 순수 Python 코어 패키지입니다. `sendgo-fastapi`는 이를 확장해 pydantic-settings 기반 환경변수 로딩, `Depends` 의존성 주입, lifespan 초기화 등 FastAPI 통합을 추가합니다.
**Q. 환경변수 없이 설정을 직접 지정할 수 있나요?**
A. 네, `init_sendgo(app, SendgoSettings(access_key="...", secret_key="..."))`처럼 설정 객체를 직접 주입할 수 있습니다.
**Q. `get_sendgo`는 요청마다 새 클라이언트를 만드나요?**
A. 아니요. 환경변수 기반 싱글턴 또는 `app.state.sendgo`에 저장된 인스턴스를 재사용합니다.
**Q. 테스트 시 Sendgo를 Mock 처리하려면?**
A. `app.dependency_overrides[get_sendgo] = lambda: mock_client`로 의존성을 교체하면 됩니다.
---
## 브랜드메시지 · 짧은 URL
이 패키지는 코어(`sendgo-python`)의 클라이언트를 그대로 노출하므로, 코어에 있는 채널이
모두 그대로 쓸 수 있습니다. 두 기능 모두 **v2 전용**입니다.
| 기능 | 접근 |
|------|------|
| 카카오 브랜드메시지 (친구톡의 후속 채널) | `sendgo.brand_message` |
| 짧은 URL (단축 + 클릭 반응 분석) | `sendgo.short_url` |
브랜드메시지는 채널 친구가 아닌 수신자에게도 보낼 수 있고(`targeting` = `N`),
수신 동의한 전체 채널 친구에게 동보 발송할 수도 있습니다(`targeting` = `F`).
짧은 URL 은 메시지 본문의 링크를 줄이고 클릭 반응(일별 추이·디바이스·유입경로·국가)을
집계합니다.
사용 예시와 파라미터는 [코어 README](https://github.com/send-go) 와
[SDK 가이드](https://sendgo.io/ko/sdk) 를 참고하세요.
## 변경 사항
### 1.2.1 (2026-08-14)
- 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
npm/PyPI/Packagist/Maven/NuGet/RubyGems 검색 결과에 그대로 찍히는 문자열이라
종료된 채널을 계속 홍보하고 있었습니다.
- 검색 키워드에 `brand-message` 를 추가했습니다 (`friendtalk` 은 유입 검색어라 유지).
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
- 짧은 URL 지원 (1.1.0 릴리스 누락분 포함).
### 1.1.0 (2026-08-11)
- 브랜드메시지·짧은 URL 접근 방법 문서화 (코어를 그대로 노출)
## 라이선스
MIT License © 2026 [Sendgo](https://sendgo.io)
---
## 패키지 정보
- **패키지**: `sendgo-fastapi` (PyPI)
- **저장소**: [send-go/fastapi](https://github.com/send-go/fastapi)
- **레지스트리**: https://pypi.org/project/sendgo-fastapi/
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **Go에서 카카오 알림톡, 브랜드메시지, SMS를 가장 쉽게 발송하는 공식 Go SDK**
`sendgo-go`는 [Sendgo](https://sendgo.io) 알림 API를 위한 공식 Go SDK입니다.
**표준 라이브러리만 사용**하며, 완전한 타입 안전성과 동시성 안전 토큰 관리를 제공합니다.
---
## 설치
```bash
go get github.com/send-go/go
```
---
## 빠른 시작
```go
package main
import (
"fmt"
"log"
"os"
sendgo "github.com/send-go/go/sendgo"
)
func main() {
client, err := sendgo.New(sendgo.Config{
AccessKey: os.Getenv("SENDGO_ACCESS_KEY"),
SecretKey: os.Getenv("SENDGO_SECRET_KEY"),
KakaoSenderKey: os.Getenv("SENDGO_KAKAO_SENDER_KEY"),
SmsSenderKey: os.Getenv("SENDGO_SMS_SENDER_KEY"),
ApiVersion: "v2",
})
if err != nil {
log.Fatal(err)
}
// 알림톡 발송
result, err := client.Alimtalk.Send(sendgo.AlimtalkRequest{
TemplateCode: "ORDER_CONFIRM_001",
Contacts: []sendgo.Contact{
{Contact: "01012345678", Name: "홍길동", Var1: "ORD-001", Var2: "29,000원"},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("발송 결과: %v\n", result)
}
```
---
## 알림톡 상세 사용법
```go
package main
import (
"log"
"os"
sendgo "github.com/send-go/go/sendgo"
)
func main() {
client, _ := sendgo.New(sendgo.Config{
AccessKey: os.Getenv("SENDGO_ACCESS_KEY"),
SecretKey: os.Getenv("SENDGO_SECRET_KEY"),
KakaoSenderKey: os.Getenv("SENDGO_KAKAO_SENDER_KEY"),
SmsSenderKey: os.Getenv("SENDGO_SMS_SENDER_KEY"),
ApiVersion: "v2",
})
// 다건 발송
client.Alimtalk.Send(sendgo.AlimtalkRequest{
TemplateCode: "ORDER_CONFIRM_001",
Contacts: []sendgo.Contact{
{Contact: "01011111111", Name: "홍길동", Var1: "ORD-001", Var2: "29,000원"},
{Contact: "01022222222", Name: "김철수", Var1: "ORD-002", Var2: "15,000원"},
{Contact: "01033333333", Name: "이영희", Var1: "ORD-003", Var2: "52,000원"},
},
})
// 예약 발송
client.Alimtalk.Send(sendgo.AlimtalkRequest{
TemplateCode: "PROMO_SUMMER_2026",
ScheduleType: "SCHEDULED",
At: sendgo.String("2026-07-28 09:00:00"),
Contacts: []sendgo.Contact{
{Contact: "01012345678", Var1: "여름 한정 50% 할인"},
},
})
// SMS 자동 대체 발송
client.Alimtalk.Send(sendgo.AlimtalkRequest{
TemplateCode: "DELIVERY_START_001",
ReplaceSms: "Y",
SmsSubject: sendgo.String("[배송 시작 안내]"),
SmsContent: sendgo.String("주문하신 상품이 출고되었습니다.\n송장번호: #{var2}"),
Contacts: []sendgo.Contact{
{Contact: "01012345678", Var1: "ORD-001", Var2: "1234567890"},
},
})
}
```
---
## 친구톡 사용법
> ⚠️ **Deprecated — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었습니다.**
> 2026-01-01 부터 친구톡 발송 요청은 카카오 측에서 **브랜드메시지(자유형)** 로 자동 대체 발송됩니다.
> 호출은 계속 성공하며, 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보내는 경로는
> 현재 이것뿐이므로 기존 코드를 당장 바꿀 필요는 없습니다.
>
> 다음의 경우에는 **브랜드메시지**를 사용하세요.
> - 템플릿 기반 리치 타입 (`FL`/`FC`/`FM`/`FP`/`FA`)
> - 채널 친구가 **아닌** 수신자 (`targeting` = `N` / `I`)
> - 수신 동의한 전체 채널 친구 동보 (`targeting` = `F`)
>
> 메시지 타입은 1:1 대응되며 변환은 서버가 처리합니다 — `FT`→`BT`, `FI`→`BI`, `FW`→`BW`,
> `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`.
```go
// 텍스트형
client.Friendtalk.Send(sendgo.FriendtalkRequest{
Content: "안녕하세요! 7월 한정 특가 이벤트를 확인해보세요.",
Contacts: []sendgo.Contact{
{Contact: "01012345678"},
},
})
// 이미지형
client.Friendtalk.Send(sendgo.FriendtalkRequest{
MessageType: "FI",
Content: "이번 주 특가 상품을 확인하세요!",
ImageURL: "https://cdn.example.com/banner.jpg",
ImageLink: "https://example.com/event",
Contacts: []sendgo.Contact{
{Contact: "01012345678"},
},
})
```
---
## 브랜드메시지 사용법
브랜드메시지는 친구톡의 후속 채널입니다. 메시지 타입이 친구톡과 1:1 대응되며
(`FT`→`BT`, `FI`→`BI`, `FW`→`BW`, `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`),
요청에는 **친구톡 코드를 그대로** 넘기고 변환은 서버가 처리합니다.
친구톡과 달리 다음이 가능합니다.
- 채널 친구가 **아닌** 수신자에게 발송 (`targeting: N`)
- 수신 동의한 **전체 채널 친구 동보** 발송 (`targeting: F`, 수신자 목록 불필요)
- 리스트·캐러셀·커머스·동영상 등 **템플릿 기반 리치 메시지**
> v2 전용입니다. 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보낼 때는 여전히 친구톡 API 를 쓰세요 — 이 엔드포인트는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다. 친구톡 요청은 카카오 측에서 브랜드메시지(자유형)로 대체 발송됩니다.
```go
// 단건 발송 — 채널 친구 대상
_, err := client.BrandMessage.Send(sendgo.BrandMessageRequest{
Targeting: "M",
MessageType: "FL",
FriendTemplateUUID: "9cd5460b-6458-4edc-9b11-c26d3013c340",
Contacts: []sendgo.Contact{
{Contact: "01012345678", Var1: "29,000원"},
},
})
// 동보 발송 — 수신 동의한 전체 채널 친구 (Contacts 불필요)
_, err = client.BrandMessage.Broadcast(sendgo.BrandMessageRequest{
MessageType: "FW",
FriendTemplateUUID: "9cd5460b-6458-4edc-9b11-c26d3013c340",
})
// 캠페인 조회
list, err := client.BrandMessage.Campaigns(sendgo.BrandMessageListQuery{Count: 10})
one, err := client.BrandMessage.Campaign("1f0a6d0e-6b3b-4f0f-9b2f-2f6f6a1b7c11")
```
---
## SMS / LMS / MMS 사용법
```go
// SMS
client.SMS.SendSMS(sendgo.SmsRequest{
Content: "[Sendgo] 인증번호: 123456 (5분 이내 입력)",
Contacts: []sendgo.Contact{
{Contact: "01012345678"},
},
})
// LMS
client.SMS.SendLMS(sendgo.SmsRequest{
Subject: sendgo.String("[중요] 서비스 점검 안내"),
Content: "안녕하세요. 서비스 점검이 예정되어 있습니다.\n■ 일시: 2026-07-25 02:00 ~ 06:00",
Contacts: []sendgo.Contact{
{Contact: "01012345678"},
},
})
// MMS
client.SMS.SendMMS(sendgo.SmsRequest{
Subject: sendgo.String("[이벤트] 7월 특가"),
Content: "이번 달 특가 상품을 확인하세요!",
Contacts: []sendgo.Contact{
{Contact: "01011111111"},
{Contact: "01022222222"},
},
})
// 예약 문자
client.SMS.SendSMS(sendgo.SmsRequest{
Content: "[알림] 예약 미팅을 확인해주세요.",
ScheduleType: "SCHEDULED",
At: sendgo.String("2026-07-23 08:00:00"),
Contacts: []sendgo.Contact{{Contact: "01012345678"}},
})
```
---
## 프레임워크 통합
### net/http 서버
```go
package main
import (
"encoding/json"
"net/http"
"os"
sendgo "github.com/send-go/go/sendgo"
)
var sg *sendgo.Client
func init() {
var err error
sg, err = sendgo.New(sendgo.Config{
AccessKey: os.Getenv("SENDGO_ACCESS_KEY"),
SecretKey: os.Getenv("SENDGO_SECRET_KEY"),
KakaoSenderKey: os.Getenv("SENDGO_KAKAO_SENDER_KEY"),
ApiVersion: "v2",
})
if err != nil {
panic(err)
}
}
func notifyOrderHandler(w http.ResponseWriter, r *http.Request) {
var req struct {
Phone string `json:"phone"`
OrderNo string `json:"order_no"`
}
json.NewDecoder(r.Body).Decode(&req)
_, err := sg.Alimtalk.Send(sendgo.AlimtalkRequest{
TemplateCode: "ORDER_CONFIRM_001",
Contacts: []sendgo.Contact{{Contact: req.Phone, Var1: req.OrderNo}},
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func main() {
http.HandleFunc("/api/notify/order", notifyOrderHandler)
http.ListenAndServe(":8080", nil)
}
```
### Gin Framework
```go
package main
import (
"net/http"
"os"
"github.com/gin-gonic/gin"
sendgo "github.com/send-go/go/sendgo"
)
func main() {
sg, _ := sendgo.New(sendgo.Config{
AccessKey: os.Getenv("SENDGO_ACCESS_KEY"),
SecretKey: os.Getenv("SENDGO_SECRET_KEY"),
KakaoSenderKey: os.Getenv("SENDGO_KAKAO_SENDER_KEY"),
ApiVersion: "v2",
})
r := gin.Default()
r.POST("/api/notify/order", func(c *gin.Context) {
var req struct {
Phone string `json:"phone"`
OrderNo string `json:"order_no"`
}
c.ShouldBindJSON(&req)
_, err := sg.Alimtalk.Send(sendgo.AlimtalkRequest{
TemplateCode: "ORDER_CONFIRM_001",
Contacts: []sendgo.Contact{{Contact: req.Phone, Var1: req.OrderNo}},
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true})
})
r.Run(":8080")
}
```
---
## 예외 처리
```go
import "github.com/send-go/go/sendgo"
_, err := client.Alimtalk.Send(req)
if err != nil {
var se *sendgo.SendgoError
if errors.As(err, &se) {
fmt.Printf("발송 실패: HTTP %d [%s]\n", se.StatusCode, se.ErrorCode)
switch se.ErrorCode {
case "INVALID_ACCESS_KEY", "INVALID_SECRET_KEY":
alertOps("Sendgo 인증키를 확인하세요.")
case "INVALID_TEMPLATE_CODE":
log.Printf("존재하지 않는 템플릿: %s", se.Message)
case "PAYMENT_REQUIRED":
alertOps("Sendgo 크레딧이 부족합니다.")
}
}
}
```
---
## 포인터 필드와 `sendgo.String`
`AlimtalkRequest` 의 `At`/`SmsSubject`/`SmsContent` 와 `SmsRequest` 의 `Subject` 는
"설정하지 않음"(JSON `null`)과 빈 문자열을 구분해야 하므로 `*string` 입니다.
Go에서는 리터럴의 주소를 얻을 수 없으므로(`&"..."` 는 컴파일 에러) 변수를 따로
선언하거나 `sendgo.String(...)` 헬퍼를 사용하세요.
```go
err := client.SMS.SendLMS(sendgo.SmsRequest{
Subject: sendgo.String("[중요] 서비스 점검 안내"),
Content: "...",
Contacts: []sendgo.Contact{{Contact: "01012345678"}},
})
```
## 설정 옵션
| 필드 | 타입 | 필수 | 기본값 | 설명 |
|------|------|------|--------|------|
| `AccessKey` | `string` | **필수** | — | Sendgo 액세스 키 |
| `SecretKey` | `string` | **필수** | — | Sendgo 시크릿 키 |
| `KakaoSenderKey` | `string` | 선택 | `""` | 카카오 발신프로필 키 |
| `SmsSenderKey` | `string` | 선택 | `""` | SMS 발신자 키 |
| `ApiVersion` | `string` | 선택 | `"v2"` | API 버전 (`v1` \| `v2`) |
| `BaseURL` | `string` | 선택 | `"https://sendgo.io"` | API 기본 URL |
---
## 짧은 URL
짧은 URL 은 메시지 본문의 링크를 줄이고, 그 링크가 실제로 눌렸는지 집계합니다.
문자는 바이트 수가 요금과 직결되므로 링크를 줄이면 그만큼 본문을 더 쓸 수 있습니다.
같은 원본 URL 을 다시 줄이면 **기존 링크가 그대로 반환**됩니다. 캠페인별로 반응을
따로 집계하려면 `forceNew` 로 새 코드를 만드세요.
`deactivate` 는 링크를 삭제하지 않고 리다이렉트만 중지합니다. 이미 발송한 메시지의
링크를 무효화할 때 쓰며, 누적 통계는 남고 이후 접속은 `410 Gone` 이 됩니다.
```go
// 짧은 URL 생성 (v2 전용)
created, err := client.ShortURL.Create(sendgo.ShortURLRequest{
TargetURL: "https://example.com/promotions/summer-sale",
Title: "여름 세일 랜딩",
})
if err != nil {
log.Fatal(err)
}
code := created["data"].(map[string]any)["code"].(string)
// 반응 통계 — 일별 추이 + 디바이스/유입경로/국가별 분해
stats, err := client.ShortURL.Stats(code, sendgo.ShortURLStatsQuery{From: "2026-08-01"})
client.ShortURL.List(sendgo.ShortURLListQuery{Count: 10})
client.ShortURL.Show(code)
client.ShortURL.Deactivate(code) // 리다이렉트만 중지, 통계는 남는다
```
`stats` 는 일별 추이(`daily`)와 디바이스(`byDevice`)·유입경로(`byReferer`)·국가(`byCountry`)별
분해를 반환합니다. 일별 추이는 사전 집계 표에서 읽으므로 클릭이 많아도 응답 시간이 일정합니다.
## 변경 사항
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
### 1.1.0 (2026-08-11)
- 짧은 URL 추가 — `client.ShortURL`
- **모듈 경로 수정** — `github.com/sendgo-dev/sendgo-go` → `github.com/send-go/go`. README·문서가 모두 후자를 쓰고 있어 `go get` 이 동작할 수 없었다.
- `sendgo.String()` 헬퍼 추가. `At`/`SmsSubject`/`SmsContent`/`Subject` 가 `*string` 이라 리터럴을 직접 넘길 수 없었다.
- `httpClient.delete()` 추가
## 라이선스
MIT License © 2026 [Sendgo](https://sendgo.io)
---
## 패키지 정보
- **패키지**: `github.com/send-go/go` (Go Modules)
- **저장소**: [send-go/go](https://github.com/send-go/go)
- **레지스트리**: https://pkg.go.dev/github.com/send-go/go
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **Java에서 카카오 알림톡, 브랜드메시지, SMS를 가장 쉽게 발송하는 순수 Java SDK**
`sendgo-java`는 [Sendgo](https://sendgo.io) 알림 API를 위한 **순수 Java 코어 SDK**입니다.
Spring, Quarkus 등 특정 프레임워크에 의존하지 않으며, `java.net.http.HttpClient`와 Jackson만 사용합니다.
Spring Boot 프로젝트라면 [`sendgo-spring`](https://github.com/send-go/spring) 패키지를 사용하세요.
---
## 설치
### Maven
```xml
io.sendgo
sendgo-java
1.1.0
```
### Gradle
```groovy
implementation 'io.sendgo:sendgo-java:1.1.0'
```
---
## 빠른 시작
```java
import io.sendgo.*;
import io.sendgo.model.*;
SendgoClient sendgo = new SendgoClient(SendgoConfig.builder()
.accessKey(System.getenv("SENDGO_ACCESS_KEY"))
.secretKey(System.getenv("SENDGO_SECRET_KEY"))
.kakaoSenderKey(System.getenv("SENDGO_KAKAO_SENDER_KEY"))
.smsSenderKey(System.getenv("SENDGO_SMS_SENDER_KEY"))
.apiVersion("v2")
.build());
// 알림톡 발송
sendgo.alimtalk().send(AlimtalkRequest.builder()
.templateCode("ORDER_CONFIRM_001")
.contacts(List.of(
Contact.builder()
.contact("01012345678")
.name("홍길동")
.var1("ORD-001")
.var2("29,000원")
.build()
))
.build());
```
---
## 알림톡 상세 사용법
```java
import io.sendgo.*;
import io.sendgo.model.*;
import java.util.List;
// 다건 발송
sendgo.alimtalk().send(AlimtalkRequest.builder()
.templateCode("ORDER_CONFIRM_001")
.contacts(List.of(
Contact.builder().contact("01011111111").name("홍길동").var1("ORD-001").var2("29,000원").build(),
Contact.builder().contact("01022222222").name("김철수").var1("ORD-002").var2("15,000원").build(),
Contact.builder().contact("01033333333").name("이영희").var1("ORD-003").var2("52,000원").build()
))
.build());
// 예약 발송
sendgo.alimtalk().send(AlimtalkRequest.builder()
.templateCode("PROMO_SUMMER_2026")
.scheduleType("SCHEDULED")
.at("2026-07-28 09:00:00")
.contacts(List.of(
Contact.builder().contact("01012345678").var1("여름 한정 50% 할인").build()
))
.build());
// SMS 자동 대체 발송
sendgo.alimtalk().send(AlimtalkRequest.builder()
.templateCode("DELIVERY_START_001")
.replaceSms("Y")
.smsSubject("[배송 시작 안내]")
.smsContent("주문하신 상품이 출고되었습니다.\n송장번호: #{var2}")
.contacts(List.of(
Contact.builder().contact("01012345678").var1("ORD-001").var2("1234567890").build()
))
.build());
```
---
## 친구톡 사용법
> ⚠️ **Deprecated — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었습니다.**
> 2026-01-01 부터 친구톡 발송 요청은 카카오 측에서 **브랜드메시지(자유형)** 로 자동 대체 발송됩니다.
> 호출은 계속 성공하며, 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보내는 경로는
> 현재 이것뿐이므로 기존 코드를 당장 바꿀 필요는 없습니다.
>
> 다음의 경우에는 **브랜드메시지**를 사용하세요.
> - 템플릿 기반 리치 타입 (`FL`/`FC`/`FM`/`FP`/`FA`)
> - 채널 친구가 **아닌** 수신자 (`targeting` = `N` / `I`)
> - 수신 동의한 전체 채널 친구 동보 (`targeting` = `F`)
>
> 메시지 타입은 1:1 대응되며 변환은 서버가 처리합니다 — `FT`→`BT`, `FI`→`BI`, `FW`→`BW`,
> `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`.
```java
// 텍스트형
sendgo.friendtalk().send(FriendtalkRequest.builder()
.content("안녕하세요! 7월 한정 특가 이벤트를 확인해보세요.")
.contacts(List.of(Contact.builder().contact("01012345678").build()))
.build());
// 이미지형
sendgo.friendtalk().send(FriendtalkRequest.builder()
.messageType("FI")
.content("이번 주 특가 상품을 확인하세요!")
.imageUrl("https://cdn.example.com/banner.jpg")
.imageLink("https://example.com/event")
.contacts(List.of(Contact.builder().contact("01012345678").build()))
.build());
```
---
## 브랜드메시지 사용법
브랜드메시지는 친구톡의 후속 채널입니다. 메시지 타입이 친구톡과 1:1 대응되며
(`FT`→`BT`, `FI`→`BI`, `FW`→`BW`, `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`),
요청에는 **친구톡 코드를 그대로** 넘기고 변환은 서버가 처리합니다.
친구톡과 달리 다음이 가능합니다.
- 채널 친구가 **아닌** 수신자에게 발송 (`targeting: N`)
- 수신 동의한 **전체 채널 친구 동보** 발송 (`targeting: F`, 수신자 목록 불필요)
- 리스트·캐러셀·커머스·동영상 등 **템플릿 기반 리치 메시지**
> v2 전용입니다. 자유 본문 타입(`FT`/`FI`/`FW`)을 개별 수신자에게 보낼 때는 여전히 친구톡 API 를 쓰세요 — 이 엔드포인트는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다. 친구톡 요청은 카카오 측에서 브랜드메시지(자유형)로 대체 발송됩니다.
```java
import io.sendgo.model.BrandMessageRequest;
import io.sendgo.model.Contact;
// 단건 발송 — 채널 친구 대상
sendgo.brandMessage().send(BrandMessageRequest.builder()
.targeting("M")
.messageType("FL")
.friendTemplateUuid("9cd5460b-6458-4edc-9b11-c26d3013c340")
.contact(Contact.builder().contact("01012345678").var1("29,000원").build())
.build());
// 동보 발송 — 수신 동의한 전체 채널 친구 (contacts 불필요)
sendgo.brandMessage().broadcast(BrandMessageRequest.builder()
.messageType("FW")
.friendTemplateUuid("9cd5460b-6458-4edc-9b11-c26d3013c340")
.build());
// 캠페인 조회
var list = sendgo.brandMessage().campaigns(null, null, 10);
var one = sendgo.brandMessage().campaign("1f0a6d0e-6b3b-4f0f-9b2f-2f6f6a1b7c11");
```
---
## SMS / LMS / MMS 사용법
```java
// SMS
sendgo.sms().sendSms(SmsRequest.sms()
.content("[Sendgo] 인증번호: 123456 (5분 이내 입력)")
.contact(Contact.builder().contact("01012345678").build()));
// LMS
sendgo.sms().sendLms(SmsRequest.lms()
.subject("[중요] 서비스 점검 안내")
.content("안녕하세요. 서비스 점검이 예정되어 있습니다.\n■ 일시: 2026-07-25 02:00 ~ 06:00")
.contact(Contact.builder().contact("01012345678").build()));
// MMS
sendgo.sms().sendMms(SmsRequest.mms()
.subject("[이벤트] 7월 특가")
.content("이번 달 특가 상품을 확인하세요!")
.contacts(List.of(
Contact.builder().contact("01011111111").build(),
Contact.builder().contact("01022222222").build()
)));
```
---
## 프레임워크 통합
### Quarkus
```java
// src/main/java/org/acme/SendgoProducer.java
import io.sendgo.*;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@ApplicationScoped
public class SendgoProducer {
@ConfigProperty(name = "sendgo.access-key")
String accessKey;
@ConfigProperty(name = "sendgo.secret-key")
String secretKey;
@ConfigProperty(name = "sendgo.kakao-sender-key")
String kakaoKey;
@Produces
@ApplicationScoped
public SendgoClient sendgoClient() {
return new SendgoClient(SendgoConfig.builder()
.accessKey(accessKey)
.secretKey(secretKey)
.kakaoSenderKey(kakaoKey)
.apiVersion("v2")
.build());
}
}
// 서비스에서 주입받아 사용
@ApplicationScoped
public class NotificationService {
@Inject SendgoClient sendgo;
public void sendOrderConfirm(String phone, String orderNo) {
sendgo.alimtalk().send(AlimtalkRequest.builder()
.templateCode("ORDER_CONFIRM_001")
.contact(Contact.builder().contact(phone).var1(orderNo).build())
.build());
}
}
```
### Micronaut
```java
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Value;
import io.sendgo.*;
import jakarta.inject.Singleton;
@Factory
public class SendgoFactory {
@Value("${sendgo.access-key}")
String accessKey;
@Value("${sendgo.secret-key}")
String secretKey;
@Singleton
public SendgoClient sendgoClient() {
return new SendgoClient(SendgoConfig.builder()
.accessKey(accessKey)
.secretKey(secretKey)
.apiVersion("v2")
.build());
}
}
```
### 순수 Java (싱글톤 패턴)
```java
public final class SendgoHolder {
private static volatile SendgoClient instance;
public static SendgoClient get() {
if (instance == null) {
synchronized (SendgoHolder.class) {
if (instance == null) {
instance = new SendgoClient(SendgoConfig.builder()
.accessKey(System.getenv("SENDGO_ACCESS_KEY"))
.secretKey(System.getenv("SENDGO_SECRET_KEY"))
.kakaoSenderKey(System.getenv("SENDGO_KAKAO_KEY"))
.apiVersion("v2")
.build());
}
}
}
return instance;
}
}
// 사용
SendgoHolder.get().alimtalk().send(AlimtalkRequest.builder()
.templateCode("ORDER_CONFIRM_001")
.contact(Contact.builder().contact("01012345678").var1("ORD-001").build())
.build());
```
---
## 예외 처리
```java
import io.sendgo.exception.SendgoException;
try {
sendgo.alimtalk().send(...);
} catch (SendgoException e) {
System.err.printf("발송 실패: HTTP %d [%s]%n", e.getStatusCode(), e.getErrorCode());
switch (e.getErrorCode()) {
case "INVALID_ACCESS_KEY",
"INVALID_SECRET_KEY" -> alertOps("Sendgo 인증키를 확인하세요.");
case "INVALID_TEMPLATE_CODE" -> log.warn("존재하지 않는 템플릿: {}", e.getMessage());
case "PAYMENT_REQUIRED" -> alertOps("Sendgo 크레딧이 부족합니다.");
case "IP_NOT_ALLOWED" -> alertOps("허용되지 않은 IP");
default -> log.error("알 수 없는 오류", e);
}
}
```
---
## 설정 옵션
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---------|------|------|--------|------|
| `accessKey` | `String` | **필수** | — | Sendgo 액세스 키 |
| `secretKey` | `String` | **필수** | — | Sendgo 시크릿 키 |
| `kakaoSenderKey` | `String` | 선택 | `null` | 카카오 발신프로필 키 |
| `smsSenderKey` | `String` | 선택 | `null` | SMS 발신자 키 |
| `apiVersion` | `String` | 선택 | `"v2"` | API 버전 (`v1` \| `v2`) |
| `baseUrl` | `String` | 선택 | `"https://sendgo.io"` | API 기본 URL |
---
## 1.1.0 변경 사항
- **`contact(...)` 가 누적된다.** 이전에는 `contacts = List.of(v)` 로 리스트를
통째로 교체했기 때문에 `.contact(a).contact(b)` 로 다건을 넣으면 `a` 가 조용히
사라졌다. 이제 호출할 때마다 추가된다. 한 번만 호출하던 기존 코드는 동작이 같다.
전체를 한 번에 지정하려면 여전히 `contacts(List.of(...))` 를 쓴다.
- **`sendSms` / `sendLms` / `sendMms` 가 messageType 을 강제한다.** 이전에는
세 메서드가 모두 요청을 그대로 넘겼기 때문에 `sendLms(SmsRequest.sms()...)` 가
SMS 로 발송됐다. 요청 자체의 타입을 그대로 쓰려면 `send(...)` 를 사용한다.
- **`Contact` 에 `var6` ~ `var8` 이 추가됐다.** 다른 언어 SDK(Node/Python/.NET/Go/Flutter)와
동일한 범위를 지원하도록 맞췄다. 그 이상은 `variable("name", "value")` 를 사용한다.
## 짧은 URL
짧은 URL 은 메시지 본문의 링크를 줄이고, 그 링크가 실제로 눌렸는지 집계합니다.
문자는 바이트 수가 요금과 직결되므로 링크를 줄이면 그만큼 본문을 더 쓸 수 있습니다.
같은 원본 URL 을 다시 줄이면 **기존 링크가 그대로 반환**됩니다. 캠페인별로 반응을
따로 집계하려면 `forceNew` 로 새 코드를 만드세요.
`deactivate` 는 링크를 삭제하지 않고 리다이렉트만 중지합니다. 이미 발송한 메시지의
링크를 무효화할 때 쓰며, 누적 통계는 남고 이후 접속은 `410 Gone` 이 됩니다.
```java
// 짧은 URL 생성 (v2 전용)
Map created = sendgo.shortUrl().create(ShortUrlRequest.builder()
.targetUrl("https://example.com/promotions/summer-sale")
.title("여름 세일 랜딩")
.build());
@SuppressWarnings("unchecked")
Map data = (Map) created.get("data");
String code = (String) data.get("code");
// 반응 통계 — 일별 추이 + 디바이스/유입경로/국가별 분해
Map stats = sendgo.shortUrl().stats(code, "2026-08-01", null);
sendgo.shortUrl().list(null, null, 10);
sendgo.shortUrl().show(code);
sendgo.shortUrl().deactivate(code); // 리다이렉트만 중지, 통계는 남는다
```
`stats` 는 일별 추이(`daily`)와 디바이스(`byDevice`)·유입경로(`byReferer`)·국가(`byCountry`)별
분해를 반환합니다. 일별 추이는 사전 집계 표에서 읽으므로 클릭이 많아도 응답 시간이 일정합니다.
## 변경 사항
### 1.2.1 (2026-08-14)
- 레지스트리 목록에 노출되는 패키지 설명에서 친구톡을 브랜드메시지로 교체했습니다.
npm/PyPI/Packagist/Maven/NuGet/RubyGems 검색 결과에 그대로 찍히는 문자열이라
종료된 채널을 계속 홍보하고 있었습니다.
- 검색 키워드에 `brand-message` 를 추가했습니다 (`friendtalk` 은 유입 검색어라 유지).
### 1.2.0 (2026-08-14)
- **친구톡 Deprecated 표기** — 친구톡은 카카오 정책에 따라 2025-12-31 종료되었고,
2026-01-01 부터 발송 요청이 브랜드메시지(자유형)로 자동 대체 발송됩니다.
관련 API 에 각 언어의 표준 deprecation 표기를 달았습니다.
- 자유 본문 타입(`FT`/`FI`/`FW`)의 개별 발송 경로는 아직 친구톡 API 뿐이라는 점을
문서에 명시했습니다 — 브랜드메시지 API 는 그 조합에 `NOT_A_BRAND_MESSAGE` 를 반환합니다.
- 브랜드메시지 전환 안내와 메시지 타입 1:1 대응표를 README 에 추가했습니다.
## 라이선스
MIT License © 2026 [Sendgo](https://sendgo.io)
---
## 패키지 정보
- **패키지**: `io.sendgo:sendgo-java` (Maven Central)
- **저장소**: [send-go/java](https://github.com/send-go/java)
- **레지스트리**: https://central.sonatype.com/artifact/io.sendgo/sendgo-java
- **라이선스**: MIT
### API 키 발급 방법
샌드고에 로그인한 뒤 **연동하기 → 연동 정보** 메뉴에서 액세스 키와 시크릿 키를 발급받고, 같은 화면에서 호출을 허용할 IP 를 등록합니다 — 등록되지 않은 주소에서 온 요청은 거부됩니다. 알림톡·브랜드메시지를 사용하려면 **발신프로필 관리** 메뉴에서 발신프로필을 등록해 `kakao_sender_key`를 발급받고, 문자를 사용하려면 **발신번호 관리** 메뉴에서 발신번호를 등록해야 합니다.
---
> **Spring Boot에서 카카오 알림톡, 브랜드메시지, SMS를 가장 쉽게 발송하는 공식 Spring Boot Starter**
`sendgo-spring`은 [`sendgo-java`](https://github.com/send-go/java) 코어를 확장한 **Spring Boot 전용 스타터**입니다.
`application.yml` 설정만으로 `SendgoClient` 빈이 자동 등록됩니다.
---
## 설치
### Maven
```xml
io.sendgo
sendgo-spring
1.0.0
```
### Gradle
```groovy
implementation 'io.sendgo:sendgo-spring:1.0.0'
```
---
## 빠른 시작 (3단계)
### 1단계 — application.yml 설정
```yaml
sendgo:
access-key: ${SENDGO_ACCESS_KEY}
secret-key: ${SENDGO_SECRET_KEY}
kakao-sender-key: ${SENDGO_KAKAO_SENDER_KEY}
sms-sender-key: ${SENDGO_SMS_SENDER_KEY}
api-version: v2
```
### 2단계 — 서비스에서 SendgoClient 주입
```java
@Service
public class NotificationService {
private final SendgoClient sendgo;
public NotificationService(SendgoClient sendgo) {
this.sendgo = sendgo;
}
public void sendOrderConfirm(String phone, String orderNo, String amount) {
sendgo.alimtalk().send(AlimtalkRequest.builder()
.templateCode("ORDER_CONFIRM_001")
.contacts(List.of(
Contact.builder()
.contact(phone)
.var1(orderNo)
.var2(amount)
.build()
))
.build());
}
}
```
### 3단계 — 컨트롤러에서 사용
```java
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final NotificationService notificationService;
public OrderController(NotificationService notificationService) {
this.notificationService = notificationService;
}
@PostMapping("/{id}/confirm")
public ResponseEntity