承接 sendly/sendly-php 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

sendly/sendly-php

Composer 安装命令:

composer require sendly/sendly-php

包简介

Official PHP SDK for the Sendly SMS API

README 文档

README

Sendly PHP SDK

Packagist license

Sendly PHP SDK

Official PHP SDK for the Sendly SMS API.

Requirements

  • PHP 8.1+
  • Composer

Installation

composer require sendly/sendly-php

Quick Start

<?php

use Sendly\Sendly;

$client = new Sendly('sk_live_v1_your_api_key');

// Send an SMS
$message = $client->messages()->send(
    '+15551234567',
    'Hello from Sendly!'
);

echo $message->id;     // "msg_abc123"
echo $message->status; // "queued"

Prerequisites for Live Messaging

Before sending live SMS messages, you need:

  1. Business Verification - Complete verification in the Sendly dashboard

    • International: Instant approval (just provide Sender ID)
    • US/Canada: Requires carrier approval (3-7 business days)
  2. Credits - Add credits to your account

    • Test keys (sk_test_*) work without credits (sandbox mode)
    • Live keys (sk_live_*) require credits for each message
  3. Live API Key - Generate after verification + credits

    • Dashboard → API Keys → Create Live Key

Test vs Live Keys

Key Type Prefix Credits Required Verification Required Use Case
Test sk_test_v1_* No No Development, testing
Live sk_live_v1_* Yes Yes Production messaging

Note: You can start development immediately with a test key. Messages to sandbox test numbers are free and don't require verification.

Configuration

$client = new Sendly('sk_live_v1_xxx', [
    'baseUrl' => 'https://sendly.live/api/v1',
    'timeout' => 60,
    'maxRetries' => 5,
]);

Messages

Send an SMS

// Marketing message (default)
$message = $client->messages()->send(
    '+15551234567',
    'Check out our new features!'
);

// Transactional message (bypasses quiet hours)
$message = $client->messages()->send(
    '+15551234567',
    'Your verification code is: 123456',
    'transactional'
);

// With custom metadata (max 4KB)
$message = $client->messages()->send(
    '+15551234567',
    'Your order #12345 has shipped!',
    null, // messageType
    ['order_id' => '12345', 'customer_id' => 'cust_abc']
);

// Send from one of your owned numbers (or an alphanumeric sender ID).
// Omit `from` to use your default sender.
$message = $client->messages()->send([
    'to' => '+15551234567',
    'text' => 'Hello from our team!',
    'from' => '+447111111111',
]);

echo $message->id;
echo $message->status;
echo $message->creditsUsed;

List Messages

// Basic listing
$messages = $client->messages()->list(['limit' => 50]);

foreach ($messages as $msg) {
    echo $msg->to;
}

// With filters
$messages = $client->messages()->list([
    'status' => 'delivered',
    'to' => '+15551234567',
    'limit' => 20,
    'offset' => 0,
]);

// Pagination info
echo $messages->total;
echo $messages->hasMore;

Get a Message

$message = $client->messages()->get('msg_abc123');

echo $message->to;
echo $message->text;
echo $message->status;
echo $message->deliveredAt?->format('Y-m-d H:i:s');

Scheduling Messages

// Schedule a message for future delivery
$scheduled = $client->messages()->schedule(
    '+15551234567',
    'Your appointment is tomorrow!',
    '2025-01-15T10:00:00Z'
);

echo $scheduled->id;
echo $scheduled->scheduledAt;

// List scheduled messages
$result = $client->messages()->listScheduled();
foreach ($result as $msg) {
    echo "{$msg->id}: {$msg->scheduledAt}\n";
}

// Get a specific scheduled message
$msg = $client->messages()->getScheduled('sched_xxx');

// Cancel a scheduled message (refunds credits)
$result = $client->messages()->cancelScheduled('sched_xxx');
echo "Refunded: {$result->creditsRefunded} credits";

Batch Messages

// Send multiple messages in one API call (up to 1000)
$batch = $client->messages()->sendBatch([
    ['to' => '+15551234567', 'text' => 'Hello User 1!'],
    ['to' => '+15559876543', 'text' => 'Hello User 2!'],
    ['to' => '+15551112222', 'text' => 'Hello User 3!'],
]);

echo $batch->batchId;
echo "Queued: {$batch->queued}";
echo "Failed: {$batch->failed}";
echo "Credits used: {$batch->creditsUsed}";

// Get batch status
$status = $client->messages()->getBatch('batch_xxx');

// List all batches
$batches = $client->messages()->listBatches();

// Preview batch (dry run) - validates without sending
$preview = $client->messages()->previewBatch([
    ['to' => '+15551234567', 'text' => 'Hello User 1!'],
    ['to' => '+447700900123', 'text' => 'Hello UK!'],
]);
echo "Total credits needed: {$preview->totalCredits}";
echo "Valid: {$preview->valid}, Invalid: {$preview->invalid}";

Iterate All Messages

// Auto-pagination with generator
foreach ($client->messages()->each() as $message) {
    echo "{$message->id}: {$message->to}\n";
}

// With filters
foreach ($client->messages()->each(['status' => 'delivered']) as $message) {
    echo "Delivered: {$message->id}\n";
}

Group MMS

Send one MMS to 2-8 recipients (US/Canada only). Everyone shares a single thread and replies fan out to all participants. Group messaging is an A2P 10DLC capability — the sending number must be an MMS-enabled, 10DLC-registered number you own. Requires the group_mms feature (and enable_mms for media) to be enabled for your account.

$group = $client->messages()->sendGroup([
    'to' => ['+14155551234', '+14155555678'],
    'text' => 'Hey team - quick sync at noon?',
    // 'from' => '+15125550100',           // optional; omit to use your default sender
    // 'mediaUrls' => ['https://.../a.jpg'], // optional; text or mediaUrls required
    // 'messageType' => 'transactional',     // default; use 'marketing' for quiet hours
]);

echo $group['id'];                // "msg_xxx"
echo $group['status'];            // "sent" (or "delivered" when simulated)
echo $group['group_message_id'];  // "grp_xxx" (present on live sends)

AI Message Enhancement

Rewrite a draft into a single, polished SMS segment (≤160 characters) and get a short explanation of what changed. Pass messageType to steer the rewrite; with no text it generates a suitable message for that type instead. At least one of text or messageType is required. Requires the ai_classification feature; when AI is unavailable the original text is returned with an empty explanation.

$result = $client->messages()->enhance(
    'hey come check out our sale this weekend',
    'marketing'
);

echo $result['enhanced'];     // polished, ≤160-char rewrite
echo $result['explanation'];  // what changed and why
echo $result['model'] ?? '';  // model used, when available

Numbers

List the phone numbers on your account, inspect one, make a number your default sender or keep a number scheduled for release, and release a number.

// List your numbers
$result = $client->numbers()->list();
foreach ($result['numbers'] as $n) {
    echo "{$n['phoneNumber']}{$n['status']} ({$n['phoneNumberType']})\n";
}

// Get a single number (includes `isDefault`)
$number = $client->numbers()->get('num_abc123');
echo $number['phoneNumber']; // "+15551234567"
echo $number['isDefault'] ? 'default sender' : 'not default';

// Make a number your workspace's default sender (must be active)
$client->numbers()->update('num_abc123', ['isDefault' => true]);

// "Keep this number": undo a scheduled period-end release
$client->numbers()->update('num_abc123', ['pendingCancellation' => false]);

// Release a number. A live paid purchase is cancelled at period end;
// anything else is released immediately.
$result = $client->numbers()->release('num_abc123');
if ($result['scheduled'] ?? false) {
    echo "Releases at {$result['scheduledReleaseAt']}";
} else {
    echo 'Released';
}

Short Links (URL Shortening)

Mint branded short links for a destination URL, list them with click analytics, and disable (kill) an individual link. Branded, owned-domain short links improve deliverability — carriers filter public shorteners — and give you click data.

Note: URL shortening is gated behind the url_shortener rollout flag and is not yet generally available; until the flag is enabled for your account the endpoints read as absent and calls throw NotFoundException (HTTP 404).

// Shorten a URL
$link = $client->links()->create('https://example.com/spring-sale?utm_source=sms');
echo $link['code'];      // "Ab3xY7"
echo $link['shortUrl'];  // "https://sendly.live/l/Ab3xY7"

// List your links with click counts
$result = $client->links()->list(['limit' => 20]);
foreach ($result['links'] as $l) {
    echo "{$l['shortUrl']} -> {$l['destinationUrl']} ({$l['clickCount']} clicks)\n";
}

// Kill a link (its redirect returns 404 until re-enabled)
$client->links()->disable('Ab3xY7');

// Re-enable it
$client->links()->enable('Ab3xY7');

Webhooks

// Create a webhook endpoint
$webhook = $client->webhooks()->create(
    'https://example.com/webhooks/sendly',
    ['message.delivered', 'message.failed']
);

echo $webhook->id;
echo $webhook->secret; // Store securely!

// List all webhooks
$webhooks = $client->webhooks()->list();

// Get a specific webhook
$wh = $client->webhooks()->get('whk_xxx');

// Update a webhook
$client->webhooks()->update('whk_xxx', [
    'url' => 'https://new-endpoint.example.com/webhook',
    'events' => ['message.delivered', 'message.failed', 'message.sent']
]);

// Test a webhook
$result = $client->webhooks()->test('whk_xxx');

// Rotate webhook secret
$rotation = $client->webhooks()->rotateSecret('whk_xxx');

// Delete a webhook
$client->webhooks()->delete('whk_xxx');

// List available webhook event types
$eventTypes = $client->webhooks()->listEventTypes();
foreach ($eventTypes as $eventType) {
    echo "Event: {$eventType}\n";
}

Account & Credits

// Get account information
$account = $client->account()->get();
echo $account->email;

// Check credit balance
$credits = $client->account()->getCredits();
echo "Available: {$credits->availableBalance} credits";
echo "Reserved: {$credits->reservedBalance} credits";
echo "Total: {$credits->balance} credits";

// View credit transaction history
$transactions = $client->account()->getCreditTransactions();
foreach ($transactions as $tx) {
    echo "{$tx->type}: {$tx->amount} credits - {$tx->description}\n";
}

// List API keys
$keys = $client->account()->listApiKeys();
foreach ($keys as $key) {
    echo "{$key->name}: {$key->prefix}*** ({$key->type})\n";
}

// Get a specific API key
$key = $client->account()->getApiKey('key_xxx');

// Get API key usage stats
$usage = $client->account()->getApiKeyUsage('key_xxx');
echo "Messages sent: {$usage->messagesSent}";

// Create a new API key
$newKey = $client->account()->createApiKey('Production Key', [
    'expiresAt' => '2027-01-01T00:00:00Z', // optional
]);
echo "New key: {$newKey['key']}"; // Only shown once!

// Rotate an API key (old key stays valid for a grace period, default 24h)
$rotation = $client->account()->rotateApiKey('key_xxx', [
    'gracePeriodHours' => 48, // optional; 24-168 inclusive
]);
echo "New key: {$rotation['newKey']['key']}"; // Only shown once!
echo $rotation['message'];                     // "Old key will expire in 48 hours"

// Revoke an API key
$client->account()->revokeApiKey('key_xxx');

Error Handling

use Sendly\Exceptions\AuthenticationException;
use Sendly\Exceptions\RateLimitException;
use Sendly\Exceptions\InsufficientCreditsException;
use Sendly\Exceptions\ValidationException;
use Sendly\Exceptions\NotFoundException;
use Sendly\Exceptions\NetworkException;
use Sendly\Exceptions\SendlyException;

try {
    $message = $client->messages()->send('+15551234567', 'Hello!');
} catch (AuthenticationException $e) {
    // Invalid API key
} catch (RateLimitException $e) {
    // Rate limit exceeded
    echo "Retry after: " . $e->getRetryAfter() . " seconds";
} catch (InsufficientCreditsException $e) {
    // Add more credits
} catch (ValidationException $e) {
    // Invalid request
    print_r($e->getDetails());
} catch (NotFoundException $e) {
    // Resource not found
} catch (NetworkException $e) {
    // Network error
} catch (SendlyException $e) {
    // Other error
    echo $e->getMessage();
    echo $e->getErrorCode();
}

Message Object

$message->id;           // Unique identifier
$message->to;           // Recipient phone number
$message->text;         // Message content
$message->status;       // queued, sending, sent, delivered, failed
$message->creditsUsed;  // Credits consumed
$message->createdAt;    // DateTimeImmutable
$message->updatedAt;    // DateTimeImmutable
$message->deliveredAt;  // DateTimeImmutable|null
$message->errorCode;    // string|null
$message->errorMessage; // string|null

// Helper methods
$message->isDelivered(); // bool
$message->isFailed();    // bool
$message->isPending();   // bool

// Convert to array
$message->toArray();

Message Status

Status Description
queued Message is queued for delivery
sending Message is being sent
sent Message was sent to carrier
delivered Message was delivered
failed Message delivery failed

Pricing Tiers

Tier Countries Credits per SMS
Domestic US, CA 2
Tier 1 GB, PL, IN, etc. 8
Tier 2 FR, JP, AU, etc. 12
Tier 3 DE, IT, MX, etc. 16

Sandbox Testing

Use test API keys (sk_test_v1_xxx) with these test numbers:

Number Behavior
+15005550000 Success (instant)
+15005550001 Fails: invalid_number
+15005550002 Fails: unroutable_destination
+15005550003 Fails: queue_full
+15005550004 Fails: rate_limit_exceeded
+15005550006 Fails: carrier_violation

Enterprise

The Enterprise API lets you programmatically manage workspaces, verification, credits, and API keys for multi-tenant platforms. Requires an enterprise master key (sk_live_v1_master_*).

Quick Provision

Create a fully configured workspace in a single call:

use Sendly\Sendly;

$client = new Sendly('sk_live_v1_master_YOUR_KEY');

$result = $client->enterprise->provision([
    'name' => 'Acme Insurance - Austin',
    'sourceWorkspaceId' => 'ws_verified',
    'creditAmount' => 5000,
    'creditSourceWorkspaceId' => 'SOURCE_WORKSPACE_ID',
    'keyName' => 'Production',
    'keyType' => 'live',
    'generateOptInPage' => true,
]);

echo $result['workspace']['id'];
echo $result['key']['key'];

Three provisioning modes:

Mode Params Description
Inherit sourceWorkspaceId Shares toll-free number from verified workspace
Inherit + New Number sourceWorkspaceId + inheritWithNewNumber => true Copies business info, purchases new number
Fresh verification => [...] Full business details, new number + carrier approval

Workspace Management

$ws = $client->enterprise->workspaces->create('Acme Insurance');
$list = $client->enterprise->workspaces->list();
$detail = $client->enterprise->workspaces->get('ws_xxx');
$client->enterprise->workspaces->delete('ws_xxx');

Credits & API Keys

$client->enterprise->workspaces->transferCredits('ws_dest', [
    'sourceWorkspaceId' => 'ws_source',
    'amount' => 5000,
]);

$key = $client->enterprise->workspaces->createKey('ws_xxx', [
    'name' => 'Production',
    'type' => 'live',
]);
echo $key['key'];

$client->enterprise->workspaces->revokeKey('ws_xxx', 'key_abc');

Webhooks & Analytics

$client->enterprise->webhooks->set('https://yourapp.com/webhooks');
$overview = $client->enterprise->analytics->overview();
$messages = $client->enterprise->analytics->messages('30d');
$delivery = $client->enterprise->analytics->delivery();

Full enterprise docs: sendly.live/docs/enterprise

License

MIT

sendly/sendly-php 适用场景与选型建议

sendly/sendly-php 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 308 次下载、GitHub Stars 达 0, 最近一次更新时间为 2025 年 12 月 19 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「api」 「sms」 「sdk」 「messaging」 「sendly」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 sendly/sendly-php 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 sendly/sendly-php 我们能提供哪些服务?
定制开发 / 二次开发

基于 sendly/sendly-php 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 308
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 0
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-12-19