定制 awaisjameel/texto 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

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

awaisjameel/texto

Composer 安装命令:

composer require awaisjameel/texto

包简介

A Laravel package to handle messaging (SMS, MMS, WhatsApp) using Twilio, Telnyx, or Meta's Cloud API

README 文档

README

** Unified, extensible Laravel gateway for sending & receiving SMS/MMS over Twilio & Telnyx, plus WhatsApp through Meta's Cloud API. Batteries included: queueing, retries, events, webhooks, polling, typed value objects.**

Latest Version on Packagist Tests Downloads License

Texto provides a unified, extensible Laravel package for carrier-grade SMS/MMS and WhatsApp messaging. Built for Laravel 10–13 (PHP 8.1+), it abstracts provider complexities (Twilio, Telnyx, Meta's WhatsApp Cloud API) through consistent contracts and value objects, enabling seamless integration with enterprise messaging workflows.

Key Features:

  • Unified API: Single interface for sending SMS/MMS across multiple providers
  • Message Persistence: Automatic storage of sent and received messages with full metadata
  • Status Tracking: Real-time delivery status updates via webhooks and fallback polling
  • Event-Driven: Rich event system for analytics, notifications, and custom automation
  • Advanced Twilio Support: Conversations API with auto-provisioned content templates
  • Reliability: Exponential backoff retry, queue-based async processing, and graceful degradation
  • Security: Webhook signature validation, rate limiting, and shared secret protection
  • Extensibility: Plugin architecture for adding new messaging providers

Table of Contents

  1. Motivation & Philosophy
  2. Feature Overview
  3. Quick Start
  4. Installation
  5. Configuration (config/texto.php)
  6. Usage Examples
  7. Queueing & Async Flow
  8. Events & Observability
  9. Data Model & Persistence
  10. Webhooks (Inbound + Status)
  11. Security (Signatures, Secrets, Rate Limiting)
  12. Status Polling (Adaptive Fallback)
  13. Retry & Backoff Strategy
  14. Twilio Conversations & Content Templates
  15. Overriding a Built‑in Driver
  16. API Reference (Value Objects, Enums, Events, Interfaces)
  17. Console Commands
  18. Testing, Fakes & Local Development
  19. Architecture Overview
  20. Troubleshooting & FAQ
  21. Performance Considerations & Best Practices
  22. Roadmap
  23. Contributing
  24. Security Policy
  25. Migration Guide
  26. License & Credits

Provider deep dives: Twilio API reference · Telnyx guide · WhatsApp Cloud API guide

1. Motivation & Philosophy

Building messaging features in Laravel applications often involves wrestling with provider-specific APIs that have inconsistent interfaces, error handling, and webhook formats. Without proper abstraction, code becomes littered with conditional logic that breaks when switching providers or adding new ones.

Texto solves this by providing a clean, consistent interface that:

  • Eliminates Provider Lock-in: Switch between Twilio, Telnyx, or custom providers with minimal code changes
  • Ensures Type Safety: Strongly typed enums and value objects prevent common mistakes
  • Promotes Clean Architecture: Clear separation between sending, persistence, and status tracking
  • Enables Observability: Comprehensive events and logging for monitoring and debugging
  • Handles Edge Cases: Built-in retry logic, queueing, and fallback polling for reliability
  • Prioritizes Security: Webhook validation, rate limiting, and shared secret protection

The philosophy is simple: messaging should be a first-class citizen in your Laravel app, not an afterthought that requires constant maintenance.

2. Feature Overview

Core Messaging

  • SMS & MMS Support: Send text messages and media attachments through Twilio and Telnyx, plus WhatsApp text/media/template messages via Meta's Cloud API
  • Unified API: Single Texto::send() method works across all providers
  • Phone Number Validation: Automatic E.164 formatting and validation using libphonenumber
  • Media Handling: Support for multiple media URLs per message

Reliability & Performance

  • Queue Integration: Async message sending with Laravel queues for high-throughput applications
  • Retry Logic: Exponential backoff for transient API failures
  • Status Polling: Fallback polling when webhooks are delayed or unavailable
  • Rate Limiting: Built-in protection against webhook abuse

Advanced Twilio Features

  • Conversations API: Rich conversation management with participant tracking
  • Content Templates: Auto-provisioning and reuse of SMS/MMS templates
  • Template Variables: Dynamic content insertion for personalized messaging

Observability & Events

  • Event System: Four key events (MessageSent, MessageReceived, MessageFailed, MessageStatusUpdated)
  • Structured Logging: Comprehensive logging for debugging and monitoring
  • Metadata Capture: Rich metadata storage including costs, segments, and custom data

Security & Compliance

  • Webhook Validation: Cryptographic signature verification for Twilio (HMAC‑SHA1), Telnyx (Ed25519), and WhatsApp (HMAC‑SHA256)
  • Rate Limiting: Configurable per-minute limits on webhook endpoints
  • Data Persistence: Optional message storage with configurable retention

Developer Experience

  • Type Safety: Strongly typed enums and value objects
  • Extensible Architecture: Plugin system for custom providers
  • Testing Support: Fake drivers and webhook validation skipping for tests
  • Laravel Integration: Service provider auto-discovery and facade registration

3. Quick Start

composer require awaisjameel/texto
php artisan texto:install   # publishes config + migration & runs migrate

php artisan texto:test-send +15551234567 "Hello from Texto"
use Texto; // facade alias configured automatically

Texto::send('+15551234567', 'Hello world');

4. Installation

Prerequisites

Before installing Texto, ensure your Laravel application meets these requirements:

  • Laravel: 10.x, 11.x, 12.x, or 13.x
  • PHP: 8.1 or higher (8.2+ recommended)
  • Database: MySQL, PostgreSQL, SQLite, or SQL Server
  • Queue System: Any Laravel-supported queue driver (Database recommended for production)
  • PHP Extensions: ext-sodium for Telnyx signature verification

Quick Installation

The fastest way to get started:

composer require awaisjameel/texto
php artisan texto:install

This command will:

  • Publish the configuration file to config/texto.php
  • Publish and run the database migration
  • Register the service provider and facade

Manual Installation

For more control over the installation process:

# 1. Install the package
composer require awaisjameel/texto

# 2. Publish configuration (optional - auto-published by texto:install)
php artisan vendor:publish --tag=texto-config

# 3. Publish migration (optional - auto-published by texto:install)
php artisan vendor:publish --tag=texto-migrations

# 4. Run migrations
php artisan migrate

Provider Setup

If you're not using package auto-discovery, add the service provider to config/app.php:

'providers' => [
    // ... other providers
    Awaisjameel\Texto\TextoServiceProvider::class,
],

'aliases' => [
    // ... other aliases
    'Texto' => Awaisjameel\Texto\Facades\Texto::class,
],

Verification

After installation, verify everything is working:

php artisan texto:test-send +15551234567 "Hello from Texto!"

This will send a test message using your configured provider and settings.

Environment Variables

# Core
TEXTO_DRIVER=twilio                 # twilio | telnyx | whatsapp
TEXTO_STORE_MESSAGES=true           # disable to skip DB persistence
TEXTO_QUEUE=false                   # true => SendMessageJob async
TEXTO_RETRY_ATTEMPTS=3
TEXTO_RETRY_BACKOFF_START=200       # ms
TEXTO_WEBHOOK_SECRET=               # optional shared secret header
TEXTO_WEBHOOK_RATE_LIMIT=300        # webhook requests per minute per endpoint
TEXTO_DEFAULT_REGION=US             # for parsing non-E.164 input

# Status polling (optional)
TEXTO_STATUS_POLL_ENABLED=false
TEXTO_STATUS_POLL_MIN_AGE=60
TEXTO_STATUS_POLL_MAX_ATTEMPTS=5
TEXTO_STATUS_POLL_QUEUED_MAX_ATTEMPTS=2
TEXTO_STATUS_POLL_BACKOFF=300
TEXTO_STATUS_POLL_BATCH=100

# Twilio
TWILIO_ACCOUNT_SID=...
TWILIO_AUTH_TOKEN=...
TWILIO_FROM_NUMBER=+15550001111
TWILIO_USE_CONVERSATIONS=true       # opt in to the classic Messages API by setting false (default: Conversations API)
TWILIO_SMS_TEMPLATE_FRIENDLY_NAME=texto_sms_template
TWILIO_MMS_TEMPLATE_FRIENDLY_NAME=texto_mms_template
TWILIO_SMS_TEMPLATE_SID=            # optional HX... SID; skips template auto-discovery
TWILIO_MMS_TEMPLATE_SID=            # optional HX... SID; skips template auto-discovery
TWILIO_CONVERSATION_PREFIX=Texto
TWILIO_CONVERSATION_WEBHOOK_URL=    # optional override
TWILIO_CONVERSATION_CACHE_TTL=604800 # seconds a conversation SID is reused per (from, to); 0 disables
TWILIO_HTTP_TIMEOUT=30              # seconds for outbound API calls

# Telnyx
TELNYX_API_KEY=...
TELNYX_MESSAGING_PROFILE_ID=...
TELNYX_FROM_NUMBER=+15550002222
TELNYX_WEBHOOK_SECRET=base64-encoded-public-key
TELNYX_WEBHOOK_TOLERANCE=300        # max webhook timestamp age in seconds
TELNYX_HTTP_TIMEOUT=30              # seconds for outbound API calls

# WhatsApp Cloud API
WHATSAPP_ACCESS_TOKEN=...
WHATSAPP_PHONE_NUMBER_ID=...
WHATSAPP_APP_SECRET=...
WHATSAPP_VERIFY_TOKEN=...
WHATSAPP_FROM_NUMBER=+15550003333   # local sender record only
WHATSAPP_HTTP_TIMEOUT=30            # seconds for outbound API calls

5. Configuration (config/texto.php)

After installation, you'll find the configuration file at config/texto.php. Here's a comprehensive guide to all available options:

Core Settings

Key Default Description
driver 'twilio' Active messaging provider ('twilio', 'telnyx', or 'whatsapp')
store_messages true Whether to persist messages in the database
queue false Enable async message sending via Laravel queues
validation.region 'US' Default region for parsing non‑E.164 phone numbers (TEXTO_DEFAULT_REGION)

Retry Configuration

'retry' => [
    'max_attempts' => env('TEXTO_RETRY_ATTEMPTS', 3),
    'backoff_start_ms' => env('TEXTO_RETRY_BACKOFF_START', 200),
],

Controls exponential backoff retry behavior for failed API calls:

  • max_attempts: Maximum number of retry attempts (default: 3)
  • backoff_start_ms: Initial delay in milliseconds (doubles each retry)

Webhook Security

'webhook' => [
    'secret' => env('TEXTO_WEBHOOK_SECRET'),
    'rate_limit' => env('TEXTO_WEBHOOK_RATE_LIMIT', 300),
],
  • secret: Optional shared secret for webhook authentication
  • rate_limit: Maximum webhook requests per minute, per webhook endpoint (default: 300)

Status Polling (Fallback)

'status_polling' => [
    'enabled' => env('TEXTO_STATUS_POLL_ENABLED', false),
    'min_age_seconds' => env('TEXTO_STATUS_POLL_MIN_AGE', 60),
    'max_attempts' => env('TEXTO_STATUS_POLL_MAX_ATTEMPTS', 5),
    'queued_max_attempts' => env('TEXTO_STATUS_POLL_QUEUED_MAX_ATTEMPTS', 2),
    'backoff_seconds' => env('TEXTO_STATUS_POLL_BACKOFF', 300),
    'batch_limit' => env('TEXTO_STATUS_POLL_BATCH', 100),
],

Configures fallback polling for messages stuck in transient states:

  • enabled: Enable/disable polling (default: false)
  • min_age_seconds: Minimum age before polling starts
  • max_attempts: Maximum polling attempts per message
  • backoff_seconds: Delay between polling attempts

Twilio Configuration

'twilio' => [
    'account_sid' => env('TWILIO_ACCOUNT_SID'),
    'auth_token' => env('TWILIO_AUTH_TOKEN'),
    'from_number' => env('TWILIO_FROM_NUMBER'),
    'use_conversations' => env('TWILIO_USE_CONVERSATIONS', true),
    'timeout' => env('TWILIO_HTTP_TIMEOUT', 30),
    'sms_template_friendly_name' => env('TWILIO_SMS_TEMPLATE_FRIENDLY_NAME', 'texto_sms_template'),
    'mms_template_friendly_name' => env('TWILIO_MMS_TEMPLATE_FRIENDLY_NAME', 'texto_mms_template'),
    // Optional explicit template SIDs (skip auto discovery/creation if provided)
    'sms_template_sid' => env('TWILIO_SMS_TEMPLATE_SID'),
    'mms_template_sid' => env('TWILIO_MMS_TEMPLATE_SID'),
    'conversation_prefix' => env('TWILIO_CONVERSATION_PREFIX', 'Texto'),
    'conversation_webhook_url' => env('TWILIO_CONVERSATION_WEBHOOK_URL'),
    'conversation_cache_ttl' => env('TWILIO_CONVERSATION_CACHE_TTL', 604800),
],

Twilio-specific settings for both classic and Conversations API modes (the base_urls array pinning the Messaging, Conversations, and Content API hosts is also part of this block).

Telnyx Configuration

'telnyx' => [
    'base_url' => env('TELNYX_BASE_URL', 'https://api.telnyx.com/v2/'),
    'api_key' => env('TELNYX_API_KEY'),
    'messaging_profile_id' => env('TELNYX_MESSAGING_PROFILE_ID'),
    'from_number' => env('TELNYX_FROM_NUMBER'),
    'webhook_secret' => env('TELNYX_WEBHOOK_SECRET'),
    'webhook_tolerance_seconds' => env('TELNYX_WEBHOOK_TOLERANCE', 300),
    'timeout' => env('TELNYX_HTTP_TIMEOUT', 30),
],

Telnyx API credentials, messaging profile configuration, the base64-encoded Ed25519 public key used to verify webhook signatures, the maximum accepted webhook timestamp age, and a transport timeout (seconds) for outbound REST calls.

WhatsApp Configuration

Configure access_token, phone_number_id, app_secret, and verify_token in the whatsapp block. WhatsApp uses Meta's signed webhook at /texto/webhook/whatsapp and does not support status polling. See the complete WhatsApp Cloud API guide for setup, templates, media, and webhook configuration.

Testing Configuration

'testing' => [
    'skip_webhook_validation' => env('TEXTO_TESTING_SKIP_WEBHOOK_VALIDATION', false),
],

Settings for testing environments to skip webhook signature validation.

6. Usage Examples

Basic SMS Sending

Send a simple text message:

use Texto;

$result = Texto::send('+15551234567', 'Hello from Texto!');

// Returns SentMessageResult with status, provider ID, etc.
echo $result->status->value; // 'sent'
echo $result->providerMessageId; // 'SM1234567890abcdef'

MMS with Media Attachments

Send messages with images, videos, or other media:

$result = Texto::send('+15551234567', 'Check out this photo!', [
    'media_urls' => [
        'https://example.com/image.jpg',
        'https://example.com/video.mp4'
    ]
]);

Per-Message Driver Override

Temporarily use a different provider for specific messages:

// Send via Telnyx instead of default Twilio
$result = Texto::send('+15551234567', 'Via Telnyx', [
    'driver' => 'telnyx'
]);

// Send via Meta's WhatsApp Cloud API (free-form messages require an open 24-hour service window)
$result = Texto::send('+15551234567', 'Via WhatsApp', [
    'driver' => 'whatsapp',
]);

Custom Sender Number and Metadata

Use different sender numbers and attach custom metadata:

$result = Texto::send('+15551234567', 'Welcome to our service!', [
    'from' => '+15550009999', // Different sender number
    'metadata' => [
        'campaign' => 'welcome_series',
        'user_id' => 12345,
        'priority' => 'high'
    ]
]);

Asynchronous Queue Processing

For high-throughput applications, enable queuing:

// In .env
TEXTO_QUEUE=true

// In code
$result = Texto::send('+15551234567', 'Queued message');
echo $result->status->value; // 'queued'

// Start a queue worker
php artisan queue:work

Controller Response

Return messages directly from controllers (auto-converts to JSON):

class NotificationController extends Controller
{
    public function sendAlert(Request $request)
    {
        $result = Texto::send(
            $request->phone,
            'Alert: ' . $request->message
        );

        // Automatically returns JSON response
        return $result;
    }
}

Event-Driven Processing

Listen to messaging events for analytics and automation:

// In EventServiceProvider
protected $listen = [
    \Awaisjameel\Texto\Events\MessageSent::class => [
        \App\Listeners\LogMessageSent::class,
    ],
    \Awaisjameel\Texto\Events\MessageStatusUpdated::class => [
        \App\Listeners\TrackDeliveryStatus::class,
    ],
];

// Listener example
class TrackDeliveryStatus
{
    public function handle(MessageStatusUpdated $event)
    {
        $result = $event->result;

        // Log delivery metrics
        Log::info('Message delivered', [
            'provider_id' => $result->providerMessageId,
            'delivered_at' => now(),
        ]);
    }
}

Bulk Messaging

Send multiple messages efficiently:

$recipients = ['+15551234567', '+15559876543', '+15551111111'];
$messages = [];

foreach ($recipients as $phone) {
    $messages[] = Texto::send($phone, 'Bulk notification');
}

// Process results
$successful = collect($messages)->where('status.value', 'sent')->count();

International Number Handling

Texto automatically handles international formatting:

// All of these work automatically
$phones = [
    '+1-555-123-4567',     // US format
    '555.123.4567',        // Local format (uses config region)
    '+44 20 7123 4567',    // UK format
    '0912345678',          // National format (parsed with config region)
];

foreach ($phones as $phone) {
    Texto::send($phone, 'International hello!');
}

7. Queueing & Async Flow

  1. In queue mode, Texto::send() stores a queued row (status queued).
  2. Dispatches SendMessageJob with deterministic primary key.
  3. Job invokes Texto::send(... ['queued_job'=>true,'queued_message_id'=>X]) to perform real API send.
  4. Repository upgrades the exact queued record (no racey pattern matching).
  5. Status webhooks or polling complete remaining transitions.

Benefits: immediate API responses, backpressure via Laravel queue, deterministic DB state.

Note: The queued job now includes a snapshot of the active driver configuration (API keys, profile IDs, etc.) so workers and scheduled pollers have the same credentials that were present when the message was enqueued. Ensure your queue transport (e.g., database table, Redis) is appropriately protected since provider secrets travel with the job payload.

8. Events & Observability

Event Fired When Payload
MessageSent Successful provider send SentMessageResult
MessageFailed Send attempt threwTextoSendFailedException SentMessageResult, error message
MessageReceived Inbound webhook parsed WebhookProcessingResult
MessageStatusUpdated Stored message status advanced (webhook) WebhookProcessingResult

Subscribe in EventServiceProvider or use listeners/jobs for analytics, billing, triggers.

Structured logging is emitted at info / debug levels for sends, polling promotions, template initialization, and failures.

9. Data Model & Persistence

Table: texto_messages

Column Notes
direction sent / received
driver twilio / telnyx / whatsapp
from_number / to_number E.164 formatted
body Nullable for pure media inbound
media_urls JSON array
status Normalized enum (queued, sending, sent, delivered, read, received, failed, undelivered, ambiguous)
provider_message_id SID / Telnyx ID (nullable until known)
error_code Provider error (if any)
segments_count (Telnyx) part count
cost_estimate (Telnyx) estimated cost
metadata Arbitrary JSON (includes polling counters, conversation info)
sent_at / received_at / status_updated_at Timestamps

Ambiguous terminal state occurs when polling exhausts attempts without a provider id or final disposition.

10. Webhooks

Auto‑registered routes:

Provider Endpoint Methods
Twilio /texto/webhook/twilio POST (inbound + status)
Telnyx /texto/webhook/telnyx POST (inbound + status)
WhatsApp /texto/webhook/whatsapp GET (Meta subscription verify handshake) + POST (inbound + status, batched)

Each provider publishes inbound and status callbacks to a single endpoint. Texto inspects each payload to determine whether it is an inbound message or a delivery status update, ensuring identical processing across providers. WhatsApp webhooks arrive batched; every entry in the batch is processed individually.

Twilio Conversations events authored by your own configured from_number (the echo of a message Texto itself sent) are recognized as authentic and intentionally ignored, so outbound messages are never re‑recorded as inbound.

Each request passes through RateLimitTextoWebhook – a per‑minute throttle (webhook.rate_limit). Authenticity is enforced by each provider's cryptographic signature inside the handler (Twilio X-Twilio-Signature, Telnyx Ed25519, Meta X-Hub-Signature-256). The VerifyTextoWebhookSecret middleware (X-Texto-Secret header, TEXTO_WEBHOOK_SECRET) is not attached to these routes — providers cannot send custom headers — it is shipped for guarding your own application‑defined endpoints.

Inbound payloads are normalized into WebhookProcessingResult then persisted via EloquentMessageRepository.

11. Security

Mechanism Description
Twilio Signature X-Twilio-Signature validated (HMAC‑SHA1 over the full public URL + sorted POST params). Behind a TLS‑terminating proxy, configure TrustProxies so the public URL is reconstructed.
Telnyx Signature Ed25519 signature validated against the Telnyx public webhook key (ext-sodium), with timestamp tolerance (TELNYX_WEBHOOK_TOLERANCE).
WhatsApp Signature Meta's X-Hub-Signature-256 (HMAC‑SHA256 of the raw body with WHATSAPP_APP_SECRET) validated; GET verify handshake uses WHATSAPP_VERIFY_TOKEN.
Shared Secret Header VerifyTextoWebhookSecret middleware checks X-Texto-Secret against TEXTO_WEBHOOK_SECRET — for your own endpoints, not the packaged provider routes.
Rate Limiting RateLimitTextoWebhook throttles every packaged webhook endpoint per minute.
Phone Parsing All numbers canonicalized using libphonenumber.

Signature validation can be skipped in test environments via TEXTO_TESTING_SKIP_WEBHOOK_VALIDATION=true.

12. Status Polling (Fallback)

Some production networks delay webhooks or they can be transiently disabled. Polling covers that gap.

Enable via TEXTO_STATUS_POLL_ENABLED=true. The service provider auto‑schedules StatusPollJob each minute. Logic:

  • Select messages in transient states (queued|sending|sent) older than min_age_seconds.
  • Skip if attempts exceed caps (max_attempts, or queued_max_attempts for still‑queued w/out provider id).
  • Enforce backoff between polls via last_poll_at metadata.
  • Promote forward‑only (e.g., queued -> sent) while avoiding regressions.
  • Mark terminal on delivered/failed/undelivered. Mark ambiguous when provider id missing after exhaustion.

Metadata counters (poll_attempts, last_poll_at, flags) are merged into metadata JSON for auditability.

13. Retry & Backoff

Retry::exponential() wraps critical provider API calls (send operations). Configured by retry.max_attempts & retry.backoff_start_ms. Delay doubles each attempt until max attempts reached. Exceptions escalate as TextoSendFailedException leading to MessageFailed event emission and (optionally) DB record with status failed.

14. Twilio Conversations & Content Templates

The Conversations API is the default. You opt in to the classic Messages API (one call per SMS, outbound-only) by setting TWILIO_USE_CONVERSATIONS=false. In Conversations mode, Texto:

  1. Lazily initializes Conversations sub‑client.
  2. Ensures (or creates) SMS / MMS Content Templates (friendly names configurable).
  3. Creates (or reuses) a Conversation per send (deduplicates participant collisions & reuses existing).
  4. Optionally attaches per‑conversation webhook (config conversation_webhook_url or metadata override).
  5. Sends message using template variables (splitting long body into up to 5 × 100‑char chunks). Falls back to body variant if template fails.

Captured metadata includes: conversation_sid, conversation_reused, optional conversation_webhook_sid.

Successful sends cache their conversation SID per (from, to) pair (default 7 days, TWILIO_CONVERSATION_CACHE_TTL seconds, 0 disables), so repeat sends to the same recipient skip the conversation setup calls and only POST the message. If a cached conversation was closed or deleted in the meantime, the full setup flow runs once automatically. A conversation created for a send that ultimately fails is deleted again (best effort) so failed sends leave no orphans.

On the classic Messages path, metadata['webhook_url'] is passed to Twilio as the per‑message StatusCallback (mirroring the Telnyx driver), so delivery receipts reach /texto/webhook/twilio without configuring callbacks on the number or messaging service.

Webhook signature note: Twilio signs the public URL of your webhook. If the app runs behind a TLS‑terminating proxy or load balancer, configure Laravel's TrustProxies middleware so fullUrl() reconstructs the public https:// URL — otherwise every signature validation fails.

Per‑Instance Credentials (no container singletons)

The package no longer binds the low‑level provider API adapters (TwilioMessagingApiInterface, TelnyxMessagingApiInterface, …) into the container. Adapters capture credentials at construction, so a boot‑time singleton would silently keep using stale (or another tenant's) credentials under per‑send driver_config overrides. Every sender now constructs its adapters from its own configuration, and each adapter authenticates its HTTP calls with its own credentials. Tests and custom integrations inject fake adapters through the sender constructors, e.g. new TwilioSender($config, $fakeMessagingApi).

The HTTP macro Http::twilio() is still credential‑aware; it omits Basic Auth when credentials are missing so generic tests can stub endpoints without failures.

Content Template Creation Robustness

Template creation logic now tolerates varied (mock) response shapes and will parse a sid from either a direct field or nested content record arrays. No behavioral change is required for production usage; failures still fall back to body‑only send paths.

15. Overriding a Built‑in Driver

extend() swaps the sender used for an existing built‑in driver (twilio, telnyx, or whatsapp). The name must match a recognized Driver enum value — extending an unknown name throws a TextoException. To add a genuinely new provider, add a Driver enum case (see below), not extend().

use Awaisjameel\Texto\Contracts\DriverManagerInterface;
use Awaisjameel\Texto\Contracts\MessageSenderInterface;
use Awaisjameel\Texto\ValueObjects\{PhoneNumber, SentMessageResult};
use Awaisjameel\Texto\Enums\{Driver, Direction, MessageStatus};

// Override the sender resolved for the built-in 'twilio' driver.
app(DriverManagerInterface::class)->extend('twilio', function () {
    return new class implements MessageSenderInterface {
        public function send(PhoneNumber $to, string $body, ?PhoneNumber $from = null, array $mediaUrls = [], array $metadata = []): SentMessageResult {
            // ...call provider API...
            return new SentMessageResult(
                Driver::Twilio,
                Direction::Sent,
                $to,
                $from,
                $body,
                $mediaUrls,
                $metadata,
                MessageStatus::Sent,
                'custom-123'
            );
        }
    };
});

Sender requirements:

  • Implement MessageSenderInterface::send() returning SentMessageResult.
  • Optionally implement PollableMessageSenderInterface::fetchStatus() for polling compatibility.
  • Throw TextoSendFailedException for terminal send failures.

Adding a brand‑new provider: introduce a new case in the Driver enum, map its raw statuses in StatusMapper, and register the sender. Each extend() name is bound to a Driver enum case, so a new provider cannot be resolved until its enum case exists. Contributing it back via PR is encouraged.

16. API Reference

Texto Facade

The main entry point for all messaging operations.

Texto::send(string $to, string $body, array $options = []): SentMessageResult

Send an SMS or MMS message.

Parameters:

  • $to (string): Recipient phone number (E.164 format or local format)
  • $body (string): Message text content
  • $options (array): Optional configuration

Options:

  • media_urls (array): Array of media URLs for MMS
  • from (string): Override sender number
  • driver (string): Override provider ('twilio', 'telnyx', or 'whatsapp')
  • metadata (array): Custom metadata to store with message
  • driver_config (array): Optional provider configuration snapshot (API keys, messaging profile IDs, etc.) that temporarily overrides config('texto.{driver}') for this send; primarily used by queued jobs or multi-tenant flows.

Note: When supplying driver_config, remember that any secrets included will travel with the queued job payload and logs you emit. Use encrypted queues or other safeguards appropriate for your environment.

Returns: SentMessageResult object

Example:

$result = Texto::send('+15551234567', 'Hello!', [
    'media_urls' => ['https://example.com/image.jpg'],
    'metadata' => ['campaign' => 'welcome']
]);

Value Objects

PhoneNumber

Represents a validated, E.164 formatted phone number.

class PhoneNumber
{
    public readonly string $e164;

    public static function fromString(string $raw, ?string $region = null): self
}

Methods:

  • fromString(string $raw, ?string $region = null): Parse and validate phone number
  • __toString(): Returns E.164 formatted number

SentMessageResult

Immutable result object returned after sending a message.

final class SentMessageResult implements Responsable, JsonSerializable
{
    public readonly Driver $driver;
    public readonly Direction $direction;
    public readonly PhoneNumber $to;
    public readonly ?PhoneNumber $from;
    public readonly string $body;
    public readonly array $mediaUrls;
    public readonly array $metadata;
    public readonly MessageStatus $status;
    public readonly ?string $providerMessageId;
    public readonly ?string $errorCode;

    public function toArray(): array
    public function jsonSerialize(): array
    public function toResponse($request): JsonResponse
}

WebhookProcessingResult

Result object for webhook processing.

final class WebhookProcessingResult
{
    public readonly Driver $driver;
    public readonly Direction $direction;
    public readonly ?PhoneNumber $from;
    public readonly ?PhoneNumber $to;
    public readonly ?string $body;
    public readonly array $mediaUrls;
    public readonly array $metadata;
    public readonly ?string $providerMessageId;
    public readonly ?MessageStatus $status;

    public static function inbound(Driver $driver, PhoneNumber $from, PhoneNumber $to, ?string $body, array $media, array $metadata, ?string $providerMessageId = null): self
    public static function status(Driver $driver, ?string $providerMessageId, MessageStatus $status, array $metadata = []): self
}

Enums

MessageStatus

Normalized message status values.

enum MessageStatus: string
{
    case Queued = 'queued';      // Message queued for sending
    case Sending = 'sending';    // Message being sent
    case Sent = 'sent';          // Message sent successfully
    case Delivered = 'delivered'; // Message delivered to recipient
    case Read = 'read';          // Read by recipient (WhatsApp; terminal)
    case Received = 'received';  // Inbound message received
    case Failed = 'failed';      // Send failed permanently
    case Undelivered = 'undelivered'; // Message undelivered
    case Ambiguous = 'ambiguous'; // Status unknown after polling
}

Driver

Available messaging providers.

enum Driver: string
{
    case Twilio = 'twilio';
    case Telnyx = 'telnyx';
    case Whatsapp = 'whatsapp';
}

Direction

Message direction.

enum Direction: string
{
    case Sent = 'sent';
    case Received = 'received';
}

Events

MessageSent

Fired when a message is successfully sent.

class MessageSent
{
    public function __construct(public readonly SentMessageResult $result) {}
}

MessageReceived

Fired when an inbound message is received via webhook.

class MessageReceived
{
    public function __construct(public readonly WebhookProcessingResult $result) {}
}

MessageStatusUpdated

Fired when a stored message's status advances via a provider status webhook.

class MessageStatusUpdated
{
    public function __construct(public readonly WebhookProcessingResult $result) {}
}

MessageFailed

Fired when a message send attempt fails.

class MessageFailed
{
    public function __construct(
        public readonly SentMessageResult $result,
        public readonly ?string $reason = null
    ) {}
}

Exceptions

TextoException

Base exception for all Texto-related errors.

TextoSendFailedException

Thrown when message sending fails.

TextoWebhookValidationException

Thrown when webhook validation fails (bad signature or malformed payload). It renders as an HTTP 403 response so providers treat the request as permanently unacceptable and stop retrying, rather than receiving a 500 that triggers retry storms.

Interfaces

MessageSenderInterface

Contract for message sending implementations.

interface MessageSenderInterface
{
    public function send(PhoneNumber $to, string $body, ?PhoneNumber $from = null, array $mediaUrls = [], array $metadata = []): SentMessageResult;
}

MessageRepositoryInterface

Contract for message persistence.

interface MessageRepositoryInterface
{
    public function storeSent(SentMessageResult $result): Model;
    public function storeInbound(WebhookProcessingResult $result): Model;
    public function storeStatus(WebhookProcessingResult $result): ?Model;
    public function updatePolledStatus(Message $message, MessageStatus $status, array $extraMetadata = []): Message;
    public function upgradeQueued(int $id, SentMessageResult $result): ?Model;
}

DriverManagerInterface

Contract for driver management.

interface DriverManagerInterface
{
    public function sender(?Driver $driver = null): MessageSenderInterface;
    public function extend(string $name, callable $factory): void;
}

Console Commands

php artisan texto:install

Install and configure Texto.

php artisan texto:test-send {to} {body?} {--driver=}

Send a test message.

Parameters:

  • to: Recipient phone number
  • body: Message body (default: "Test message")
  • --driver: Override provider driver

17. Console Commands

Command Description
texto:install Publish config + migration then run migrate.
texto:test-send {to} {body?} Fire a manual test message (optional--driver=).

18. Testing, Fakes & Local Development

  • Uses Pest & Orchestra Testbench for package isolation.
  • Static analysis via PHPStan (composer analyse).
  • Code style via Pint (composer format).
  • Swap drivers with a fake:
app(\Awaisjameel\Texto\Contracts\DriverManagerInterface::class)
    ->extend('twilio', fn () => new \Awaisjameel\Texto\Drivers\FakeSender());
  • Skip webhook signature validation during tests: set TEXTO_TESTING_SKIP_WEBHOOK_VALIDATION=true.

Run full suite:

composer test

19. Architecture Overview

Layer Responsibility
Texto facade/root Orchestrates send workflow, queue placeholder creation, events.
DriverManager Resolves concrete sender implementation (built‑ins + extensions).
Drivers (TwilioSender, TelnyxSender, WhatsappSender) Provider API invocation + provider‑specific metadata enrichment. Each constructs its own API adapters from its config.
StatusMapper Converts raw provider statuses / events to internal enum.
EloquentMessageRepository Persistence & deterministic queued upgrade + polling updates.
Jobs (SendMessageJob, StatusPollJob) Async send & periodic status reconciliation.
Webhook Handlers Parse & validate inbound/status payloads per provider.
Support Utilities (Retry, PollingParameterResolver) Cross‑cutting helpers.
Value Objects / Enums Strongly typed domain primitives.

Design goals: minimal public API surface (Texto::send), encapsulated provider variance, explicit lifecycle events, observability via logs + metadata.

20. Troubleshooting & FAQ

Common Issues

Q: Messages stuck in queued status A: This usually indicates queue processing issues.

  • Verify TEXTO_QUEUE=true in your environment
  • Ensure a queue worker is running: php artisan queue:work
  • Check queue connection configuration
  • Review Laravel logs for job processing errors
  • Enable status polling as fallback: TEXTO_STATUS_POLL_ENABLED=true

Q: Webhook signature validation fails (401 errors) A: Signature validation ensures webhook authenticity.

  • For Twilio: Verify TWILIO_AUTH_TOKEN matches your Twilio console
  • Ensure webhook URLs in provider console exactly match your routes (including protocol; query strings are supported)
  • Behind a TLS‑terminating proxy or load balancer, configure Laravel's TrustProxies middleware so the public https:// URL is reconstructed for signature checks
  • For local development, use ngrok or similar tunneling service

Q: Twilio Conversations template creation warnings A: Template auto-provisioning may fail due to permissions.

  • This is non-fatal; Texto falls back to direct message sending
  • Check Twilio account has Content API permissions
  • Verify TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN are correct
  • Template creation warnings don't prevent message sending

Q: Telnyx cost/segment data missing A: Cost and segment information is only provided in specific response scenarios.

  • Ensure your Telnyx API key has messaging permissions
  • Cost data appears only when Telnyx includes it in API responses
  • Segment counts depend on message content and provider logic

Q: Messages failing with provider errors A: Check provider account status and configuration.

  • Verify API credentials are correct and active
  • Ensure sender numbers are verified/purchased in provider console
  • Check provider account has sufficient balance/credits
  • Review message content for prohibited terms

Q: High memory usage with large message volumes A: Optimize for high-throughput scenarios.

  • Enable queuing: TEXTO_QUEUE=true
  • Use database queue driver for reliability
  • Configure appropriate queue worker settings
  • Monitor queue depth and processing rates

Status Definitions

Q: What does ambiguous status mean? A: Messages reach ambiguous status when polling exhausts all attempts without determining final delivery status.

  • Occurs when provider ID is missing and polling can't retrieve status
  • Investigate upstream provider logs for root cause
  • May indicate provider API issues or message filtering

Q: Difference between failed and undelivered? A: These represent different failure modes:

  • failed: Immediate sending failure (invalid number, blocked content, etc.)
  • undelivered: Message sent but delivery failed (phone off, full mailbox, etc.)

Configuration Issues

Q: How to disable message persistence? A: Set TEXTO_STORE_MESSAGES=false in your environment.

  • Events will still fire normally
  • SentMessageResult objects are still returned
  • Useful for testing or when external logging is preferred

Q: Phone number validation too strict A: Adjust the default region for number parsing.

  • Set TEXTO_DEFAULT_REGION to your primary market (e.g., 'GB' for UK)
  • This affects how local format numbers are interpreted
  • E.164 format (+country code) always works regardless of region

Provider-Specific Issues

Q: Twilio rate limiting A: Twilio enforces sending limits based on account type.

  • Free accounts: 100 messages/day
  • Trial accounts: Limited sending
  • Full accounts: Higher limits based on verification level
  • Implement queuing and backoff strategies

Q: Telnyx webhook delays A: Telnyx webhooks may have higher latency than Twilio.

  • Enable status polling for critical delivery tracking
  • Configure appropriate polling intervals
  • Monitor webhook delivery logs

Performance Tuning

Q: Optimizing for high volume A: Several configuration options for performance:

  • Use Redis/database queues instead of sync processing
  • Configure multiple queue workers
  • Enable status polling with appropriate batch sizes
  • Monitor database indexes on texto_messages table
  • Consider message archiving for old records

Q: Database performance with many messages A: The texto_messages table can grow quickly.

  • Add database indexes on frequently queried columns
  • Implement message archiving/cleanup strategies
  • Consider partitioning for very high volume
  • Monitor query performance and optimize as needed

Development & Testing

Q: Testing without sending real messages A: Use the fake driver for testing:

app(DriverManagerInterface::class)->extend('twilio', fn() => new FakeSender());
  • Skip webhook validation in tests: TEXTO_TESTING_SKIP_WEBHOOK_VALIDATION=true
  • Use test credentials or mock HTTP responses

Q: Local development with webhooks A: Webhooks require public URLs for provider access.

  • Use ngrok, localtunnel, or similar services
  • Configure webhook URLs in provider console
  • Consider webhook testing tools like webhook.site for debugging

Extending Texto

Q: Adding a new provider (e.g., Vonage) A: extend() only overrides the senders of built‑in drivers (twilio, telnyx, whatsapp); passing an unrecognized name (such as 'vonage') throws a TextoException. A genuinely new provider needs its own Driver enum case so the manager can resolve it:

// 1. Add a case to the Driver enum (e.g. case Vonage = 'vonage';) and map its statuses in StatusMapper.
// 2. Register the sender for that driver:
app(DriverManagerInterface::class)->extend('vonage', function() {
    return new class implements MessageSenderInterface {
        public function send(PhoneNumber $to, string $body, ?PhoneNumber $from = null, array $mediaUrls = [], array $metadata = []): SentMessageResult {
            // Your implementation
        }
    };
});
  • Adding the Driver enum case is required first; without it extend('vonage', ...) is rejected.
  • Consider contributing back via PR for official support.
  • Follow existing driver patterns for consistency.

Q: Custom webhook handling A: Extend webhook handlers for custom logic:

  • Create custom handler class implementing WebhookHandlerInterface
  • Register in service provider or route configuration
  • Handle provider-specific webhook formats

21. Performance Considerations & Best Practices

Database Optimization

For high-volume applications, optimize the texto_messages table:

-- Add performance indexes
CREATE INDEX idx_texto_messages_status_created ON texto_messages (status, created_at);
CREATE INDEX idx_texto_messages_provider_id ON texto_messages (provider_message_id);
CREATE INDEX idx_texto_messages_from_to ON texto_messages (from_number, to_number);

-- Consider partitioning for very high volume
-- Partition by month for message archiving
ALTER TABLE texto_messages PARTITION BY RANGE (YEAR(created_at)) (
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION p2025 VALUES LESS THAN (2026)
);

Queue Configuration

For reliable message processing at scale:

// config/queue.php
'connections' => [
    'database' => [
        'driver' => 'database',
        'table' => 'jobs',
        'queue' => 'default',
        'retry_after' => 90, // Increase for messaging jobs
    ],
],

Run multiple workers for high throughput:

# Multiple workers for parallel processing
php artisan queue:work --queue=texto-high,texto-normal --max-jobs=1000 --sleep=3
php artisan queue:work --queue=texto-bulk --max-jobs=500 --sleep=5

Monitoring & Alerting

Implement monitoring for critical messaging operations:

// Monitor queue health
$pendingJobs = DB::table('jobs')->where('queue', 'like', 'texto%')->count();
if ($pendingJobs > 1000) {
    Log::warning('High texto queue backlog', ['count' => $pendingJobs]);
}

// Monitor failure rates
$failureRate = Message::where('status', 'failed')
    ->where('created_at', '>', now()->subHour())
    ->count() / Message::where('created_at', '>', now()->subHour())->count();

if ($failureRate > 0.1) { // 10% failure rate
    // Alert or take action
}

Cost Optimization

Track and optimize messaging costs:

// Analyze costs by provider and campaign
$costs = Message::selectRaw('
        driver,
        SUM(cost_estimate) as total_cost,
        COUNT(*) as message_count,
        AVG(cost_estimate) as avg_cost
    ')
    ->whereNotNull('cost_estimate')
    ->where('created_at', '>', now()->subMonth())
    ->groupBy('driver')
    ->get();

// Implement cost thresholds
if ($costs->sum('total_cost') > 1000) { // Monthly budget
    // Send alert or implement throttling
}

Security Best Practices

Secure your messaging infrastructure:

// Use environment variables for secrets
// Never commit API keys to version control

// Implement rate limiting per user/phone
RateLimiter::for('texto-send', function (Request $request) {
    return Limit::perMinute(10)->by($request->user()->id);
});

// Validate phone numbers strictly
$phone = PhoneNumber::fromString($request->phone, 'US'); // Specify region
if (!$phone) {
    throw new InvalidPhoneNumberException();
}

Error Handling & Resilience

Implement comprehensive error handling:

try {
    $result = Texto::send($phone, $message, $options);
} catch (TextoSendFailedException $e) {
    // Log detailed error
    Log::error('Message send failed', [
        'phone' => $phone,
        'error' => $e->getMessage(),
        'driver' => config('texto.driver')
    ]);

    // Implement fallback logic
    if (config('texto.driver') === 'twilio') {
        // Try Telnyx as fallback
        $result = Texto::send($phone, $message, ['driver' => 'telnyx'] + $options);
    }

    // Notify user or take alternative action
}

Testing Strategies

Comprehensive testing approach:

// Unit tests for drivers
class TwilioSenderTest extends TestCase
{
    public function test_sends_message_successfully()
    {
        // Mock Twilio client
        $this->mock(TwilioClient::class, function ($mock) {
            $mock->shouldReceive('messages->create')
                ->once()
                ->andReturn((object)['sid' => 'SM123']);
        });

        $result = app(TwilioSender::class)->send(
            PhoneNumber::fromString('+15551234567'),
            'Test message'
        );

        $this->assertEquals(MessageStatus::Sent, $result->status);
    }
}

// Integration tests with fake driver
class MessagingIntegrationTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        // Use fake driver for integration tests
        app(DriverManagerInterface::class)->extend('twilio', fn() => new FakeSender());
    }
}

Scaling Considerations

For enterprise-level messaging:

  1. Horizontal Scaling: Deploy across multiple servers with shared queue
  2. Database Sharding: Split message storage across multiple databases
  3. CDN for Media: Use CDNs for MMS media to reduce bandwidth
  4. Provider Redundancy: Implement multi-provider failover logic
  5. Caching: Cache frequently used phone number validations
  6. Async Processing: Always use queues for production deployments

Compliance & Data Protection

Handle sensitive messaging data appropriately:

// Implement data retention policies
Message::where('created_at', '<', now()->subMonths(6))
    ->whereIn('status', ['delivered', 'failed'])
    ->delete();

// Encrypt sensitive metadata
$message->metadata = encrypt(json_encode([
    'ssn' => $sensitiveData, // Encrypted storage
    'campaign' => 'public_data'
]));

// Implement audit logging
Log::channel('messaging-audit')->info('Message sent', [
    'id' => $message->id,
    'to' => $message->to_number, // Log for compliance
    'timestamp' => now(),
    'user_id' => auth()->id()
]);

22. Roadmap

Planned Features

  • Multi-provider Routing: Intelligent load balancing and failover across providers
  • Additional Providers: Official support for MessageBird, Vonage, AWS SNS, and others
  • Template Engine: Unified templating system for all providers
  • Bulk Operations: Batch sending with progress tracking and error aggregation
  • Advanced Analytics: Built-in reporting and analytics dashboard
  • Webhook Enhancements: Improved webhook signature verification and replay protection
  • Rate Limiting: Provider-aware rate limiting and throttling
  • Geographic Routing: Route messages via local providers for cost optimization

Community Contributions

We welcome contributions! Areas of particular interest:

  • New provider implementations
  • Performance optimizations
  • Enhanced testing utilities
  • Documentation improvements
  • Integration packages for popular frameworks

Version Compatibility

Texto Version Laravel Version PHP Version Status
1.x 10.x - 13.x 8.1+ Active

See the Migration Guide below and CHANGELOG.md for upgrade notes.

23. Contributing

We welcome contributions from the community! Here's how to get involved:

Development Setup

# Fork and clone the repository
git clone https://github.com/your-username/texto.git
cd texto

# Install dependencies
composer install

# Copy environment file and configure
cp .env.example .env
# Add your Twilio/Telnyx test credentials

# Run tests
composer test

# Run static analysis
composer analyse

# Format code
composer format

Contribution Guidelines

  1. Open an Issue First: For significant changes, open a descriptive issue to discuss the proposed changes.

  2. Code Quality: All contributions must pass quality checks:

    composer analyse  # PHPStan static analysis
    composer format   # Laravel Pint code formatting
    composer test     # Pest test suite
  3. Testing: Add tests for new features and bug fixes:

    • Unit tests for classes and methods
    • Integration tests for full workflows
    • Use the FakeSender for testing without external APIs
  4. Documentation: Update documentation for user-visible changes:

    • README.md for new features and usage examples
    • Inline code documentation (PHPDoc)
    • CHANGELOG.md for version history
  5. Type Safety: Keep new public APIs strongly typed using PHP 8.1+ features (enums, readonly properties, constructor promotion).

Code Style

Follow Laravel's coding standards with Pint configuration:

// Good: Use type hints and return types
public function send(PhoneNumber $to, string $body): SentMessageResult

// Good: Use enums for fixed values
public function __construct(public readonly MessageStatus $status)

// Good: Comprehensive PHPDoc
/**
 * Send an SMS/MMS message using the active driver.
 *
 * @param  string  $to  E.164 formatted recipient number
 * @param  string  $body  Message body text
 * @param  array{media_urls?:string[], metadata?:array}  $options
 */

Testing Strategy

// Unit test example
test('phone number validation', function () {
    $phone = PhoneNumber::fromString('+15551234567');
    expect($phone->e164)->toBe('+15551234567');
});

// Integration test example
test('message sending workflow', function () {
    // Use fake driver to avoid external calls
    app(DriverManagerInterface::class)->extend('twilio', fn() => new FakeSender());

    $result = Texto::send('+15551234567', 'Test message');

    expect($result->status)->toBe(MessageStatus::Sent);
    expect($result->providerMessageId)->toBeString();
});

Pull Request Process

  1. Branch Naming: Use descriptive branch names:

    • feature/add-vonage-driver
    • fix/webhook-validation-bug
    • docs/improve-api-reference
  2. Commit Messages: Follow conventional commits:

    • feat: add Vonage driver support
    • fix: resolve webhook signature validation
    • docs: update API reference section
  3. PR Description: Include:

    • Clear description of changes
    • Screenshots for UI changes (if applicable)
    • Test coverage information
    • Breaking changes (if any)
  4. Review Process: All PRs require review and must pass CI checks.

Areas for Contribution

High Priority:

  • New provider implementations (MessageBird, Vonage, AWS SNS)
  • Performance optimizations for high-volume sending
  • Enhanced webhook security features

Medium Priority:

  • Additional testing utilities and helpers
  • Documentation improvements and translations
  • Integration packages for popular Laravel packages

Good for Beginners:

  • Bug fixes and small improvements
  • Additional code examples and tutorials
  • Test coverage improvements

Community Support

  • Discussions: Use GitHub Discussions for questions and ideas
  • Issues: Report bugs and request features via GitHub Issues

Recognition

Contributors are recognized in:

  • CHANGELOG.md for significant contributions
  • GitHub's contributor insights
  • Social media mentions for major features

Thank you for contributing to Texto! 🎉

24. Security Policy

Report vulnerabilities privately via GitHub Security Advisories. Do not disclose publicly until patched. Avoid sharing live credentials or full raw webhook payloads containing PII in issues.

25. Migration Guide

Upgrading Versions

Unreleased (next version)

Breaking: the low-level provider API adapters (TwilioMessagingApiInterface, TelnyxMessagingApiInterface, WhatsappApiInterface, …) are no longer bound in the service container. If you resolved them via app(...), construct them directly with the credentials they should use (e.g. new TelnyxMessagingApi($apiKey)), or inject a custom implementation through the sender constructors (new TwilioSender($config, $fakeMessagingApi)). See CHANGELOG.md for the full list of changes.

From 1.0.x to 1.1.x

No breaking changes. New features include:

  • Enhanced status polling with configurable backoff strategies
  • Improved error handling and structured logging
  • Additional metadata fields for cost tracking
  • Better webhook validation and security

Migration Steps:

  1. Update package: composer update awaisjameel/texto
  2. Review new configuration options in config/texto.php
  3. Update environment variables if needed
  4. Test webhook endpoints with new validation

From 0.x to 1.x

Breaking changes in the 1.0 release:

Configuration Changes:

// Old (0.x)
'driver' => env('TEXTO_DRIVER', 'twilio'),

// New (1.x) - same, but additional options available
'driver' => env('TEXTO_DRIVER', 'twilio'),
'store_messages' => env('TEXTO_STORE_MESSAGES', true),
'queue' => env('TEXTO_QUEUE', false),

API Changes:

// Old (0.x)
Texto::send('+15551234567', 'Hello');

// New (1.x) - same API, enhanced return type
$result = Texto::send('+15551234567', 'Hello');
$result->status; // Now returns MessageStatus enum

Migration Steps:

  1. Backup your database
  2. Update to 1.x: composer update awaisjameel/texto
  3. Run php artisan texto:install to update config and migrations
  4. Update any code using status strings to use MessageStatus enums
  5. Test thoroughly in staging environment

Environment Variable Changes

Old Variable New Variable Notes
- TEXTO_STORE_MESSAGES Control message persistence
- TEXTO_QUEUE Enable async processing
- TEXTO_WEBHOOK_SECRET Shared secret for webhook auth
- TEXTO_STATUS_POLL_ENABLED Enable status polling fallback

Database Schema Changes

Version 1.x adds new columns to texto_messages:

-- New columns in 1.x
ALTER TABLE texto_messages ADD COLUMN segments_count INT NULL;
ALTER TABLE texto_messages ADD COLUMN cost_estimate DECIMAL(10,4) NULL;
ALTER TABLE texto_messages ADD COLUMN status_updated_at TIMESTAMP NULL;

These are nullable and backward compatible.

Webhook URL Changes

Webhook routes remain the same but include enhanced validation:

  • /texto/webhook/twilio - Twilio webhooks (inbound + status)
  • /texto/webhook/telnyx - Telnyx webhooks (inbound + status)

Ensure your provider console webhook URLs match exactly.

Testing Changes

Update your tests to use the new FakeSender:

// Old approach
// Custom mock setup

// New approach
app(DriverManagerInterface::class)->extend('twilio', fn() => new FakeSender());

26. License & Credits

License

Released under the MIT License. See LICENSE.md for details.

Credits

Created by: awaisjameel

Inspiration & Thanks:

  • Spatie Laravel Package Tools - Package skeleton
  • Laravel OSS Ecosystem - Best practices and patterns
  • Twilio & Telnyx Developer Communities - API insights

Contributors

We'd like to thank all contributors who have helped make Texto better:

Sponsors

Support Texto's development:

GitHub Sponsors

Related Projects

Made with ❤️ for the Laravel community

Quick Reference Cheat Sheet

// Basic SMS
$result = Texto::send('+15551234567', 'Hello World!');

// MMS with media
$result = Texto::send('+15551234567', 'Check this out!', [
    'media_urls' => ['https://example.com/image.jpg']
]);

// Override provider per message
$result = Texto::send('+15551234567', 'Via Telnyx', [
    'driver' => 'telnyx'
]);

// Custom sender and metadata
$result = Texto::send('+15551234567', 'Promotional message', [
    'from' => '+15550009999',
    'metadata' => ['campaign' => 'spring_sale', 'priority' => 'high']
]);

// Async processing (when TEXTO_QUEUE=true)
$result = Texto::send('+15551234567', 'Queued message');
// $result->status === MessageStatus::Queued

// Event listeners
Event::listen(MessageSent::class, function ($event) {
    Log::info('Message sent', ['id' => $event->result->providerMessageId]);
});

Support & Community

  • 📖 Documentation: You're reading it! Check the GitHub repository for the latest updates
  • 🐛 Bug Reports: Open an issue on GitHub
  • 💡 Feature Requests: Start a discussion on GitHub
  • Show Support: Star the repo if Texto saves you time and effort!

Built with ❤️ for the Laravel community by awaisjameel

awaisjameel/texto 适用场景与选型建议

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

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

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

围绕 awaisjameel/texto 我们能提供哪些服务?
定制开发 / 二次开发

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

BUG 修复 & 性能优化

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

项目外包 & 长期维护

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

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

统计信息

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

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-11-06