bilalhaidar / laravel-slicktext
A comprehensive Laravel wrapper for the SlickText SMS Marketing API v2
Requires
- php: ^8.1
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0
- pestphp/pest: ^3.8
- pestphp/pest-plugin-laravel: ^3.2
- phpunit/phpunit: ^10.0|^11.0
README
A comprehensive, feature-rich Laravel wrapper for the SlickText SMS Marketing API v2. This package provides an elegant, fluent interface for managing SMS campaigns, contacts, lists, and all SlickText features directly from your Laravel application.
Features
- 🚀 Full API Coverage - Complete support for all SlickText API v2 endpoints
- 💬 Contact Management - Create, update, delete, and manage SMS contacts
- 📱 Campaign Management - Send SMS/MMS campaigns, schedule messages
- 📋 List Management - Organize contacts into lists and segments
- 📊 Analytics - Track campaign performance and contact engagement
- 💼 Multi-Brand Support - Easily switch between multiple SlickText brands
- 🎯 Inbox Conversations - Manage one-on-one SMS conversations
- âš¡ Laravel Integration - Facade support, service container binding
- 🔒 Exception Handling - Granular exceptions for different error types
- ✅ Fully Tested - Comprehensive test suite included
Requirements
- PHP 8.1 or higher
- Laravel 10.x, 11.x, or 12.x
- A SlickText account with API access
Installation
Step 1: Install via Composer
composer require bilalhaidar/laravel-slicktext
Step 2: Publish Configuration (Optional)
The package auto-discovers the service provider. Optionally publish the config file:
php artisan vendor:publish --tag=slicktext-config
This creates config/slicktext.php.
Step 3: Add Credentials to .env
Add your SlickText API credentials to your .env file:
SLICKTEXT_API_KEY=your_api_key_here SLICKTEXT_BRAND_ID=your_brand_id_here SLICKTEXT_BASE_URL=https://dev.slicktext.com/v1
Where to find your credentials:
- API Key: https://app.slicktext.com/settings/api
- Brand ID: Visible in your SlickText dashboard URL
Step 4: Verify Installation
Test the integration in tinker:
php artisan tinker
use BilalHaidar\SlickText\Facades\SlickText; SlickText::brands()->get();
If you see your brand information, you're all set! 🎉
Configuration
Publish the configuration file:
php artisan vendor:publish --tag=slicktext-config
Add your credentials to .env:
SLICKTEXT_API_KEY=your_api_key_here SLICKTEXT_BRAND_ID=your_brand_id_here
Quick Start
Using the Facade
use BilalHaidar\SlickText\Facades\SlickText; // Get brand information $brand = SlickText::brands()->get(); // Create a contact $contact = SlickText::contacts()->create([ 'mobile_number' => '+16155553338', 'first_name' => 'John', 'last_name' => 'Doe', 'email' => 'john@example.com', 'opt_in_status' => 'subscribed', ]); // Send a campaign $campaign = SlickText::campaigns()->sendNow( 'Hello! Check out our sale today!', ['list_id_1', 'list_id_2'] );
Using Dependency Injection (Recommended)
use BilalHaidar\SlickText\SlickText; class SmsController extends Controller { public function __construct( protected SlickText $slickText ) {} public function sendMessage() { $campaign = $this->slickText->campaigns()->sendNow( 'Your message here', ['list_id'] ); } }
Working with Multiple Brands
// Switch to a different brand $otherBrand = SlickText::forBrand('other_brand_id'); $contacts = $otherBrand->contacts()->all(); // Or in one line $contacts = SlickText::forBrand('brand_id')->contacts()->all();
Resources
Contacts
// List all contacts with pagination $contacts = SlickText::contacts()->all([ 'page' => 0, 'pageSize' => 50, ]); // Iterate through results foreach ($contacts as $contact) { echo $contact['first_name']; } // Check for more pages if ($contacts->hasMore()) { $nextPage = SlickText::contacts()->all([ 'page' => $contacts->nextPage(), ]); } // Find a contact by ID $contact = SlickText::contacts()->find('contact_id'); // Find by mobile number $contact = SlickText::contacts()->findByMobile('+16155553338'); // Find by email $contact = SlickText::contacts()->findByEmail('john@example.com'); // Create a new contact $contact = SlickText::contacts()->create([ 'mobile_number' => '+16155553338', 'first_name' => 'John', 'last_name' => 'Doe', 'email' => 'john@example.com', 'birthdate' => '1990-01-15', 'city' => 'Nashville', 'state' => 'TN', 'zip' => '37201', 'country' => 'US', 'timezone' => 'America/Chicago', 'opt_in_status' => 'subscribed', 'favorite_color' => 'blue', // Custom field ]); // Update a contact $contact = SlickText::contacts()->update('contact_id', [ 'first_name' => 'Jane', 'email' => 'jane@example.com', ]); // Subscribe/unsubscribe SlickText::contacts()->subscribe('contact_id'); SlickText::contacts()->unsubscribe('contact_id'); // Delete a contact SlickText::contacts()->delete('contact_id'); // Upsert (create or update) by mobile $contact = SlickText::contacts()->upsertByMobile('+16155553338', [ 'first_name' => 'Updated Name', ]); // Bulk create contacts $results = SlickText::contacts()->bulkCreate([ ['mobile_number' => '+16155553331', 'first_name' => 'John'], ['mobile_number' => '+16155553332', 'first_name' => 'Jane'], ['mobile_number' => '+16155553333', 'first_name' => 'Bob'], ]);
Campaigns
// List campaigns $campaigns = SlickText::campaigns()->all(); // Get specific campaign $campaign = SlickText::campaigns()->find('campaign_id'); // Send immediately $campaign = SlickText::campaigns()->sendNow( 'Your message here', ['list_id_1', 'list_id_2'], ['name' => 'Flash Sale Campaign'] ); // Schedule for later $campaign = SlickText::campaigns()->schedule( 'Your scheduled message', ['list_id'], '2025-02-01T10:00:00Z', ['name' => 'Scheduled Campaign'] ); // Send to segments $campaign = SlickText::campaigns()->sendToSegments( 'Message for segment', ['segment_id_1', 'segment_id_2'] ); // Send MMS with media $campaign = SlickText::campaigns()->sendMms( 'Check out this image!', 'https://example.com/image.jpg', ['list_id'] ); // Get sent campaigns $sent = SlickText::campaigns()->sent(); // Get scheduled campaigns $scheduled = SlickText::campaigns()->scheduled(); // Get draft campaigns $drafts = SlickText::campaigns()->drafts(); // Update an unsent campaign SlickText::campaigns()->update('campaign_id', [ 'body' => 'Updated message content', ]); // Delete a campaign SlickText::campaigns()->delete('campaign_id');
Lists
// Get all lists $lists = SlickText::lists()->all(); // Create a list $list = SlickText::lists()->create([ 'name' => 'VIP Customers', 'description' => 'Our most valued customers', ]); // Find or create a list $list = SlickText::lists()->firstOrCreate('Newsletter Subscribers'); // Get contacts in a list $contacts = SlickText::lists()->getContacts('list_id'); // Get contact count $count = SlickText::lists()->getContactCount('list_id'); // Add contacts to lists SlickText::lists()->addContacts( ['contact_id_1', 'contact_id_2'], ['list_id_1', 'list_id_2'] ); // Add single contact SlickText::lists()->addContact('contact_id', 'list_id'); // Remove contacts from lists SlickText::lists()->removeContacts(['contact_id'], ['list_id']); // Remove single contact SlickText::lists()->removeContact('contact_id', 'list_id'); // Update a list SlickText::lists()->update('list_id', ['name' => 'Updated Name']); // Delete a list SlickText::lists()->delete('list_id');
Messages
// Get all messages $messages = SlickText::messages()->all(); // Get a specific message $message = SlickText::messages()->find('message_id'); // Get messages sent after a date $messages = SlickText::messages()->sentAfter('2025-01-01'); // Get messages sent before a date $messages = SlickText::messages()->sentBefore('2025-01-31'); // Get messages in a date range $messages = SlickText::messages()->betweenDates('2025-01-01', '2025-01-31'); // Get messages for a specific contact $messages = SlickText::messages()->forContact('contact_id');
Segments
// Get all segments $segments = SlickText::segments()->all(); // Get a specific segment $segment = SlickText::segments()->find('segment_id'); // Get contacts in a segment $contacts = SlickText::segments()->getContacts('segment_id'); // Get contact count $count = SlickText::segments()->getContactCount('segment_id'); // Update a segment SlickText::segments()->update('segment_id', ['name' => 'Updated Segment']); // Delete a segment SlickText::segments()->delete('segment_id');
Custom Fields
// Get all custom fields $fields = SlickText::customFields()->all(); // Create a custom field $field = SlickText::customFields()->create([ 'name' => 'favorite_product', 'type' => 'text', ]); // Update a custom field SlickText::customFields()->update('field_id', ['name' => 'preferred_product']); // Delete a custom field SlickText::customFields()->delete('field_id');
Links (Trackable Short Links)
// Get all links $links = SlickText::links()->all(); // Create a short link $link = SlickText::links()->shorten('https://example.com/sale', 'Summer Sale'); // Update a link SlickText::links()->update('link_id', ['name' => 'Updated Link Name']); // Delete a link SlickText::links()->delete('link_id');
Workflows
// Get all workflows $workflows = SlickText::workflows()->all(); // Get a specific workflow $workflow = SlickText::workflows()->find('workflow_id'); // Enable a workflow SlickText::workflows()->enable('workflow_id'); // Disable a workflow SlickText::workflows()->disable('workflow_id'); // Delete a workflow SlickText::workflows()->delete('workflow_id');
Analytics
// Get contact counts $counts = SlickText::analytics()->contactCounts(); // Get contact growth $growth = SlickText::analytics()->contactGrowth(); // Get campaign analytics $analytics = SlickText::analytics()->campaigns(); // Get specific campaign analytics $campaignAnalytics = SlickText::analytics()->campaign('campaign_id'); // Get message analytics $messageAnalytics = SlickText::analytics()->messages(); // Get message credit usage $credits = SlickText::analytics()->messageCredits(); // Get workflow analytics $workflowAnalytics = SlickText::analytics()->workflow('workflow_id');
Inbox (One-on-One Conversations)
// Get all conversations $conversations = SlickText::inbox()->conversations(); // Get open conversations $open = SlickText::inbox()->openConversations(); // Get closed conversations $closed = SlickText::inbox()->closedConversations(); // Get unassigned conversations $unassigned = SlickText::inbox()->unassignedConversations(); // Get a specific conversation $conversation = SlickText::inbox()->findConversation('conversation_id'); // Close a conversation SlickText::inbox()->closeConversation('conversation_id'); // Reopen a conversation SlickText::inbox()->reopenConversation('conversation_id'); // Assign a conversation SlickText::inbox()->assignConversation('conversation_id', 'user_id'); // Unassign a conversation SlickText::inbox()->unassignConversation('conversation_id'); // Get inbox tags $tags = SlickText::inbox()->tags(); // Add tags to conversation SlickText::inbox()->addTagsToConversation('conversation_id', ['tag_id_1', 'tag_id_2']);
Brands
// Get current brand info $brand = SlickText::brands()->get(); // Get all accessible brands $brands = SlickText::brands()->all(); // Get credit usage $usage = SlickText::brands()->getCreditUsage();
Keywords
// Get all keywords $keywords = SlickText::keywords()->all(); // Get a specific keyword $keyword = SlickText::keywords()->find('keyword_id'); // Delete a keyword SlickText::keywords()->delete('keyword_id');
Popups
// Get all popups $popups = SlickText::popups()->all(); // Get a specific popup $popup = SlickText::popups()->find('popup_id'); // Update a popup SlickText::popups()->update('popup_id', ['status' => 'active']); // Delete a popup SlickText::popups()->delete('popup_id');
Pagination
All list methods return a PaginatedResponse object:
$contacts = SlickText::contacts()->all(['page' => 0, 'pageSize' => 50]); // Get items $items = $contacts->items(); // Check if there are more pages if ($contacts->hasMore()) { $nextPageNumber = $contacts->nextPage(); } // Get current page info $currentPage = $contacts->currentPage(); $perPage = $contacts->perPage(); // Count items on current page $count = count($contacts); // Iterate directly foreach ($contacts as $contact) { // ... } // Check if empty if ($contacts->isEmpty()) { // No results } // Get first/last item $first = $contacts->first(); $last = $contacts->last();
Error Handling
use YourVendor\SlickText\Exceptions\SlickTextException; use YourVendor\SlickText\Exceptions\AuthenticationException; use YourVendor\SlickText\Exceptions\NotFoundException; use YourVendor\SlickText\Exceptions\ValidationException; use YourVendor\SlickText\Exceptions\RateLimitException; try { $contact = SlickText::contacts()->find('invalid_id'); } catch (NotFoundException $e) { // Contact not found (404) echo $e->getMessage(); } catch (AuthenticationException $e) { // Invalid API key or forbidden (401/403) echo $e->getMessage(); } catch (ValidationException $e) { // Validation error (422) echo $e->getMessage(); print_r($e->getErrors()); } catch (RateLimitException $e) { // Rate limit exceeded (429) echo "Retry after: " . $e->getRetryAfter() . " seconds"; } catch (SlickTextException $e) { // Other API errors echo $e->getMessage(); echo $e->getStatusCode(); }
Rate Limiting
The API has a default rate limit of 8 requests per second (480 per minute). You can check your current rate limit status:
$slickText = app('slicktext'); $rateLimitInfo = $slickText->getHttpClient()->getRateLimitInfo(); // Returns: // [ // 'limit' => 480, // 'remaining' => 479, // 'reset_in_seconds' => 55, // ]
Webhooks
SlickText supports webhooks for various events. Configure your webhook URL in the SlickText dashboard and handle incoming webhooks:
// In your routes/api.php Route::post('/webhooks/slicktext', [SlickTextWebhookController::class, 'handle']); // In your controller class SlickTextWebhookController extends Controller { public function handle(Request $request) { $payload = $request->all(); $event = $payload['event'] ?? null; switch ($event) { case 'contact.created': // Handle new contact break; case 'contact.edited': // Handle contact update break; case 'contact.deleted': // Handle contact deletion break; case 'campaign.sent': // Handle campaign sent break; case 'inbox.message.received': // Handle incoming message break; // Add more event handlers as needed } return response()->json(['received' => true]); } }
Testing
Run the test suite:
composer test
Or with coverage:
composer test-coverage
Using This Package as a Template
This package can serve as an excellent example for building your own Laravel API wrappers. Here's how to use it as a reference:
Architecture Overview
src/
├── Exceptions/ # Custom exception classes for API errors
├── Http/ # HTTP client handling authentication & requests
├── Resources/ # API resource classes (Contacts, Campaigns, etc.)
├── Support/ # Helper classes (PaginatedResponse)
├── Facades/ # Laravel facade
├── SlickText.php # Main client class
└── SlickTextServiceProvider.php # Laravel service provider
Key Patterns Used
- Resource-Based Architecture: Each API endpoint group has its own Resource class
- Fluent Interface: Chainable methods for readable code
- Lazy Loading: Resources are instantiated only when accessed
- Immutable Brand Switching:
forBrand()returns a new instance - Exception Hierarchy: Specific exceptions for different HTTP status codes
- Pagination Support: Custom
PaginatedResponseclass for paginated results
To Build Your Own API Wrapper
-
Fork or clone this repository
-
Replace the SlickText-specific code with your API
-
Keep the architectural patterns:
Http/HttpClient.php- Modify base URL and authenticationResources/BaseResource.php- Adapt endpoint building logicExceptions/- Use same exception hierarchySupport/PaginatedResponse.php- Reuse if your API paginates
-
Update the service provider and facades
-
Write tests using the same structure in
tests/
Example: Creating a New Resource
namespace YourVendor\YourPackage\Resources; use YourVendor\YourPackage\Support\PaginatedResponse; class YourResource extends BaseResource { public function all(array $options = []): PaginatedResponse { return $this->http->getPaginated( 'your-endpoint', $this->buildListParams($options) ); } public function find(string $id): array { return $this->http->get("your-endpoint/{$id}"); } public function create(array $data): array { return $this->http->post('your-endpoint', $data); } }
Development & Contributing
We welcome contributions! This section explains how to set up the development environment.
Setting Up Development Environment
-
Fork the repository on GitHub
-
Clone your fork:
git clone https://github.com/YOUR-USERNAME/laravel-slicktext.git cd laravel-slicktext -
Install dependencies:
composer install
-
Create a
.envfile for testing:cp .env.example .env
Add your test credentials:
SLICKTEXT_API_KEY=your_test_api_key SLICKTEXT_BRAND_ID=your_test_brand_id
-
Run tests to verify setup:
composer test
Making Changes
-
Create a feature branch:
git checkout -b feature/your-feature-name
-
Make your changes following the existing code style
-
Write tests for new features:
# Create test in tests/Unit/ or tests/Feature/ # Run tests composer test
-
Update documentation if needed (README.md, docblocks)
-
Commit your changes:
git add . git commit -m "Add feature: your feature description"
-
Push to your fork:
git push origin feature/your-feature-name
-
Create a Pull Request on GitHub
Coding Standards
- Follow PSR-12 coding standards
- Use PHP 8.1+ features (typed properties, constructor promotion)
- Write descriptive docblocks for public methods
- Use Laravel conventions (camelCase methods, snake_case config)
- Add type hints to all parameters and return values
Running Code Quality Tools
# Run PHP CS Fixer (if configured) composer format # Run PHPStan (if configured) composer analyse
How to Fork and Add Features
Forking for Your Own Use
If you want to customize this package for your specific needs:
-
Fork the repository to your GitHub account
-
Update the package name in
composer.json:{ "name": "your-vendor/laravel-slicktext", "description": "Your custom SlickText integration" } -
Update namespaces throughout the codebase:
- Replace
BilalHaidar\SlickTextwithYourVendor\YourPackage - Update in all PHP files
- Update in
composer.jsonautoload section - Update in tests
- Replace
-
Install from your fork:
// In your Laravel app's composer.json { "repositories": [ { "type": "vcs", "url": "https://github.com/your-username/laravel-slicktext" } ], "require": { "your-vendor/laravel-slicktext": "dev-main" } }
-
Add your custom features in new Resource classes or methods
Adding New API Endpoints
Example: Adding a hypothetical "Templates" resource:
-
Create the resource class:
# Create src/Resources/TemplateResource.php<?php namespace BilalHaidar\SlickText\Resources; class TemplateResource extends BaseResource { public function all(array $options = []): PaginatedResponse { return $this->http->getPaginated( $this->brandEndpoint('templates'), $this->buildListParams($options) ); } public function create(array $data): array { return $this->http->post( $this->brandEndpoint('templates'), $data ); } }
-
Register in main client (
src/SlickText.php):protected ?TemplateResource $templates = null; public function templates(): TemplateResource { return $this->templates ??= new TemplateResource($this->http, $this->brandId); }
-
Add to facade docblock (
src/Facades/SlickText.php):/** * @method static TemplateResource templates() */
-
Write tests (
tests/Unit/TemplateResourceTest.php) -
Update README with usage examples
Troubleshooting
Common Issues
"Class 'SlickText' not found"
# Clear config cache php artisan config:clear # Regenerate autoload files composer dump-autoload
"Brand ID is required for this operation"
- Ensure
SLICKTEXT_BRAND_IDis set in.env - Or explicitly set:
SlickText::forBrand('your_brand_id')
"Unauthenticated" or 401 errors
- Verify your API key is correct in
.env - Check the key has proper permissions in SlickText dashboard
Rate limit errors (429)
- SlickText has a default limit of 8 requests/second
- Implement backoff/retry logic in your application
- Check rate limit info:
$client->getHttpClient()->getRateLimitInfo()
SSL Certificate errors
- Update CA certificates:
composer update - Or set in config:
'verify' => false(not recommended for production)
Getting Help
- Documentation: https://api.slicktext.com/docs/v2/overview
- Issues: https://github.com/bhaidar/laravel-slicktext/issues
- Email: bhaidar@gmail.com
Examples
See the CreateContactAndSendInboxMessage.php file for real-world examples including:
- Creating contacts and sending messages
- Building an SMS service class
- Controller integration
- MMS messages with media
- Inbox conversation management
API Documentation
For complete SlickText API documentation:
- API Overview: https://api.slicktext.com/docs/v2/overview
- Authorization: https://api.slicktext.com/docs/v2/overview
- Rate Limits: https://api.slicktext.com/docs/v2/overview
Changelog
Please see CHANGELOG for recent changes.
Credits
- Author: Bilal Haidar
- SlickText API: SlickText
License
The MIT License (MIT). Please see License File for more information.