> **The Python SDK for sending Kakao Alimtalk, Brand Message and SMS**

`sendgo-python` is the official Python SDK for the [Sendgo](https://sendgo.io) messaging API.
It works on its own and is the core behind the `sendgo-django` and `sendgo-fastapi` extensions.

Requires Python 3.10 or newer.

---

## Install

```bash
pip install sendgo-python
```

---

## Quick start

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

# Send an Alimtalk
client.alimtalk.send(
    template_code="ORDER_CONFIRM_001",
    contacts=[
        {"contact": "01012345678", "name": "Gildong Hong", "var1": "ORD-001", "var2": "29,000 KRW"},
    ],
)

# Send an SMS
client.sms.send_sms(
    content="[Sendgo] Your code is 123456 (valid for 5 minutes)",
    contacts=[{"contact": "01012345678"}],
)
```

All methods take keyword arguments only, so call sites stay readable as payloads grow.
Tokens are issued and refreshed for you.

---

## Alimtalk in detail

```python
# Multiple recipients
client.alimtalk.send(
    template_code="ORDER_CONFIRM_001",
    contacts=[
        {"contact": "01011111111", "name": "Gildong Hong", "var1": "ORD-001", "var2": "29,000 KRW"},
        {"contact": "01022222222", "name": "Chulsoo Kim", "var1": "ORD-002", "var2": "15,000 KRW"},
    ],
)

# Scheduled send
client.alimtalk.send(
    template_code="PROMO_SUMMER_2026",
    schedule_type="SCHEDULED",
    at="2026-07-28 09:00:00",
    contacts=[{"contact": "01012345678", "var1": "Summer sale — 50% off"}],
)

# Fall back to SMS when the Alimtalk fails
client.alimtalk.send(
    template_code="DELIVERY_START_001",
    replace_sms="Y",
    sms_subject="[Shipping notice]",
    sms_content="Your order has shipped.\nTracking: #{var2}",
    contacts=[{"contact": "01012345678", "var1": "ORD-001", "var2": "1234567890"}],
)
```

---

## Friendtalk

> ⚠️ **Deprecated — Friendtalk was discontinued on 2025-12-31 under Kakao's policy.**
> Since 2026-01-01, Friendtalk send requests are automatically delivered as
> **Brand Message (free-form)** by Kakao. Calls still succeed, and this is still the
> only path for free-form types (`FT`/`FI`/`FW`) sent to individual recipients, so
> there is no need to change working code right now.
>
> Use **Brand Message** instead for:
> - template-based rich types (`FL`/`FC`/`FM`/`FP`/`FA`)
> - recipients who are **not** channel friends (`targeting` = `N` / `I`)
> - broadcasts to every opted-in channel friend (`targeting` = `F`)
>
> Message types map one-to-one and the server does the conversion — `FT`→`BT`,
> `FI`→`BI`, `FW`→`BW`, `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`.
```python
# Text
client.friendtalk.send(
    content="Hello! Check out this month's deals.",
    contacts=[{"contact": "01012345678"}],
)

# Image
client.friendtalk.send(
    message_type="FI",
    content="This week's featured products.",
    image_url="https://cdn.example.com/banner.jpg",
    image_link="https://example.com/event",
    contacts=[{"contact": "01012345678"}],
)

# With buttons
client.friendtalk.send(
    content="Your coupon has arrived. Use it now!",
    buttons=[{"name": "Get coupon", "type": "WL", "linkMo": "https://example.com/coupon"}],
    contacts=[{"contact": "01012345678"}],
)
```

---

## Brand Message

Brand Message is the successor channel to Friendtalk. Message types map one-to-one
(`FT`→`BT`, `FI`→`BI`, `FW`→`BW`, `FL`→`BL`, `FC`→`BC`, `FM`→`BM`, `FP`→`BP`, `FA`→`BA`);
pass the **Friendtalk code** and the server converts it.

Unlike Friendtalk it can also:

- reach recipients who are **not channel friends** (`targeting="N"`)
- **broadcast to every consenting channel friend** (`targeting="F"`, no recipient list needed)
- send **template-based rich messages** — lists, carousels, commerce, video

> v2 only. For plain text or images (`FT`/`FI`/`FW`) to channel friends, the Friendtalk API is simpler.

```python
# Single send — channel friends
client.brand_message.send(
    targeting="M",
    message_type="FL",
    friend_template_uuid="9cd5460b-6458-4edc-9b11-c26d3013c340",
    contacts=[{"contact": "01012345678", "var1": "29,000 KRW"}],
)

# Broadcast — every consenting channel friend (no contacts)
client.brand_message.broadcast(
    message_type="FW",
    friend_template_uuid="9cd5460b-6458-4edc-9b11-c26d3013c340",
)

# Campaign lookups. `from` is a Python keyword, so the argument is `from_`.
campaigns = client.brand_message.campaigns(from_="2026-08-01", count=10)
one = client.brand_message.campaign("1f0a6d0e-6b3b-4f0f-9b2f-2f6f6a1b7c11")
```

Two argument names avoid Python keyword collisions: `from_` (for `from`) and `list_` (for the carousel `list` payload). Both are sent under their original API names.

---

## SMS / LMS / MMS

```python
# SMS (up to 90 bytes)
client.sms.send_sms(
    content="[Sendgo] Your code is 123456 (valid for 5 minutes)",
    contacts=[{"contact": "01012345678"}],
)

# LMS (long text, up to 2,000 bytes)
client.sms.send_lms(
    subject="[Important] Scheduled maintenance",
    content="Maintenance is scheduled.\n\n■ When: 2026-07-25 02:00–06:00\n■ Impact: all services",
    contacts=[{"contact": "01012345678"}],
)

# MMS (with an image)
client.sms.send_mms(
    subject="[Event] July deals",
    content="Check out this month's deals!",
    contacts=[{"contact": "01012345678"}],
)

# Scheduled SMS
client.sms.send_sms(
    content="[Reminder] Please confirm your appointment.",
    schedule_type="SCHEDULED",
    at="2026-07-23 08:00:00",
    contacts=[{"contact": "01012345678"}],
)
```

---

## Framework integration

### Django

Use `sendgo-django`, which reads a `SENDGO` settings dict and gives you a lazily built client:

```python
# settings.py
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"),
    "API_VERSION": "v2",
}
```

```python
from sendgo_django import client

client.alimtalk.send(
    template_code="ORDER_CONFIRM_001",
    contacts=[{"contact": order.phone, "var1": order.number}],
)
```

### FastAPI

Use `sendgo-fastapi`, which exposes the client as a dependency:

```python
from fastapi import Depends, FastAPI
from sendgo_fastapi import SendgoDep

app = FastAPI()

@app.post("/notify")
async def notify(phone: str, order_no: str, sendgo = Depends(SendgoDep)):
    return sendgo.alimtalk.send(
        template_code="ORDER_CONFIRM_001",
        contacts=[{"contact": phone, "var1": order_no}],
    )
```

### Celery

Sending is a network call, so it belongs in a task rather than a request cycle:

```python
from celery import shared_task
from sendgo import Sendgo, SendgoError

@shared_task(bind=True, max_retries=3)
def send_order_confirm(self, phone: str, order_no: str):
    try:
        Sendgo(...).alimtalk.send(
            template_code="ORDER_CONFIRM_001",
            contacts=[{"contact": phone, "var1": order_no}],
        )
    except SendgoError as exc:
        # 5xx is transient; 4xx will not succeed on retry.
        if exc.status_code >= 500:
            raise self.retry(exc=exc, countdown=2 ** self.request.retries)
        raise
```

---

## Error handling

```python
from sendgo import SendgoError

try:
    client.alimtalk.send(
        template_code="ORDER_CONFIRM_001",
        contacts=[{"contact": "01012345678"}],
    )
except SendgoError as e:
    if e.error_code in ("INVALID_ACCESS_KEY", "INVALID_SECRET_KEY"):
        notify_ops("Check the Sendgo API keys.")
    elif e.error_code == "PAYMENT_REQUIRED":
        notify_ops("Out of Sendgo credit.")
    elif e.error_code == "IP_NOT_ALLOWED":
        notify_ops("IP is not allow-listed.")
    elif e.status_code >= 500:
        retry_later()          # retryable
    else:
        logger.error("Sendgo error %s: %s", e.status_code, e)
```

Branch on `error_code`, not on the message text — messages can change, codes are the contract.
`TOKEN_EXPIRED` and `TOKEN_MISMATCH` are handled inside the SDK: the token is reissued and the request retried once.

---

## Configuration options

| Argument | Type | Required | Default | Description |
|----------|------|----------|---------|-------------|
| `access_key` | `str` | **required** | — | Sendgo access key |
| `secret_key` | `str` | **required** | — | Sendgo secret key |
| `kakao_sender_key` | `str \| None` | optional | `None` | Kakao sender profile key |
| `sms_sender_key` | `str \| None` | optional | `None` | SMS caller ID key |
| `api_version` | `str` | optional | `"v1"` | API version (`v1` \| `v2`) |
| `base_url` | `str` | optional | `"https://sendgo.io"` | API base URL |

---

## Short URL

Short URLs shrink the links in your message body and count whether they were
actually clicked. SMS is billed by byte, so a shorter link leaves more room for copy.

> v2 only.

Shortening the same target URL again **returns the existing link**. Pass `forceNew`
to mint a new code when you want per-campaign reaction figures kept separate.

`deactivate` does not delete the link — it only stops the redirect. Use it when a link
in an already-sent message has to be killed; the accumulated stats stay, and visitors
to a stopped link get `410 Gone`.

```python
created = sendgo.short_url.create(
    target_url="https://example.com/promotions/summer-sale",
    title="Summer sale landing",
)

code = created["data"]["code"]
link = created["data"]["shortUrl"]

# Reaction stats — daily series + device / referrer / country breakdowns
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)   # Stops the redirect only; the stats stay
```

`stats` returns a daily series (`daily`) plus breakdowns by device (`byDevice`), referrer (`byReferer`) and country (`byCountry`). The daily series is read from a pre-aggregated table, so response time stays flat no matter how many clicks accumulate.

---

## Package information

- **Package**: `sendgo-python` (PyPI)
- **Repository**: [send-go/python](https://github.com/send-go/python)
- **Registry**: https://pypi.org/project/sendgo-python/
- **License**: MIT

### Getting your API keys

Sign in to Sendgo and open **API/SDK → API integration** to issue an access key and secret key.
Register a Kakao sender profile under **Kakao channel** to get your `kakao_sender_key`, and a caller ID under **Sender numbers** for SMS.