文档菜单
Java / SDK REFERENCE
Spring Boot SDK 指南
用于发送 Kakao 通知消息、好友消息、品牌消息与 SMS 的官方 Spring Boot Starter —— 由配置属性自动装配客户端 Bean。
이 문서의 목차
- 패키지
- io.sendgo:sendgo-spring
- 언어
- Java
- 레지스트리
- Maven Central
implementation "io.sendgo:sendgo-spring:1.0.1"用于发送 Kakao 通知消息、品牌消息与短信的官方 Spring Boot Starter
io.sendgo:sendgo-spring 把 io.sendgo:sendgo-java 核心封装为 Spring Boot Starter:设置 sendgo.access-key 即可自动装配出一个 SendgoClient Bean。
要求 Spring Boot 3.x 与 Java 17+。
安装
Gradle
implementation "io.sendgo:sendgo-spring:1.0.1"
Maven
<dependency>
<groupId>io.sendgo</groupId>
<artifactId>sendgo-spring</artifactId>
<version>1.0.1</version>
</dependency>
核心 sendgo-java 会作为传递依赖一并引入。
配置
# application.yml
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
或使用 properties 形式:
sendgo.access-key=${SENDGO_ACCESS_KEY}
sendgo.secret-key=${SENDGO_SECRET_KEY}
sendgo.kakao-sender-key=${SENDGO_KAKAO_SENDER_KEY}
sendgo.api-version=v2
Starter 附带配置元数据,因此 IDE 能对这些键做自动补全。
自动装配以 sendgo.access-key 存在为条件 —— 未设置时应用仍能正常启动,这让不发送消息的本地 profile 不受影响。
Starter 与纯 sendgo-java 核心的 api-version 默认值都是 v2,因此只有需要固定使用 v1 的旧对接才要显式指定。
注入客户端
import io.sendgo.SendgoClient;
import io.sendgo.model.AlimtalkRequest;
import io.sendgo.model.Contact;
import org.springframework.stereotype.Service;
@Service
public class OrderNotifier {
private final SendgoClient sendgo;
public OrderNotifier(SendgoClient sendgo) {
this.sendgo = sendgo;
}
public void confirmed(String phone, String orderNo) {
sendgo.alimtalk().send(AlimtalkRequest.builder()
.templateCode("ORDER_CONFIRM_001")
.contact(Contact.builder().contact(phone).var1(orderNo).build())
.build());
}
}
各发送渠道都是该 Bean 上的方法:alimtalk()、friendtalk()、brandMessage()、sms()。
contact(...) 每次调用都会追加一位接收者;也可用 contacts(List.of(...)) 一次性指定全部。
覆盖该 Bean
自动装配的 Bean 带 @ConditionalOnMissingBean,因此自行声明的 Bean 会优先生效 —— 适用于多租户或按环境使用不同基础地址:
@Configuration
public class SendgoConfiguration {
@Bean
public SendgoClient sendgoClient(TenantContext tenants) {
return new SendgoClient(io.sendgo.SendgoConfig.builder()
.accessKey(tenants.current().sendgoAccessKey())
.secretKey(tenants.current().sendgoSecretKey())
.apiVersion("v2")
.build());
}
}
品牌消息
好友消息已于 2025-12-31 停止服务。 自 2026-01-01 起,Kakao 会将好友消息请求 自动改以品牌消息(自由形式)发出。
friendtalk仍可调用,且自由文本类型FT/FI/FW发往单个接收者的路径目前仍只有这一条 —— 基于模板的富文本类型 (FL/FC/FM/FP/FA)、非好友定向(N/I)与群发(F)请使用品牌消息。 品牌消息是好友消息的后继渠道:可触达非频道好友的接收者(targeting("N")),也可向已同意接收的全部频道好友群发(targeting("F"))。消息类型与好友消息一一对应(FT→BT、FI→BI、FW→BW、FL→BL、FC→BC、FM→BM、FP→BP、FA→BA)—— 传入好友消息的代码,服务端会自动转换。
仅 v2 支持,也是本 Starter 的默认版本。
import io.sendgo.model.BrandMessageRequest;
import java.util.Map;
@Service
public class CampaignService {
private final SendgoClient sendgo;
public CampaignService(SendgoClient sendgo) {
this.sendgo = sendgo;
}
// 单条发送 —— targeting 为 M/N/I 时必须提供接收者
public void promote(String phone) {
sendgo.brandMessage().send(BrandMessageRequest.builder()
.targeting("M")
.messageType("FL")
.friendTemplateUuid("9cd5460b-6458-4edc-9b11-c26d3013c340")
.contact(Contact.builder().contact(phone).var1("29,000元").build())
.build());
}
// 群发 —— 已同意接收的全部频道好友(不传接收者)
public Map<String, Object> announce() {
return sendgo.brandMessage().broadcast(BrandMessageRequest.builder()
.messageType("FW")
.friendTemplateUuid("9cd5460b-6458-4edc-9b11-c26d3013c340")
.build());
}
// 群发在上游为异步处理,需轮询查看进度
public Map<String, Object> status(String campaignId) {
return sendgo.brandMessage().campaign(campaignId);
}
}
群发可能触达整个好友列表,请把它放在带 @PreAuthorize 的管理端点或定时任务里,不要暴露为公开接口。
targeting 的取值含义:
| 值 | 含义 |
|---|---|
M |
频道好友中的指定接收者 |
N |
非频道好友的接收者 |
I |
按标识符指定的接收者 |
F |
已同意接收的全部频道好友(群发) |
短信 / 长短信 / 多媒体短信
import io.sendgo.model.SmsRequest;
sendgo.sms().sendSms(SmsRequest.sms()
.content("[Sendgo] 验证码:123456(请在 5 分钟内输入)")
.contact(Contact.builder().contact("01012345678").build()));
sendgo.sms().sendLms(SmsRequest.lms()
.subject("[重要] 服务维护通知")
.content("服务将于 2026-07-25 02:00 ~ 06:00 进行维护。")
.contact(Contact.builder().contact("01012345678").build()));
sendgo.sms().sendMms(SmsRequest.mms()
.subject("[活动] 7 月特惠")
.content("欢迎查看本月特价商品。")
.contact(Contact.builder().contact("01012345678").build()));
SmsRequest 不使用 builder()/build():它通过 sms() / lms() / mms() 静态工厂创建,再用返回自身的链式 setter 配置。
在事务提交后发送
在事务内发送意味着之后的回滚仍然留下了已投递的消息。请使用由事务提交触发的应用事件:
public record OrderCreated(String phone, String orderNo) {}
@Service
public class OrderService {
private final ApplicationEventPublisher events;
private final OrderRepository orders;
@Transactional
public void create(OrderForm form) {
Order order = orders.save(Order.from(form));
events.publishEvent(new OrderCreated(order.getPhone(), order.getNumber()));
}
}
@Component
public class OrderCreatedListener {
private final OrderNotifier notifier;
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void on(OrderCreated event) {
notifier.confirmed(event.phone(), event.orderNo());
}
}
AFTER_COMMIT 保证记录已持久化后才发出消息,@Async 则把外发 HTTP 调用移出请求线程。
重试临时故障
@Service
public class ResilientNotifier {
private final SendgoClient sendgo;
@Retryable(
retryFor = SendgoException.class,
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2))
public void send(AlimtalkRequest request) {
sendgo.alimtalk().send(request);
}
@Recover
public void recover(SendgoException e, AlimtalkRequest request) {
// 4xx 重试也不会成功 —— 记录后停止。
log.error("Sendgo {} [{}]: {}", e.getStatusCode(), e.getErrorCode(), e.getMessage());
}
}
若只想重试 5xx,请在 send 开头检查 getStatusCode(),对 4xx 抛出一个不可重试的异常。
测试
用 mock 替换该 Bean,这样测试就不会真的发出请求:
@SpringBootTest
class OrderNotifierTest {
@MockBean
SendgoClient sendgo;
@Autowired
OrderNotifier notifier;
@Test
void sendsAlimtalk() {
var alimtalk = mock(AlimtalkService.class);
given(sendgo.alimtalk()).willReturn(alimtalk);
notifier.confirmed("01012345678", "ORD-001");
then(alimtalk).should().send(any(AlimtalkRequest.class));
}
}
异常处理
import io.sendgo.exception.SendgoException;
try {
sendgo.alimtalk().send(request);
} catch (SendgoException e) {
switch (e.getErrorCode()) {
case "INVALID_ACCESS_KEY", "INVALID_SECRET_KEY" ->
log.error("请检查 Sendgo 密钥");
case "PAYMENT_REQUIRED" ->
log.error("Sendgo 余额不足");
case "IP_NOT_ALLOWED" ->
log.error("该 IP 未在白名单中");
default -> {
if (e.getStatusCode() >= 500) {
throw e; // 交给 @Retryable 处理
}
log.error("Sendgo {}: {}", e.getStatusCode(), e.getMessage());
}
}
}
请根据 getErrorCode() 分支,而不要匹配错误消息文本 —— 消息文案可能变更,错误代码才是契约。
TOKEN_EXPIRED 与 TOKEN_MISMATCH 已在核心 SDK 内部处理:会重新签发令牌并自动重试该请求一次。
配置属性
| 属性 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|
sendgo.access-key |
必填 | — | Sendgo 访问密钥;同时用于启用自动装配 |
sendgo.secret-key |
必填 | — | Sendgo 私密密钥 |
sendgo.kakao-sender-key |
选填 | null |
Kakao 发送者资料密钥 |
sendgo.sms-sender-key |
选填 | null |
短信主叫号码密钥 |
sendgo.api-version |
选填 | v2 |
API 版本(v1 | v2) |
sendgo.url |
选填 | https://sendgo.io |
API 基础地址 |
短链接
短链接会缩短消息正文中的链接,并统计该链接是否被真正点击。 短信按字节计费,因此链接更短就意味着正文可以写更多内容。
仅 v2 支持。
再次缩短同一个原始 URL 会直接返回已有的链接。若希望按活动分别统计反应,
请使用 forceNew 生成新的短码。
deactivate 不会删除链接,只停止重定向。当已发送消息中的链接必须失效时使用它;
累计统计会保留,访问已停止的链接会返回 410 Gone。
@Service
public class LinkService {
private final SendgoClient sendgo;
public LinkService(SendgoClient sendgo) {
this.sendgo = sendgo;
}
public String shorten(String targetUrl) {
Map<String, Object> created = sendgo.shortUrl().create(ShortUrlRequest.builder()
.targetUrl(targetUrl)
.build());
@SuppressWarnings("unchecked")
Map<String, Object> data = (Map<String, Object>) created.get("data");
return (String) data.get("shortUrl");
}
public Map<String, Object> stats(String code) {
return sendgo.shortUrl().stats(code);
}
}
stats 返回按日趋势(daily)以及按设备(byDevice)、来源(byReferer)、国家(byCountry)的分解。按日趋势读取的是预聚合表,因此点击量再大响应时间也保持稳定。
包信息
- 包名:
io.sendgo:sendgo-spring(Maven Central) - 仓库:send-go/spring
- 注册表:https://central.sonatype.com/artifact/io.sendgo/sendgo-spring
- 许可:MIT
如何获取 API 密钥
登录 Sendgo 后,在 API/SDK → API 对接 菜单中签发访问密钥与私密密钥。
使用通知消息/好友消息需在 Kakao 渠道 登记发送者资料以获取 sendgo.kakao-sender-key;使用短信需在 发送号码 菜单登记主叫号码。
이 패키지로 할 수 있는 것
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.
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.