npmjs.com ↗
Kurulumnpm install signalbird · composer require pariette/signalbird · pip install signalbird

Gönderim (Messaging) SDK - Node.js & PHP / Laravel

Signalbird Gönderim istemcisi. Sunucunuzdan e-posta, SMS ve push gönderir; kişileri ve listeleri yönetir; kampanya açar; mesaj durumlarını okur ve webhook imzalarını doğrular.

Telsiz istemcisinden (SignalbirdClient / Signalbird::log()) ayrıdır: farklı anahtar, farklı kapı, farklı kota. Aynı pakettedir, ayrı sınıftır.

text
takım anahtarı  →  sb_…                       (Konsol → SDK anahtarları)
kapsam          →  email:send, contacts:write… (anahtar açılırken seçilir)
sınıf           →  transactional | commercial  (her gönderimde ZORUNLU)

Anahtar ve kapsamlar

Panelde Konsol → SDK anahtarları → Yeni anahtar ile bir takım anahtarı (sb_…) açın. Anahtar size bir kez gösterilir; gizlidir ve yalnız sunucuda kullanılır - tarayıcıya ya da mobil uygulamaya gömülmez.

Anahtar açılırken kapsam seçilir. Bir metodun çalışması için ilgili kapsamın anahtarda olması gerekir; yoksa API_KEY_SCOPE döner.

Kapsam Açtığı işlemler
email:send · sms:send · push:send sendEmail, sendSms, previewSms, sendPush
contacts:read · contacts:write kişi ve liste okuma / yazma, bulkContacts
campaigns:read · campaigns:write kampanya listeleme, açma, iptal
email:read · sms:read · push:read mesaj günlüğü (listMessages, getMessage)

Yanlış türde anahtar (açık anahtar sb_public_live_…) verilirse istemci kurulurken WRONG_KEY_TYPE fırlatır - haftalar sonra bulunacak bir 401 yerine ilk saniyede.

Kurulum

bash
npm install signalbird        # Node 18+, bağımlılık yok
composer require pariette/signalbird    # PHP 8.1+, ext-curl, Guzzle gerekmez

Hızlı başlangıç

Node.js / TypeScript

typescript
import { SignalbirdMessaging } from 'signalbird'

const sb = new SignalbirdMessaging({ domainKey: process.env.SIGNALBIRD_DOMAIN_KEY! })

const res = await sb.sendEmail({
  to: 'ahmet@example.com',
  class: 'transactional',
  subject: 'Siparişiniz alındı',
  body: '<p>Sipariş #1042 hazırlanıyor.</p>',
})

if (res.ok) console.log(res.data.id, res.data.status)   // 'msg_…', 'queued'
else console.warn(res.code, res.message)                // 'NO_SENDING_DOMAIN', …

Yapılandırma: { domainKey, baseUrl?, timeout?, throwOnError?, debug? } - varsayılanlar https://signalbird.io/api, 15000 ms, false, false.

PHP

php
use Signalbird\Sdk\Messaging\MessagingClient;

$sb = new MessagingClient(getenv('SIGNALBIRD_DOMAIN_KEY'));

$res = $sb->sendSms([
    'to'    => '+905321234567',
    'class' => 'transactional',
    'body'  => 'Doğrulama kodunuz: 482913',
]);

if ($res['ok']) { $id = $res['data']['id']; }
else            { $code = $res['code']; }

Kurucu: new MessagingClient(string $domainKey, ?string $baseUrl = null, int $timeout = 15, bool $throwOnError = false).

Laravel

bash
# .env
SIGNALBIRD_DOMAIN_KEY=sb_xxxxxxxxxxxxxxxxxxxxxxxx
# SIGNALBIRD_MESSAGING_URL=https://signalbird.io/api   # opsiyonel
php
use Signalbird\Sdk\Facades\Signalbird;

$res = Signalbird::messaging()->sendPush([
    'to'      => 'external:user-1042',
    'class'   => 'commercial',
    'subject' => 'Hafta sonu indirimi',
    'body'    => 'Sepetinizdeki ürünlerde %20',
]);

Signalbird::messaging() config('signalbird.messaging_key') ile kurulur ve tekildir. Telsiz metotları (Signalbird::info() vb.) etkilenmez.

Metotlar

Node ve PHP'de metot adları birebir aynıdır; Node nesne alır, PHP dizi. Alan adları API ile aynıdır (snake_case) - SDK yeniden adlandırma yapmaz.

Şablon seçimi iki biçimdedir ve biri yeterlidir: template (panelde yazan AD, büyük/küçük harfe duyarsız) ya da template_id (sayı). Şablon verdiğinizde subject ve body isteğe bağlıdır - konu şablondan gelir, ama siz konu yazdıysanız sizinki kazanır. Bulunamayan şablon 422 TEMPLATE_NOT_FOUND döner; gövdesiz posta gönderilmez.

Alan Metot HTTP Not
Gönderim sendEmail({ to, class, subject?, body?, template?, template_id?, vars?, sending_domain_id?, sending_address_id?, contact_id?, from_name?, reply_to?, attachments?, module_key? }) POST /v1/email/send 202 → { id, status, units }
Gönderim sendSms({ to, class, body, brand_id?, contact_id? }) POST /v1/sms/send E.164 numara
Gönderim previewSms(body) POST /v1/sms/preview Parça/karakter hesabı, kota harcamaz
Gönderim sendPush({ to, class, subject, body, vars?, contact_id? }) POST /v1/push/send to: token, contact:<id> ya da external:<id>
Kişiler listContacts({ q?, list_id?, page?, per_page? }) GET /v1/contacts Sayfalı
Kişiler createContact({ email?, phone?, first_name?, last_name?, attributes?, list_ids?, consent_source?, consent_text? }) POST /v1/contacts
Kişiler updateContact(id, partial) PATCH /v1/contacts/{id}
Kişiler deleteContact(id) DELETE /v1/contacts/{id}
Kişiler bulkContacts({ contacts, list_id?, consent_source?, consent_text? }) POST /v1/contacts/bulk 1000'lik parçalar, sıralı
Listeler listContactLists() GET /v1/contact-lists
Listeler createContactList({ name, description? }) POST /v1/contact-lists
Listeler deleteContactList(id) DELETE /v1/contact-lists/{id}
Kampanyalar listCampaigns({ status?, channel?, page?, per_page? }) GET /v1/campaigns
Kampanyalar createCampaign({ name, channel, domain_id, list_id | segment_id, body, subject?, sending_domain_id?, brand_id?, scheduled_at?, metadata?, external_ref? }) POST /v1/campaigns 202 → { batch, class, summary }
Kampanyalar getCampaign(id) GET /v1/campaigns/{id} { batch, status_breakdown, jobs }
Kampanyalar cancelCampaign(id) POST /v1/campaigns/{id}/cancel Bitmişse ALREADY_FINISHED
Kampanyalar listCampaignMessages(id, { page?, per_page?, status? }) GET /v1/campaigns/{id}/messages Sayfalı
Kampanyalar iterateCampaignMessages(id, query?) (çoklu GET) Node: for await; PHP: Generator. Sayfa alınamazsa fırlatır
Mesajlar listMessages({ status?, channel?, batch_id?, page?, per_page? }) GET /v1/messages Alıcı maskeli döner
Mesajlar getMessage(id) GET /v1/messages/{id}

class zorunludur ve varsayılanı yoktur. transactional mı commercial mi olduğuna SDK karar vermez - bu hukuki bir kapıdır. Ticari iletilerde onay (NO_CONSENT) ve bastırma listesi (SUPPRESSED) uygulanır.

Sonuç biçimi ve hata yönetimi

Her metot aynı biçimde döner:

typescript
// Node
type SbResult<T> =
  | { ok: true;  status: number; data: T }
  | { ok: false; status: number; code: string; message: string; data?: unknown }
php
// PHP
['ok' => bool, 'status' => int, 'data' => ?array, 'code' => ?string, 'message' => ?string]

Sessiz hata varsayılandır. Ağ koparsa, kota dolarsa ya da alıcı bastırma listesindeyse çağrı ok: false + code döner ve uygulamanız çalışmaya devam eder. Sipariş akışını bir e-posta hatası çökertmemelidir.

Geliştirmede ya da hatanın yukarı taşınmasını istediğiniz yerlerde throwOnError: true:

typescript
const sb = new SignalbirdMessaging({ domainKey, throwOnError: true })
try {
  await sb.sendEmail({ … })
} catch (e) {
  if (e instanceof SignalbirdError) console.error(e.code, e.status, e.data)
}
php
$sb = new MessagingClient($key, throwOnError: true);
try {
    $sb->sendEmail([...]);
} catch (\Signalbird\Sdk\SignalbirdException $e) {
    $e->getErrorCode();   // 'LIMIT_REACHED'
    $e->getStatus();      // 429
    $e->getBody();        // API'nin ham yanıtı
}

Yeniden deneme yoktur. Aynı iletiyi iki kez göndermek, hiç göndermemekten pahalıdır; NETWORK_ERROR / TIMEOUT sonrası tekrar kararı çağıranındır.

Toplu kişi yükleme

typescript
const list = await sb.createContactList({ name: 'Bülten - Ağustos' })

const res = await sb.bulkContacts({
  list_id: list.data.id,
  consent_source: 'web-form',
  consent_text: 'Kampanya e-postası almayı kabul ediyorum.',
  contacts: rows.map(r => ({
    email: r.email,
    first_name: r.name,
    attributes: { plan: r.plan },
  })),
})

// { imported: 4120, updated: 310, skipped: [{ email: 'x', reason: 'invalid' }, …] }

bulkContacts diziyi 1000'lik parçalara böler ve sırayla gönderir (paralel değil: aynı e-posta iki parçada da varsa yarış olmasın). Sayımlar tek yanıtta birleşir. Bir parça başarısız olursa orada durulur; o ana kadar biriken sayım data içinde döner - kaç kişinin işlendiğini görürsünüz.

Kampanya akışı

typescript
// 1. liste + kişiler
const list = await sb.createContactList({ name: 'Yeni özellik duyurusu' })
await sb.bulkContacts({ list_id: list.data.id, contacts })

// 2. kampanyayı aç (hemen ya da zamanlanmış)
const created = await sb.createCampaign({
  name: 'Ağustos duyurusu',
  channel: 'email',
  domain_id: 7,               // TXT ile doğrulanmış alan adınız (zorunlu)
  list_id: list.data.id,
  subject: 'Yeni: takvim entegrasyonu',
  body: '<p>Merhaba {{first_name}}, …</p>',
  scheduled_at: '2026-08-21T09:00:00+03:00',
  external_ref: 'crm-cmp-77',
})
// created.data.summary → { total, queued, skipped, stopped_reason }

// 3. durumu izle
const detail = await sb.getCampaign(created.data.batch.id)
// detail.data.status_breakdown → { queued: 120, sent: 3900, bounced: 12, … }

// 4. mesajları gez
for await (const m of sb.iterateCampaignMessages(created.data.batch.id, { status: 'bounced' })) {
  console.log(m.recipient, m.last_code)
}

listCampaignMessages tek sayfa döner; iterateCampaignMessages sayfaları kendisi gezer ve bir sayfa alınamazsa fırlatır - sessiz yarım liste "hepsi bu" sanılır, o daha tehlikelidir. cancelCampaign yalnız bitmemiş partilerde çalışır.

Webhook doğrulama

Panelde Konsol → Webhooks ile bir uç nokta tanımlayın. Her teslimat şu başlıklarla gelir:

Başlık İçerik
X-Signalbird-Event message.queued · sent · delivered · bounced · failed · opened · clicked · unsubscribed · complained · campaign.finished · campaign.canceled
X-Signalbird-Delivery Teslimat kimliği (tekrarları ayıklamak için)
X-Signalbird-Signature sha256=<hex hmac-sha256(ham_gövde, secret)>
X-Signalbird-Timestamp Unix zaman damgası

Gövde: { id, type, occurred_at, team_id, data: { message_id, batch_id, channel, contact_id, external_ref, batch_external_ref, status, code, detail, recipient } }. 2xx dışı yanıtta 3 kez yeniden denenir (1 dk / 5 dk / 30 dk).

İmza ham gövde üzerinden hesaplanır. JSON'u ayrıştırıp yeniden serileştirmek anahtar sırasını değiştirir ve imzayı bozar.

Express

typescript
import express from 'express'
import { verifyWebhook } from 'signalbird'

app.post('/webhooks/signalbird', express.raw({ type: 'application/json' }), (req, res) => {
  const ok = verifyWebhook(req.body, req.header('X-Signalbird-Signature'), process.env.SIGNALBIRD_WEBHOOK_SECRET!)
  if (!ok) return res.status(401).end()

  const event = JSON.parse(req.body.toString('utf8'))
  if (event.type === 'message.bounced') { /* … */ }
  res.status(204).end()
})

Laravel

php
use Illuminate\Http\Request;
use Signalbird\Sdk\Messaging\Webhook;

Route::post('/webhooks/signalbird', function (Request $request) {
    $ok = Webhook::verify(
        $request->getContent(),                       // ham gövde
        $request->header('X-Signalbird-Signature'),
        config('services.signalbird.webhook_secret'),
    );
    abort_unless($ok, 401);

    $event = $request->json()->all();
    // $event['type'], $event['data']['message_id'] …
    return response()->noContent();
})->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);

Hata kodları

Kod Anlamı
API_KEY_MISSING · API_KEY_INVALID Anahtar yok, yanlış ya da iptal edilmiş
API_KEY_SCOPE Anahtarda bu işlem için kapsam yok
API_KEY_IP_BLOCKED İstek, anahtarın IP izin listesi dışından geldi
API_KEY_NO_TEAM Anahtarın bağlı olduğu takım yok / pasif
MODULE_DISABLED Paketinizde bu kanal (email/sms/push) yok
LIMIT_REACHED Aylık kota doldu
OVERAGE_CEILING_REACHED Aşım tavanına ulaşıldı - panelden yükseltilebilir
SUPPRESSED Alıcı bastırma listesinde (bounce / şikâyet / abonelikten çıkma)
NO_CONSENT Ticari ileti için kişinin onayı yok
NO_SENDING_DOMAIN Doğrulanmış gönderim alanı yok
INVALID_PHONE Numara E.164 biçiminde değil
LIST_NOT_FOUND list_id bu takıma ait değil
NO_RECIPIENTS Listede gönderilebilir kişi yok
ALREADY_FINISHED Kampanya bitmiş, iptal edilemez
VALIDATION_ERROR Alan doğrulaması (HTTP 422); ayrıntı data.errors
NETWORK_ERROR · TIMEOUT İstek gitmedi ya da zaman aşımı - tekrar kararı sizin