> **The official Laravel package for sending Kakao Alimtalk, Brand Message and SMS**

`sendgo/laravel` wraps the [`sendgo/php`](https://github.com/send-go/php) core as a **Laravel-native package**: the service provider and facade are auto-discovered, and the config file is publishable.

Requires Laravel 10 or newer and PHP 8.2+.

---

## Install

```bash
composer require sendgo/laravel
```

Laravel's package auto-discovery registers the service provider and the `Sendgo` facade for you.

---

## Configure

### 1. Environment variables

```env
SENDGO_ACCESS_KEY=your_access_key
SENDGO_SECRET_KEY=your_secret_key
SENDGO_KAKAO_SENDER_KEY=your_kakao_sender_key
SENDGO_SMS_SENDER_KEY=your_sms_sender_key
SENDGO_API_VERSION=v2
```

### 2. Publish the config (optional)

```bash
php artisan vendor:publish --tag=sendgo-config
```

This writes `config/sendgo.php`, which reads the variables above.

---

## Quick start

```php
<?php

use Sendgo\Laravel\Facades\Sendgo;

// Send an Alimtalk
Sendgo::alimtalk()->send([
    'templateCode' => 'ORDER_CONFIRM_001',
    'contacts'     => [
        ['contact' => '01012345678', 'name' => 'Gildong Hong', 'var1' => 'ORD-001'],
    ],
]);

// Send an SMS
Sendgo::sms()->sendSms([
    'content'  => '[Sendgo] Your code is 123456 (valid for 5 minutes)',
    'contacts' => [['contact' => '01012345678']],
]);
```

The facade exposes each channel as a method: `Sendgo::alimtalk()`, `Sendgo::friendtalk()`, `Sendgo::brandMessage()`, `Sendgo::sms()`.

---

## Dependency injection

If you prefer injecting the client — easier to fake in tests — resolve the core class:

```php
<?php

namespace App\Services;

use Sendgo\Php\Sendgo;

class OrderNotifier
{
    public function __construct(private readonly Sendgo $sendgo) {}

    public function confirmed(string $phone, string $orderNo): void
    {
        $this->sendgo->alimtalk->send([
            'templateCode' => 'ORDER_CONFIRM_001',
            'contacts'     => [['contact' => $phone, 'var1' => $orderNo]],
        ]);
    }
}
```

Both styles reach the same singleton, so the facade and the injected instance share one token cache.

---

## 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. Message types map one-to-one
(`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.

Unlike Friendtalk it can also reach recipients who are **not channel friends** (`targeting: 'N'`) and **broadcast to every consenting channel friend** (`targeting: 'F'`).

> v2 only. Set `SENDGO_API_VERSION=v2`.

```php
<?php

use Sendgo\Laravel\Facades\Sendgo;

// Single send — channel friends
Sendgo::brandMessage()->send([
    'targeting'          => 'M',
    'messageType'        => 'FL',
    'friendTemplateUuid' => '9cd5460b-6458-4edc-9b11-c26d3013c340',
    'contacts'           => [['contact' => '01012345678', 'var1' => '29,000 KRW']],
]);

// Broadcast — every consenting channel friend (no contacts)
Sendgo::brandMessage()->broadcast([
    'messageType'        => 'FW',
    'friendTemplateUuid' => '9cd5460b-6458-4edc-9b11-c26d3013c340',
]);

// Campaign lookups
$list = Sendgo::brandMessage()->campaigns(['count' => 10]);
$one  = Sendgo::brandMessage()->campaign('1f0a6d0e-6b3b-4f0f-9b2f-2f6f6a1b7c11');
```

---

## Queued sending

Sending is an outbound HTTP call, so it belongs in a job rather than a request cycle.

```php
<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Sendgo\Php\Exception\SendgoException;
use Sendgo\Php\Sendgo;

class SendOrderConfirm implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue;

    public int $tries = 3;

    public function __construct(
        private readonly string $phone,
        private readonly string $orderNo,
    ) {}

    public function handle(Sendgo $sendgo): void
    {
        try {
            $sendgo->alimtalk->send([
                'templateCode' => 'ORDER_CONFIRM_001',
                'contacts'     => [['contact' => $this->phone, 'var1' => $this->orderNo]],
            ]);
        } catch (SendgoException $e) {
            // 5xx is transient; a 4xx will not succeed on retry, so fail fast.
            if ($e->getStatusCode() >= 500) {
                $this->release(now()->addSeconds(30));

                return;
            }

            $this->fail($e);
        }
    }
}
```

```php
SendOrderConfirm::dispatch($order->phone, $order->number);
```

---

## Notification channel

To send through Laravel's notification system, forward from a notification to the client:

```php
<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;
use Sendgo\Php\Sendgo;

class OrderConfirmed extends Notification
{
    public function __construct(private readonly string $orderNo) {}

    public function via(object $notifiable): array
    {
        return ['sendgo'];
    }

    public function toSendgo(object $notifiable): void
    {
        app(Sendgo::class)->alimtalk->send([
            'templateCode' => 'ORDER_CONFIRM_001',
            'contacts'     => [['contact' => $notifiable->phone, 'var1' => $this->orderNo]],
        ]);
    }
}
```

Register the driver in a service provider:

```php
Notification::extend('sendgo', fn () => new class {
    public function send($notifiable, Notification $notification): void
    {
        $notification->toSendgo($notifiable);
    }
});
```

---

## Testing

Bind a fake over the container binding so no request leaves your test suite:

```php
use Sendgo\Php\Sendgo;

$this->instance(Sendgo::class, new class extends Sendgo {
    public array $sent = [];

    public function __construct() {} // skip the parent's token setup

    public function __call(string $name, array $arguments): object
    {
        return new class($this, $name) {
            public function __construct(private $parent, private string $channel) {}

            public function send(array $payload): array
            {
                $this->parent->sent[] = [$this->channel, $payload];

                return ['message' => 'Success'];
            }
        };
    }
});
```

---

## Error handling

```php
<?php

use Sendgo\Php\Exception\SendgoException;
use Sendgo\Laravel\Facades\Sendgo;

try {
    Sendgo::alimtalk()->send([
        'templateCode' => 'ORDER_CONFIRM_001',
        'contacts'     => [['contact' => '01012345678']],
    ]);
} catch (SendgoException $e) {
    match ($e->getErrorCode()) {
        'INVALID_ACCESS_KEY',
        'INVALID_SECRET_KEY'    => Log::critical('Check the Sendgo API keys.'),
        'PAYMENT_REQUIRED'      => Log::critical('Out of Sendgo credit.'),
        'IP_NOT_ALLOWED'        => Log::critical('IP is not allow-listed.'),
        'INVALID_TEMPLATE_CODE' => Log::warning('Unknown template'),
        default                 => Log::error("Sendgo {$e->getStatusCode()}: {$e->getMessage()}"),
    };
}
```

Branch on `getErrorCode()`, not on the message text — messages can change, codes are the contract.

---

## Configuration reference

`config/sendgo.php` maps one-to-one onto the core client options.

| Key | Env | Default | Description |
|-----|-----|---------|-------------|
| `access_key` | `SENDGO_ACCESS_KEY` | — | Sendgo access key (**required**) |
| `secret_key` | `SENDGO_SECRET_KEY` | — | Sendgo secret key (**required**) |
| `kakao_sender_key` | `SENDGO_KAKAO_SENDER_KEY` | `null` | Kakao sender profile key |
| `sms_sender_key` | `SENDGO_SMS_SENDER_KEY` | `null` | SMS caller ID key |
| `api_version` | `SENDGO_API_VERSION` | `v1` | API version (`v1` \| `v2`) |
| `url` | `SENDGO_URL` | `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`.

```php
use Sendgo\Laravel\Facades\Sendgo;

$short = Sendgo::shortUrl()->create([
    'targetUrl' => 'https://example.com/promotions/summer-sale',
    'title'     => 'Summer sale landing',
]);

$link = $short['data']['shortUrl'];
$code = $short['data']['code'];

$stats = Sendgo::shortUrl()->stats($code, ['from' => '2026-08-01']);
Sendgo::shortUrl()->deactivate($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/laravel` (Packagist)
- **Repository**: [send-go/laravel](https://github.com/send-go/laravel)
- **Registry**: https://packagist.org/packages/sendgo/laravel
- **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 `kakao_sender_key`, and a caller ID under **Sender numbers** for SMS.