카카오 알림톡을 처음 보내기까지 필요한 건 다섯 단계입니다. 그중 코드는 마지막 하나뿐이고, 앞의 넷은 **한 번만 하면 되는 계정 설정**입니다.

> 급하다면: 계정 설정(1~3단계)이 이미 끝나 있으면 [5단계](#5단계-알림톡-발송)로 바로 가세요.

## 1단계 — 액세스 키와 시크릿 키 발급

[샌드고 콘솔](https://sendgo.io)에 로그인한 뒤 **연동 관리 → 앱**에서 앱을 만들면 `accessKey` 와 `secretKey` 가 발급됩니다.

이 두 값은 계정의 발송 권한 전체를 가집니다. 환경변수에 넣으세요.

```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
```

앱에 **허용 IP** 를 걸어두면 키가 유출돼도 다른 곳에서는 쓸 수 없습니다. 서버 IP 가 고정이라면 걸어두는 편이 좋습니다.

## 2단계 — 발신번호와 카카오 발신프로필 등록

한국에서는 **전기통신사업법**상 사전등록된 번호로만 문자를 보낼 수 있습니다. 등록되지 않은 번호는 가입할 때가 아니라 **발송할 때** 실패하므로, 코드를 쓰기 전에 끝내야 합니다.

- **발신번호(SMS)**: 콘솔 → 발신번호에서 통신서비스 이용증명원 등을 제출해 등록합니다. 승인까지 영업일 기준 시간이 걸립니다.
- **카카오 발신프로필**: 카카오톡 채널을 샌드고에 연결하면 `kakaoSenderKey` 가 발급됩니다. 채널은 카카오 비즈니스에서 **비즈니스 채널**로 전환돼 있어야 합니다.

자세한 절차는 [발신번호 사전등록](/ko/cookbook/sender-number)에 정리돼 있습니다.

## 3단계 — 알림톡 템플릿 등록과 승인

알림톡의 본문은 **미리 승인받은 템플릿**입니다. 발송할 때 하는 일은 템플릿의 변수를 채우는 것뿐입니다.

```text
[#{var2}] 주문이 확인되었습니다.

주문번호: #{var1}
결제금액: #{var3}
```

이런 템플릿을 등록해 `ORDER_CONFIRM_001` 같은 **템플릿 코드**를 받으면 준비가 끝납니다. 심사에서 자주 반려되는 이유와 통과 요령은 [알림톡 템플릿 등록과 심사 통과](/ko/cookbook/alimtalk-template)에 있습니다.

> 광고성 문구는 알림톡 템플릿으로 승인되지 않습니다. 정보성만 가능합니다. 홍보 메시지는 [브랜드메시지](/ko/cookbook/send-brand-message)를 쓰세요.

## 4단계 — SDK 설치

쓰고 있는 언어의 공식 패키지를 설치합니다. 프레임워크 패키지(Laravel, Spring, NestJS 등)는 코어를 자동으로 끌어오므로 **둘 다 설치하지 마세요**.

```bash
composer require sendgo/laravel      # Laravel
composer require sendgo/php          # 순수 PHP
npm install @sendgo/node             # Node.js / TypeScript
pip install sendgo-python            # Python
go get github.com/send-go/go         # Go
gem install sendgo                   # Ruby
dotnet add package Sendgo.SDK        # .NET
```

전체 목록과 선택 기준은 [내 언어에 맞는 SDK 고르기](/ko/cookbook/choose-sdk)에 있습니다.

## 5단계 — 알림톡 발송

클라이언트를 한 번 만들어 두고 재사용합니다. 발신 키는 **클라이언트 생성 시점에** 넣어두면 발송할 때마다 다시 쓰지 않아도 됩니다.

### Node.js / TypeScript

```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',
});

await sendgo.alimtalk.send({
  templateCode: 'ORDER_CONFIRM_001',
  contacts: [
    { contact: '01012345678', name: '홍길동', var1: 'ORD-001', var2: '주문', var3: '29,000원' },
  ],
});
```

`@sendgo/node` 는 **기본 내보내기(default export)** 입니다. `import { Sendgo } from '@sendgo/node'` 는 동작하지 않습니다.

### Python

```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",
)

client.alimtalk.send(
    template_code="ORDER_CONFIRM_001",
    contacts=[
        {"contact": "01012345678", "name": "홍길동", "var1": "ORD-001", "var3": "29,000원"}
    ],
)
```

### PHP

```php
<?php

use Sendgo\Php\Sendgo;

$sendgo = new 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'      => 'v2',
]);

$sendgo->alimtalk->send([
    'templateCode' => 'ORDER_CONFIRM_001',
    'contacts'     => [
        ['contact' => '01012345678', 'name' => '홍길동', 'var1' => 'ORD-001', 'var3' => '29,000원'],
    ],
]);
```

### Laravel

`sendgo/laravel` 은 ServiceProvider 를 자동 등록하므로 컨테이너에서 바로 주입받습니다.

```php
<?php

use Sendgo\Php\Sendgo;

class OrderController extends Controller
{
    public function __construct(private Sendgo $sendgo) {}

    public function confirm(Order $order)
    {
        $this->sendgo->alimtalk->send([
            'templateCode' => 'ORDER_CONFIRM_001',
            'contacts'     => [[
                'contact' => $order->user->phone,
                'name'    => $order->user->name,
                'var1'    => $order->number,
                'var3'    => number_format($order->total).'원',
            ]],
        ]);

        return response()->json(['success' => true]);
    }
}
```

### Java

```java
import io.sendgo.*;
import io.sendgo.model.*;
import java.util.List;

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").build()
    ))
    .build());
```

### Go

```go
package main

import (
    "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)
    }

    _, err = client.Alimtalk.Send(sendgo.AlimtalkRequest{
        TemplateCode: "ORDER_CONFIRM_001",
        Contacts: []sendgo.Contact{
            {Contact: "01012345678", Name: "홍길동", Var1: "ORD-001"},
        },
    })
    if err != nil {
        log.Fatal(err)
    }
}
```

## 첫 발송이 실패했다면

| 오류 코드 | 뜻 | 할 일 |
| --- | --- | --- |
| `INVALID_ACCESS_KEY` | 액세스 키가 틀렸다 | 환경변수가 실제로 로드됐는지 확인 |
| `ACCESS_KEY_NOT_APPROVED` | 앱이 아직 승인되지 않았다 | 콘솔에서 앱 승인 상태 확인 |
| `IP_NOT_ALLOWED` | 허용 IP 밖에서 호출했다 | 앱의 허용 IP 목록에 서버 IP 추가 |
| `INVALID_TEMPLATE_CODE` | 없는 템플릿 코드 | 콘솔의 템플릿 코드와 대조 (승인 완료 상태여야 함) |
| `INVALID_KAKAO_SENDER_KEY` | 카카오 발신프로필 키가 틀렸다 | 콘솔 → 카카오 발신프로필에서 키 재확인 |
| `EMPTY_CONTACTS` | 수신자가 비었다 | `contacts` 배열이 실제로 채워졌는지 확인 |
| `PAYMENT_REQUIRED` | 크레딧 부족 | 충전 후 재시도 (재시도만으로는 해결되지 않음) |

전체 목록과 재시도 전략은 [오류 코드와 재시도 전략](/ko/cookbook/error-handling)에 있습니다.

## 다음 단계

- 수신자마다 다른 값을 넣어 한 번에 보내기 → [대량 발송과 치환 변수](/ko/cookbook/bulk-send)
- 알림톡이 실패하면 문자로 대신 보내기 → [SMS 대체 발송](/ko/cookbook/sms-fallback)
- 정해진 시각에 보내기 → [예약 발송](/ko/cookbook/scheduled-send)
- 템플릿 없이 자유 본문 보내기 → [SMS·LMS·MMS 보내기](/ko/cookbook/send-sms)