> **The official WordPress plugin for sending Kakao Alimtalk, Brand Message and SMS, with automatic WooCommerce order notifications**

`sendgo/wordpress` bundles the [`sendgo/php`](https://github.com/send-go/php) core through Composer and wires it into WordPress: a settings screen for your keys, and WooCommerce hooks that notify the buyer when an order changes state.

Requires PHP 8.2+.

---

## Install

### From wordpress.org or a zip (recommended)

The distributed zip bundles the core SDK in `vendor/`, so it runs without Composer.
Upload it under **Plugins → Add New**, or unpack it into `wp-content/plugins/sendgo`.

### With Composer

```bash
composer require sendgo/wordpress
```

Or from inside the plugin directory:

```bash
cd wp-content/plugins/sendgo
composer install
```

`composer install` generates the `vendor/autoload.php` that contains the core SDK. A checkout
straight from source has no `vendor/` directory, and **without it the plugin cannot send** —
`client()` returns `null` and an admin notice explains why.

### Activate

Activate **Sendgo** under **Plugins** in the WordPress admin.

---

## Configure

Open **Settings → Sendgo**:

| Field | Description |
|-------|-------------|
| Access Key | Sendgo access key |
| Secret Key | Sendgo secret key |
| Kakao Sender Key | Kakao sender profile key (Alimtalk / Brand Message) |
| SMS Sender Key | SMS caller ID key (SMS / LMS / MMS) |
| API Version | `v1` or `v2` |

Sending stays disabled until **both** the access key and secret key are filled in. Keys are stored server-side in the `sendgo_options` option and never exposed to the front end.

The API version defaults to **`v1`**. Brand Message and Short URL need `v2`, so switch it if you plan to use those channels.

> The API base URL is read from the `url` option but has no settings field. To point the plugin at a different host, set it programmatically:
> ```php
> $options = get_option('sendgo_options', []);
> $options['url'] = 'https://staging.sendgo.io';
> update_option('sendgo_options', $options);
> ```
> Before 1.2.4 this value was dropped the first time the settings form was saved. It now survives a save.

---

## WooCommerce order notifications

With WooCommerce active, the plugin notifies the billing phone number on these transitions:

- **Completed** (`woocommerce_order_status_completed`)
- **Processing** (`woocommerce_order_status_processing`)

Configure each state separately under **Settings → Sendgo → WooCommerce Order Notifications**:

| Field | Description |
|-------|-------------|
| `[Order completed] Alimtalk template code` | Template sent on completion. The order number is passed as the first variable (`#{var1}`). |
| `[Order completed] SMS text used if Alimtalk fails` | Sent when the Alimtalk fails or no template code is set. Supports the `{order_number}` placeholder. |
| `[Processing] Alimtalk template code` | Template sent when the order enters processing. |
| `[Processing] SMS text used if Alimtalk fails` | SMS fallback for the processing state. |

**A state you leave blank sends nothing.** Filling in both means every order triggers two messages — one on processing, one on completion — so only configure the states you actually want to announce.

Each order notifies **once per state**. On a successful send the plugin records order meta (`_sendgo_notified_completed` / `_sendgo_notified_processing`), so re-saving the order in the admin or another plugin re-applying the status will not send again. A failed send is not recorded, so the next transition retries it.

A send failure never interrupts the checkout or order flow — it is caught and written to the WooCommerce log under source `sendgo`.

> **Upgrading from 1.0.x** — earlier versions shared a single `order_template_code` between both states, so an order moving processing → completed delivered the **same message twice** and billed the customer twice. Completed keeps the original option key; processing now needs its own field before it sends anything. Existing configurations keep working; only the duplicate goes away.

### Phone numbers

The billing phone is reduced to digits (`preg_replace('/[^0-9]/', ...)`) before sending, so `010-1234-5678` and `+82 10 1234 5678` both need to already be a Korean number the API accepts — the plugin strips punctuation but does not convert country codes. Orders with an empty billing phone are skipped silently.

---

## Sending from your own code

Once the plugin is loaded, the core client is available directly:

```php
$client = Sendgo_Plugin::instance()->client();

if ($client) {
    // Alimtalk
    $client->alimtalk->send([
        'templateCode' => 'ORDER_CONFIRM_001',
        'contacts'     => [['contact' => '01012345678', 'var1' => 'ORD-001']],
    ]);

    // SMS
    $client->sms->sendSms([
        'content'  => 'Verification code: 123456',
        'contacts' => [['contact' => '01012345678']],
    ]);
}
```

Always guard on `$client` — it is `null` when the keys are unset or `vendor/autoload.php` is missing, and calling a method on `null` would white-screen the page you are hooked into.

Every channel is a property on the client:

| Property | Channel |
|----------|---------|
| `$client->alimtalk` | Kakao Alimtalk |
| `$client->friendtalk` | Kakao Friendtalk |
| `$client->brandMessage` | Kakao Brand Message (v2 only) |
| `$client->sms` | SMS / LMS / MMS |

The client is memoised for the request, so calling `Sendgo_Plugin::instance()->client()` repeatedly is cheap.

### Hook it to your own event

```php
add_action('user_register', function (int $user_id): void {
    $client = Sendgo_Plugin::instance()->client();
    if (!$client) {
        return;
    }

    $user  = get_userdata($user_id);
    $phone = preg_replace('/[^0-9]/', '', (string) get_user_meta($user_id, 'billing_phone', true));

    if ('' === $phone) {
        return;
    }

    try {
        $client->alimtalk->send([
            'templateCode' => 'WELCOME_001',
            'contacts'     => [['contact' => $phone, 'var1' => $user->display_name]],
        ]);
    } catch (\Throwable $e) {
        // Never let a messaging failure break registration.
        error_log('Sendgo welcome message failed: ' . $e->getMessage());
    }
}, 10, 1);
```

Wrapping the call in `try`/`catch` is the important part: an unhandled `SendgoException` inside a WordPress hook surfaces as a fatal error on whatever page fired it.

---

## 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 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`).

> **v2 only.** Set **Settings → Sendgo → API Version** to `v2`; the plugin defaults to `v1`.

```php
$client = Sendgo_Plugin::instance()->client();

// Single send — targeting M/N/I requires `contacts`
$client->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 recipient list
$result = $client->brandMessage->broadcast([
    'messageType'        => 'FW',
    'friendTemplateUuid' => '9cd5460b-6458-4edc-9b11-c26d3013c340',
]);

// A broadcast is asynchronous upstream, so poll for progress
$client->brandMessage->campaign($result['data']['campaignId']);
$client->brandMessage->campaigns(['count' => 10]);
```

A broadcast can reach your entire friend list, so trigger it from a deliberate admin action — a WP-CLI command or an admin-post handler behind a capability check — never from a public hook.

---

## Error handling

```php
use Sendgo\Php\Exception\SendgoException;

try {
    $client->alimtalk->send([...]);
} catch (SendgoException $e) {
    // Send failures should be logged, not surfaced to the shopper.
    if (function_exists('wc_get_logger')) {
        wc_get_logger()->error(
            sprintf('Sendgo %d [%s]: %s', $e->getStatusCode(), $e->getErrorCode(), $e->getMessage()),
            ['source' => 'sendgo']
        );
    }
}
```

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.

---

## FAQ

**Can I use it without WooCommerce?**
Yes. `Sendgo_Plugin::instance()->client()` works on any WordPress install; only the automatic order notifications depend on WooCommerce.

**`client()` returns `null`.**
Either the access key / secret key is unset, or the plugin came from source and `composer install`
was never run, so `vendor/autoload.php` does not exist. The second case raises an admin notice.
The distributed zip includes `vendor/`, so it cannot happen there.

**Nothing sends and there is no error.**
Check that you registered the outbound IP of your WordPress site under **Integrations → Integration Info**
in the console — a request from an unlisted address is rejected. On shared hosting or behind a CDN
that address differs from the one your browser reports.

**Where are the credentials stored?**
Server-side in the `sendgo_options` option. They are never printed to the front end, and the secret key renders as a password field in the admin.

**Does uninstalling clean everything up?**
`uninstall.php` deletes the `sendgo_options` option. The `_sendgo_notified_*` order meta is left in place — it is harmless, and deleting it would mean a bulk write across every order.

---

## 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
$client = Sendgo_Plugin::instance()->client();

if ($client) {
    $short = $client->shortUrl->create([
        'targetUrl' => get_permalink($post_id),
        'title'     => get_the_title($post_id),
    ]);

    $link = $short['data']['shortUrl'];
}
```

`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.

---

## External services

This plugin sends Kakao Alimtalk, Kakao Brand Message and SMS/LMS/MMS through the Sendgo API
(https://sendgo.io). A Sendgo account is required and message delivery is a paid service.

- Before any send, the access key and secret key are exchanged for a short-lived token.
- When a WooCommerce order changes to `processing` or `completed` and that state has a template
  code or SMS fallback configured, the buyer's billing phone number and the order number are sent
  along with your sender key and the template code.
- When you call the client from your own code, the recipient numbers and body you pass are sent as is.

Nothing is sent when the plugin is merely installed or activated, or when both the template code
and the SMS fallback are empty.

- Terms of service: https://sendgo.io/terms-of-service
- Privacy policy: https://sendgo.io/privacy-policy

---

## Package information

- **Package**: `sendgo/wordpress` (Packagist)
- **Repository**: [send-go/wordpress](https://github.com/send-go/wordpress)
- **Registry**: https://packagist.org/packages/sendgo/wordpress
- **License**: MIT

### Getting your API keys

Sign in to Sendgo and open **Integrations → Integration Info** to issue an access key and secret key,
and to register the IP addresses allowed to use them — a request from an unlisted address is rejected.
Register a Kakao sender profile under **Kakao Sender Profile** to get your Kakao Sender Key, and a caller
ID under **Sender Management** for SMS.