Documentation menu
Java / SDK REFERENCE
Spring Boot SDK Guide
The official Spring Boot starter for sending Kakao Alimtalk, Brand Message and SMS — auto-configured client bean from application properties.
이 문서의 목차
- 패키지
- io.sendgo:sendgo-spring
- 언어
- Java
- 레지스트리
- Maven Central
implementation "io.sendgo:sendgo-spring:1.0.1"The official Spring Boot starter for sending Kakao Alimtalk, Brand Message and SMS
io.sendgo:sendgo-spring wraps the io.sendgo:sendgo-java core as a Spring Boot starter: set sendgo.access-key and a SendgoClient bean is auto-configured.
Requires Spring Boot 3.x and Java 17+.
Install
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>
The core sendgo-java comes along as a transitive dependency.
Configure
# 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
Or in properties form:
sendgo.access-key=${SENDGO_ACCESS_KEY}
sendgo.secret-key=${SENDGO_SECRET_KEY}
sendgo.kakao-sender-key=${SENDGO_KAKAO_SENDER_KEY}
sendgo.api-version=v2
The starter ships configuration metadata, so your IDE autocompletes these keys.
Auto-configuration is conditional on sendgo.access-key being present — the app still starts without it, which keeps local profiles that do not send messages working.
Both the starter and the bare sendgo-java core default api-version to v2, so you only need to set it to pin v1 for a legacy integration.
Inject the client
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());
}
}
Every channel is a method on the bean: alimtalk(), friendtalk(), brandMessage(), sms().
Overriding the bean
The auto-configured bean is @ConditionalOnMissingBean, so declaring your own takes precedence — useful for multi-tenant setups or a custom base URL per environment:
@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());
}
}
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, which is the starter's default.
import io.sendgo.model.BrandMessageRequest;
@Service
public class CampaignService {
private final SendgoClient sendgo;
public CampaignService(SendgoClient sendgo) {
this.sendgo = sendgo;
}
// Single send — channel friends
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 KRW").build())
.build());
}
// Broadcast — every consenting channel friend (no recipient list)
public Map<String, Object> announce() {
return sendgo.brandMessage().broadcast(BrandMessageRequest.builder()
.messageType("FW")
.friendTemplateUuid("9cd5460b-6458-4edc-9b11-c26d3013c340")
.build());
}
// A broadcast is asynchronous upstream, so poll for the result
public Map<String, Object> status(String campaignId) {
return sendgo.brandMessage().campaign(campaignId);
}
}
Sending after a transaction commits
Sending inside a transaction means a later rollback still leaves the message delivered. Use an application event committed by the transaction:
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 guarantees the row is durable before the message goes out, and @Async keeps the outbound HTTP call off the request thread.
Retrying transient failures
@Service
public class ResilientNotifier {
private final SendgoClient sendgo;
@Retryable(
retryFor = SendgoException.class,
noRetryFor = { }, // filtered in the recover method instead
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) {
// A 4xx will not succeed on retry — log it and stop.
log.error("Sendgo {} [{}]: {}", e.getStatusCode(), e.getErrorCode(), e.getMessage());
}
}
If you only want 5xx retried, check getStatusCode() at the top of send and rethrow a non-retryable exception for 4xx.
Testing
Replace the bean with a mock so no request leaves the test:
@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));
}
}
Error handling
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("Check the Sendgo API keys");
case "PAYMENT_REQUIRED" ->
log.error("Out of Sendgo credit");
case "IP_NOT_ALLOWED" ->
log.error("IP is not allow-listed");
default -> {
if (e.getStatusCode() >= 500) {
throw e; // let @Retryable handle it
}
log.error("Sendgo {}: {}", e.getStatusCode(), e.getMessage());
}
}
}
Branch on getErrorCode(), 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.
Properties reference
| Property | Required | Default | Description |
|---|---|---|---|
sendgo.access-key |
required | — | Sendgo access key; also enables auto-configuration |
sendgo.secret-key |
required | — | Sendgo secret key |
sendgo.kakao-sender-key |
optional | null |
Kakao sender profile key |
sendgo.sms-sender-key |
optional | null |
SMS caller ID key |
sendgo.api-version |
optional | v2 |
API version (v1 | v2) |
sendgo.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.
@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 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:
io.sendgo:sendgo-spring(Maven Central) - Repository: send-go/spring
- Registry: https://central.sonatype.com/artifact/io.sendgo/sendgo-spring
- 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.
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.