기업회원 신청 서류 안내
신청자 구분에 따라 재직증명서 또는 위임장을 함께 제출해 주세요.
신청자 구분에 따라 재직증명서 또는 위임장을 함께 제출해 주세요.
C# / .NET / SDK REFERENCE
The official ASP.NET Core extension for sending Kakao Alimtalk, Brand Message and SMS — registers the client for dependency injection.
dotnet add package Sendgo.AspNetCoreThe official ASP.NET Core extension for sending Kakao Alimtalk, Brand Message and SMS
Sendgo.AspNetCore wraps the Sendgo.SDK core with an AddSendgo extension that registers SendgoClient as a singleton.
Targets .NET 8.0.
dotnet add package Sendgo.AspNetCore
The core Sendgo.SDK comes along as a dependency.
// Program.cs
builder.Services.AddSendgo(builder.Configuration.GetSection("Sendgo"));
// appsettings.json
{
"Sendgo": {
"AccessKey": "your_access_key",
"SecretKey": "your_secret_key",
"KakaoSenderKey": "your_kakao_sender_key",
"SmsSenderKey": "your_sms_sender_key",
"ApiVersion": "v2"
}
}
Keep the keys out of appsettings.json in source control — bind them from user secrets in development and from environment variables or a vault in production:
dotnet user-secrets set "Sendgo:AccessKey" "your_access_key"
dotnet user-secrets set "Sendgo:SecretKey" "your_secret_key"
Environment variables use the double-underscore separator: Sendgo__AccessKey.
builder.Services.AddSendgo(options =>
{
options.AccessKey = builder.Configuration["Sendgo:AccessKey"]!;
options.SecretKey = builder.Configuration["Sendgo:SecretKey"]!;
options.KakaoSenderKey = builder.Configuration["Sendgo:KakaoSenderKey"];
options.ApiVersion = "v2";
});
Both overloads register the same singleton, so the token cache is shared across the whole app. SendgoClient implements IDisposable, and the DI container disposes the singleton on shutdown — do not wrap it in a using.
using Sendgo;
using Sendgo.Models;
public class OrderNotifier(SendgoClient sendgo, ILogger<OrderNotifier> logger)
{
public Task ConfirmedAsync(string phone, string orderNo, CancellationToken ct = default) =>
sendgo.SendAlimtalkAsync(new AlimtalkRequest
{
TemplateCode = "ORDER_CONFIRM_001",
Contacts = new[] { new Contact { PhoneNumber = phone, Var1 = orderNo } },
}, ct);
}
builder.Services.AddScoped<OrderNotifier>();
Note that Contact.PhoneNumber serialises as contact on the wire — the property is named for C# readability.
app.MapPost("/notify", async (
NotifyRequest body,
SendgoClient sendgo,
CancellationToken ct) =>
{
await sendgo.SendAlimtalkAsync(new AlimtalkRequest
{
TemplateCode = "ORDER_CONFIRM_001",
Contacts = new[] { new Contact { PhoneNumber = body.Phone, Var1 = body.OrderNo } },
}, ct);
return Results.Accepted();
});
record NotifyRequest(string Phone, string OrderNo);
Passing the endpoint's CancellationToken through means an aborted request also cancels the outbound call.
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
ApiVersion = "v2".
app.MapPost("/campaigns/targeted", async (
string phone, SendgoClient sendgo, CancellationToken ct) =>
{
var result = await sendgo.SendBrandMessageAsync(new BrandMessageRequest
{
Targeting = "M",
MessageType = "FL",
FriendTemplateUuid = "9cd5460b-6458-4edc-9b11-c26d3013c340",
Contacts = new[] { new Contact { PhoneNumber = phone, Var1 = "29,000 KRW" } },
}, ct);
return Results.Ok(result);
});
app.MapPost("/campaigns/broadcast", async (SendgoClient sendgo, CancellationToken ct) =>
{
// No recipient list — Kakao expands the audience
var result = await sendgo.BroadcastBrandMessageAsync(new BrandMessageRequest
{
MessageType = "FW",
FriendTemplateUuid = "9cd5460b-6458-4edc-9b11-c26d3013c340",
}, ct);
return Results.Accepted(value: result);
});
app.MapGet("/campaigns", (SendgoClient sendgo, CancellationToken ct) =>
sendgo.GetBrandMessagesAsync(count: 10, ct: ct));
app.MapGet("/campaigns/{campaignId}", (string campaignId, SendgoClient sendgo, CancellationToken ct) =>
sendgo.GetBrandMessageAsync(campaignId, ct));
A broadcast is processed asynchronously upstream, so the send response only acknowledges acceptance — poll the detail endpoint for progress.
Sending is an outbound HTTP call, so keep it off the request path for anything the caller does not need to wait on:
public class NotificationQueue
{
private readonly Channel<AlimtalkRequest> _channel =
Channel.CreateBounded<AlimtalkRequest>(1000);
public ValueTask EnqueueAsync(AlimtalkRequest request, CancellationToken ct) =>
_channel.Writer.WriteAsync(request, ct);
public IAsyncEnumerable<AlimtalkRequest> ReadAllAsync(CancellationToken ct) =>
_channel.Reader.ReadAllAsync(ct);
}
public class NotificationWorker(
NotificationQueue queue,
SendgoClient sendgo,
ILogger<NotificationWorker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await foreach (var request in queue.ReadAllAsync(stoppingToken))
{
try
{
await sendgo.SendAlimtalkAsync(request, stoppingToken);
}
catch (SendgoException e) when (e.StatusCode < 500)
{
// A 4xx will not succeed on retry — drop it and record why.
logger.LogError("Sendgo {Code}: {Message}", e.ErrorCode, e.Message);
}
catch (SendgoException e)
{
logger.LogWarning("Transient Sendgo failure {Status}, re-queueing", e.StatusCode);
await queue.EnqueueAsync(request, stoppingToken);
}
}
}
}
builder.Services.AddSingleton<NotificationQueue>();
builder.Services.AddHostedService<NotificationWorker>();
SendgoClient is sealed and creates its own HttpClient internally, so it cannot be subclassed or handed a stub message handler. Put your own interface in front of it and mock that — which is also what keeps your call sites independent of the SDK:
public interface IOrderNotifier
{
Task ConfirmedAsync(string phone, string orderNo, CancellationToken ct = default);
}
public class OrderNotifier(SendgoClient sendgo) : IOrderNotifier
{
public Task ConfirmedAsync(string phone, string orderNo, CancellationToken ct = default) =>
sendgo.SendAlimtalkAsync(new AlimtalkRequest
{
TemplateCode = "ORDER_CONFIRM_001",
Contacts = new[] { new Contact { PhoneNumber = phone, Var1 = orderNo } },
}, ct);
}
builder.Services.AddScoped<IOrderNotifier, OrderNotifier>();
public class TestFactory : WebApplicationFactory<Program>
{
public Mock<IOrderNotifier> Notifier { get; } = new();
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.RemoveAll<IOrderNotifier>();
services.AddScoped(_ => Notifier.Object);
});
}
}
For an end-to-end test that must exercise the SDK itself, point Sendgo:BaseUrl at a local stub server (WireMock.Net, or a second WebApplication) instead of mocking the client.
Map Sendgo errors onto HTTP status codes rather than letting them surface as 500s:
using Sendgo.Exceptions;
app.UseExceptionHandler(handler => handler.Run(async context =>
{
var error = context.Features.Get<IExceptionHandlerFeature>()?.Error;
if (error is not SendgoException e)
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
return;
}
context.Response.StatusCode = e.ErrorCode switch
{
// Our configuration is wrong, not the caller's request.
"INVALID_ACCESS_KEY" or "INVALID_SECRET_KEY" or "IP_NOT_ALLOWED"
=> StatusCodes.Status500InternalServerError,
"PAYMENT_REQUIRED" => StatusCodes.Status402PaymentRequired,
_ => e.StatusCode >= 500 ? StatusCodes.Status502BadGateway : StatusCodes.Status400BadRequest,
};
await context.Response.WriteAsJsonAsync(new { error = e.ErrorCode });
}));
Branch on ErrorCode, 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.
The configuration section binds directly onto SendgoOptions.
| Key | Required | Default | Description |
|---|---|---|---|
Sendgo:AccessKey |
required | — | Sendgo access key |
Sendgo:SecretKey |
required | — | Sendgo secret key |
Sendgo:KakaoSenderKey |
optional | null |
Kakao sender profile key |
Sendgo:SmsSenderKey |
optional | null |
SMS caller ID key |
Sendgo:ApiVersion |
optional | v1 |
API version (v1 | v2) |
Sendgo:BaseUrl |
optional | https://sendgo.io |
API base 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.MapPost("/shorten", async (
string targetUrl, SendgoClient sendgo, CancellationToken ct) =>
{
var created = await sendgo.CreateShortUrlAsync(new ShortUrlRequest
{
TargetUrl = targetUrl,
}, ct);
return Results.Ok(created);
});
app.MapGet("/shorten/{code}/stats", (string code, SendgoClient sendgo, CancellationToken ct) =>
sendgo.GetShortUrlStatsAsync(code, ct: ct));
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.
Sendgo.AspNetCore (NuGet)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:KakaoSenderKey, and a caller ID under Sender numbers for SMS.
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 Korean SMS (90 bytes), LMS (long text) and MMS (with images) through Sendgo. Type selection, byte counting, verification-code patterns and advertising rules.