Documentation menu
Python / SDK REFERENCE
FastAPI SDK Guide
The official FastAPI extension for sending Kakao Alimtalk, Brand Message and SMS — injected as a dependency, configured from environment variables.
이 문서의 목차
- 패키지
- sendgo-fastapi
- 언어
- Python
- 레지스트리
- PyPI
pip install sendgo-fastapiThe official FastAPI extension for sending Kakao Alimtalk, Brand Message and SMS
sendgo-fastapi wraps the sendgo-python core as a FastAPI dependency, configured through a pydantic-settings model.
Requires FastAPI 0.110+ and Python 3.10+.
Install
pip install sendgo-fastapi
The core sendgo-python comes along as a dependency.
Configure
SendgoSettings reads these environment variables, so there is nothing to wire up in code:
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
access_key and secret_key have no defaults, so pydantic raises a validation error at startup if either is missing — a misconfigured deploy fails fast instead of at the first send.
Quick start
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}],
)
The dependency yields the core client, so every channel is available:
| Attribute | Channel |
|---|---|
sendgo.alimtalk |
Kakao Alimtalk |
sendgo.friendtalk |
Kakao Friendtalk |
sendgo.brand_message |
Kakao Brand Message (v2 only) |
sendgo.sms |
SMS / LMS / MMS |
Annotated form
With Annotated the dependency reads as part of the type, which keeps long signatures tidy:
from typing import Annotated
from fastapi import Depends
from sendgo import Sendgo
from sendgo_fastapi import SendgoDep
SendgoClient = Annotated[Sendgo, Depends(SendgoDep)]
@app.post("/notify")
async def notify(phone: str, order_no: str, sendgo: SendgoClient):
return sendgo.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": phone, "var1": order_no}],
)
Brand Message
Friendtalk was discontinued on 2025-12-31. Since 2026-01-01 Kakao delivers Friendtalk requests as Brand Message (free-form) automatically.
friendtalkstill works and is still the only path for free-formFT/FI/FWto individual recipients — use Brand Message for template-based rich types (FL/FC/FM/FP/FA), non-friend targeting (N/I) and broadcasts (F). Brand Message is the successor channel to Friendtalk: it reaches recipients who are not channel friends (targeting="N") and can broadcast to every consenting channel friend (targeting="F"). Message types map one-to-one with Friendtalk (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.
v2 only. Set
SENDGO_API_VERSION=v2.
@app.post("/campaigns/targeted")
async def targeted(phone: str, sendgo: SendgoClient):
return sendgo.brand_message.send(
targeting="M",
message_type="FL",
friend_template_uuid="9cd5460b-6458-4edc-9b11-c26d3013c340",
contacts=[{"contact": phone, "var1": "29,000 KRW"}],
)
@app.post("/campaigns/broadcast")
async def broadcast(sendgo: SendgoClient):
# No recipient list — Kakao expands the audience
return sendgo.brand_message.broadcast(
message_type="FW",
friend_template_uuid="9cd5460b-6458-4edc-9b11-c26d3013c340",
)
@app.get("/campaigns")
async def campaigns(sendgo: SendgoClient, count: int = 10):
# `from` is a Python keyword, so the argument is `from_`.
return sendgo.brand_message.campaigns(count=count)
@app.get("/campaigns/{campaign_id}")
async def campaign(campaign_id: str, sendgo: SendgoClient):
return sendgo.brand_message.campaign(campaign_id)
A broadcast is processed asynchronously upstream, so the send response only acknowledges acceptance — poll GET /campaigns/{campaign_id} for progress.
Background tasks
The core client is synchronous, so calling it directly inside an async def endpoint blocks the event loop. Move sending to a background task:
from fastapi import BackgroundTasks, Depends, FastAPI
from sendgo import Sendgo, SendgoError
from sendgo_fastapi import SendgoDep
def deliver(sendgo: Sendgo, phone: str, order_no: str) -> None:
try:
sendgo.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": phone, "var1": order_no}],
)
except SendgoError as e:
logger.error("Sendgo %s [%s]: %s", e.status_code, e.error_code, e)
@app.post("/orders", status_code=202)
async def create_order(
phone: str,
order_no: str,
background: BackgroundTasks,
sendgo = Depends(SendgoDep),
):
background.add_task(deliver, sendgo, phone, order_no)
return {"accepted": True}
For anything that must survive a restart, use a real queue (Celery, ARQ, Dramatiq) rather than BackgroundTasks.
Running in a thread pool
If you need the result inside the request, keep the event loop free with run_in_threadpool:
from starlette.concurrency import run_in_threadpool
@app.post("/notify")
async def notify(phone: str, sendgo: SendgoClient):
return await run_in_threadpool(
sendgo.alimtalk.send,
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": phone}],
)
Testing
Override the dependency so no request leaves your test suite:
from unittest.mock import MagicMock
from fastapi.testclient import TestClient
from sendgo_fastapi import SendgoDep
fake = MagicMock()
app.dependency_overrides[SendgoDep] = lambda: fake
client = TestClient(app)
client.post("/notify", params={"phone": "01012345678", "order_no": "ORD-001"})
fake.alimtalk.send.assert_called_once()
app.dependency_overrides.clear()
Error handling
Map Sendgo errors onto HTTP status codes rather than letting them surface as 500s:
from fastapi import HTTPException
from sendgo import SendgoError
@app.post("/notify")
async def notify(phone: str, sendgo: SendgoClient):
try:
return sendgo.alimtalk.send(
template_code="ORDER_CONFIRM_001",
contacts=[{"contact": phone}],
)
except SendgoError as e:
if e.error_code in ("INVALID_ACCESS_KEY", "INVALID_SECRET_KEY", "IP_NOT_ALLOWED"):
# Our configuration is wrong, not the caller's request.
logger.critical("Sendgo configuration error: %s", e.error_code)
raise HTTPException(status_code=500, detail="Messaging unavailable") from e
if e.error_code == "PAYMENT_REQUIRED":
raise HTTPException(status_code=402, detail="Out of credit") from e
if e.status_code >= 500:
raise HTTPException(status_code=502, detail="Upstream error") from e
raise HTTPException(status_code=400, detail=e.error_code) from 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 core SDK: the token is reissued and the request retried once.
Settings reference
| Environment variable | Field | Required | Default | Description |
|---|---|---|---|---|
SENDGO_ACCESS_KEY |
access_key |
required | — | Sendgo access key |
SENDGO_SECRET_KEY |
secret_key |
required | — | Sendgo secret key |
SENDGO_KAKAO_SENDER_KEY |
kakao_sender_key |
optional | None |
Kakao sender profile key |
SENDGO_SMS_SENDER_KEY |
sms_sender_key |
optional | None |
SMS caller ID key |
SENDGO_API_VERSION |
api_version |
optional | "v1" |
API version (v1 | v2) |
SENDGO_BASE_URL |
base_url |
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.
@app.post("/shorten")
async def shorten(target_url: str, sendgo: SendgoClient):
created = sendgo.short_url.create(target_url=target_url)
return {"shortUrl": created["data"]["shortUrl"]}
@app.get("/shorten/{code}/stats")
async def link_stats(code: str, sendgo: SendgoClient):
return sendgo.short_url.stats(code)
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-fastapi(PyPI) - Repository: send-go/fastapi
- Registry: https://pypi.org/project/sendgo-fastapi/
- 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 SENDGO_KAKAO_SENDER_KEY, and a caller ID under Sender numbers for SMS.
이 패키지로 할 수 있는 것
Send your first Kakao Alimtalk in 5 minutes →
From issuing an access key to sending a Kakao Alimtalk, with working code in Node.js, Python, PHP, Laravel, Java and Go.
Finish the Sendgo integration from your AI agent — MCP server and Account API →
Pick the organisation, issue API keys, register sender numbers and templates from a coding agent. Everything except topping up credit works without the console.
Sendgo API authentication — access keys and bearer tokens →
Exchange an accessKey and secretKey for a bearer token, and call the Sendgo API with it. Differences between v1 and v2, token caching, and the 401/403 codes.
Send a Kakao Alimtalk — PHP, Node.js, Python, Java, Go examples →
Send Kakao Alimtalk with an approved template code. Runnable code for Laravel, Node.js, Python, PHP, Java, Go, Ruby and .NET, plus every required field and why sends fail.
Send SMS, LMS and MMS in South Korea — code examples →
Send Korean SMS (90 bytes), LMS (long text) and MMS (with images) through Sendgo. Type selection, byte counting, verification-code patterns and advertising rules.
Send a Kakao Brand Message — the successor to Friendtalk →
Send Brand Messages to channel friends, non-friends, or every consenting friend at once. How targeting splits the request path, and when to keep using the Friendtalk endpoint.
SMS fallback when a Kakao Alimtalk fails →
Use replaceSms so a text message goes out when the Alimtalk cannot be delivered. Required fields, cost implications, and the mistake that silently sends nothing.
Bulk Alimtalk sending and per-recipient variables →
Send one template to many recipients with different values each, in batches. Batch sizing, partial failures, queue patterns and the data hygiene that prevents most incidents.
Scheduling an Alimtalk or SMS — scheduleType and at →
Send at a specific time with scheduleType SCHEDULED. Timestamp format, the KST timezone trap, and how scheduling interacts with the advertising night ban.
Doing everything over the API — operations automation for resellers →
Register Kakao channels, submit Alimtalk templates for review, file sender numbers, and sync opt-outs without ever sending anyone to sendgo.io. Includes webhook delivery of review outcomes.
Sendgo API error codes and retry strategy →
Every error code returned by the Sendgo send endpoints, and how to tell a failure worth retrying from one that will fail identically every time.
Short links in SMS and Alimtalk, with click tracking →
Shorten long URLs to fit inside an SMS and measure who clicked. Creating short links, reading click stats, and splitting stats per campaign.
관련 패키지
Python →
The Python SDK for sending Kakao Alimtalk, Brand Message and SMS — works standalone or with Django and FastAPI.
pip install sendgo-pythonDjango →
The official Django extension for sending Kakao Alimtalk, Brand Message and SMS — configured from settings, with a lazily built client.
pip install sendgo-django