Documentation menu
JavaScript / TypeScript / SDK REFERENCE
NestJS SDK Guide
The official NestJS module for sending Kakao Alimtalk, Brand Message and SMS — sync and async registration, injectable service.
이 문서의 목차
- 패키지
- @sendgo/nestjs
- 언어
- JavaScript / TypeScript
- 레지스트리
- npm
npm install @sendgo/nestjsThe official NestJS module for sending Kakao Alimtalk, Brand Message and SMS
@sendgo/nestjs wraps the @sendgo/node core as a NestJS module, so the client is registered once and injected wherever you need it.
Requires NestJS 10 or newer.
Install
npm install @sendgo/nestjs
The core @sendgo/node comes along as a dependency — you do not install it separately.
Register the module
Static configuration
import { Module } from '@nestjs/common';
import { SendgoModule } from '@sendgo/nestjs';
@Module({
imports: [
SendgoModule.forRoot({
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',
}),
],
})
export class AppModule {}
Async configuration with ConfigService
Prefer this when configuration comes from @nestjs/config, Vault, or any async source:
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { SendgoModule } from '@sendgo/nestjs';
@Module({
imports: [
ConfigModule.forRoot(),
SendgoModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
accessKey: config.getOrThrow<string>('SENDGO_ACCESS_KEY'),
secretKey: config.getOrThrow<string>('SENDGO_SECRET_KEY'),
kakaoSenderKey: config.get<string>('SENDGO_KAKAO_SENDER_KEY'),
apiVersion: 'v2',
}),
}),
],
})
export class AppModule {}
getOrThrow makes a missing key fail at bootstrap rather than at the first send.
Inject the service
import { Injectable, Logger } from '@nestjs/common';
import { SendgoService } from '@sendgo/nestjs';
@Injectable()
export class OrderNotifier {
private readonly logger = new Logger(OrderNotifier.name);
constructor(private readonly sendgo: SendgoService) {}
async confirmed(phone: string, orderNo: string) {
await this.sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [{ contact: phone, var1: orderNo }],
});
}
}
SendgoService exposes each channel as a getter that forwards to the core client:
| Getter | Channel |
|---|---|
sendgo.alimtalk |
Kakao Alimtalk |
sendgo.friendtalk |
Kakao Friendtalk |
sendgo.brandMessage |
Kakao Brand Message (v2 only) |
sendgo.sms |
SMS / LMS / MMS |
sendgo.client |
the underlying Sendgo instance |
Use sendgo.client when you need something the service does not surface yet — it is the same singleton, so the token cache is shared.
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 can reach recipients who are not channel friends (targeting: 'N') and 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
apiVersion: 'v2'when registering the module.
@Injectable()
export class CampaignService {
constructor(private readonly sendgo: SendgoService) {}
// Single send — channel friends
sendToFriends(phone: string) {
return this.sendgo.brandMessage.send({
targeting: 'M',
messageType: 'FL',
friendTemplateUuid: '9cd5460b-6458-4edc-9b11-c26d3013c340',
contacts: [{ contact: phone, var1: '29,000 KRW' }],
});
}
// Broadcast — every consenting channel friend (no recipient list)
broadcast() {
return this.sendgo.brandMessage.broadcast({
messageType: 'FW',
friendTemplateUuid: '9cd5460b-6458-4edc-9b11-c26d3013c340',
});
}
// Campaign lookups
list() {
return this.sendgo.brandMessage.campaigns({ count: 10 });
}
detail(campaignId: string) {
return this.sendgo.brandMessage.campaign(campaignId);
}
}
Queued sending with BullMQ
Sending is an outbound HTTP call, so keep it out of the request cycle:
import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Job, UnrecoverableError } from 'bullmq';
import { SendgoService } from '@sendgo/nestjs';
import { SendgoError } from '@sendgo/node';
@Processor('notifications')
export class NotificationProcessor extends WorkerHost {
constructor(private readonly sendgo: SendgoService) {
super();
}
async process(job: Job<{ phone: string; orderNo: string }>) {
try {
await this.sendgo.alimtalk.send({
templateCode: 'ORDER_CONFIRM_001',
contacts: [{ contact: job.data.phone, var1: job.data.orderNo }],
});
} catch (error) {
if (error instanceof SendgoError && error.statusCode < 500) {
// A 4xx will not succeed on retry — stop burning attempts.
throw new UnrecoverableError(`${error.errorCode}: ${error.message}`);
}
throw error; // 5xx and network errors fall through to BullMQ's backoff
}
}
}
Testing
Override the provider so no request leaves the test suite:
import { Test } from '@nestjs/testing';
import { SendgoService } from '@sendgo/nestjs';
const sent: unknown[] = [];
const moduleRef = await Test.createTestingModule({
providers: [OrderNotifier],
})
.useMocker((token) => {
if (token === SendgoService) {
return {
alimtalk: { send: async (payload: unknown) => { sent.push(payload); return { success: true }; } },
};
}
})
.compile();
await moduleRef.get(OrderNotifier).confirmed('01012345678', 'ORD-001');
expect(sent).toHaveLength(1);
Error handling
import { SendgoError } from '@sendgo/node';
try {
await this.sendgo.alimtalk.send({ /* ... */ });
} catch (error) {
if (!(error instanceof SendgoError)) throw error;
switch (error.errorCode) {
case 'INVALID_ACCESS_KEY':
case 'INVALID_SECRET_KEY':
this.logger.error('Check the Sendgo API keys');
break;
case 'PAYMENT_REQUIRED':
this.logger.error('Out of Sendgo credit');
break;
case 'IP_NOT_ALLOWED':
this.logger.error('IP is not allow-listed');
break;
default:
this.logger.error(`Sendgo ${error.statusCode}: ${error.message}`);
}
}
Branch on errorCode, 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
forRoot() and the factory return of forRootAsync() both take the core client options.
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
accessKey |
string |
required | — | Sendgo access key |
secretKey |
string |
required | — | Sendgo secret key |
kakaoSenderKey |
string |
optional | — | Kakao sender profile key |
smsSenderKey |
string |
optional | — | SMS caller ID key |
apiVersion |
'v1' | 'v2' |
optional | 'v1' |
API version |
baseUrl |
string |
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.
@Injectable()
export class LinkService {
constructor(private readonly sendgo: SendgoService) {}
async shorten(targetUrl: string) {
const created = await this.sendgo.shortUrl.create({ targetUrl });
return created.data.shortUrl;
}
stats(code: string) {
return this.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:
@sendgo/nestjs(npm) - Repository: send-go/nestjs
- Registry: https://www.npmjs.com/package/@sendgo/nestjs
- 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 kakaoSenderKey, 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.
Advertising message rules in Korea — (광고) prefix, opt-out, night ban →
What Korean law requires of promotional SMS and Kakao messages, and how to enforce it in code: the (광고) prefix, a free opt-out, and no sending between 21:00 and 08:00 KST.
Kakao Friendtalk shut down (2025-12-31) — migrating to Brand Message →
What happens to existing Friendtalk code now that the channel has ended, when you must migrate to Brand Message, and the one case where the Friendtalk endpoint is still the right call.
관련 패키지
Node.js →
The Node.js and TypeScript SDK for sending Kakao Alimtalk, Brand Message and SMS — fully typed, zero runtime dependencies.
npm install @sendgo/nodeReact / Next.js →
The official React SDK for sending Kakao Alimtalk, Brand Message and SMS from Next.js Server Actions and Route Handlers — server-side only.
npm install @sendgo/reactVue / Nuxt →
The official Vue SDK for sending Kakao Alimtalk, Brand Message and SMS from Nuxt server routes — server-side only.
npm install @sendgo/vue