본문 바로가기
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/nestjs

The 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. friendtalk still works and is still the only path for free-form FT/FI/FW to 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

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.

이 패키지로 할 수 있는 것

관련 패키지

Focus on building. Leave messaging to Sendgo.Back to top ↑