SendMessageJob¶
The SendMessageJob handles queued message sending with automatic retries and event dispatching.
Overview¶
use Lynkbyte\EvolutionApi\Jobs\SendMessageJob;
// Queue a message
SendMessageJob::text('my-instance', '5511999999999', 'Hello!')
->dispatch();
Class Reference¶
namespace Lynkbyte\EvolutionApi\Jobs;
class SendMessageJob implements ShouldQueue
{
public readonly string $instanceName;
public readonly string $messageType;
public readonly array $message;
public readonly ?string $connectionName;
public int $tries;
public array $backoff;
public int $maxExceptions;
}
Static Constructors¶
Text Messages¶
SendMessageJob::text(
instanceName: 'my-instance',
number: '5511999999999',
text: 'Hello, World!',
options: [
'delay' => 1000,
'linkPreview' => true,
],
connectionName: null
);
Media Messages¶
SendMessageJob::media(
instanceName: 'my-instance',
number: '5511999999999',
mediatype: 'image', // image, video, document
media: 'https://example.com/image.jpg',
options: [
'caption' => 'Check this out!',
'filename' => 'photo.jpg',
],
connectionName: null
);
Creating Jobs Directly¶
For more control, create jobs directly:
use Lynkbyte\EvolutionApi\Jobs\SendMessageJob;
// Text message
$job = new SendMessageJob(
instanceName: 'my-instance',
messageType: 'text',
message: [
'number' => '5511999999999',
'text' => 'Hello!',
],
connectionName: 'production'
);
// Media message
$job = new SendMessageJob(
instanceName: 'my-instance',
messageType: 'media',
message: [
'number' => '5511999999999',
'mediatype' => 'image',
'media' => 'https://example.com/image.jpg',
'caption' => 'Amazing photo!',
]
);
// Audio message
$job = new SendMessageJob(
instanceName: 'my-instance',
messageType: 'audio',
message: [
'number' => '5511999999999',
'audio' => 'https://example.com/audio.mp3',
]
);
// Location message
$job = new SendMessageJob(
instanceName: 'my-instance',
messageType: 'location',
message: [
'number' => '5511999999999',
'latitude' => -23.550520,
'longitude' => -46.633308,
'name' => 'São Paulo',
'address' => 'São Paulo, Brazil',
]
);
Dispatching Jobs¶
Basic Dispatch¶
// Dispatch to default queue
SendMessageJob::text('my-instance', '5511999999999', 'Hello!')
->dispatch();
Delayed Dispatch¶
// Send after 5 minutes
SendMessageJob::text('my-instance', '5511999999999', 'Reminder!')
->delay(now()->addMinutes(5))
->dispatch();
Custom Queue¶
// Send to specific queue
SendMessageJob::text('my-instance', '5511999999999', 'Priority!')
->onQueue('high-priority')
->dispatch();
Chain Jobs¶
use Illuminate\Support\Facades\Bus;
// Send multiple messages in sequence
Bus::chain([
SendMessageJob::text('my-instance', '5511999999999', 'Message 1'),
SendMessageJob::text('my-instance', '5511999999999', 'Message 2'),
SendMessageJob::text('my-instance', '5511999999999', 'Message 3'),
])->dispatch();
Batch Processing¶
use Illuminate\Support\Facades\Bus;
// Send to multiple recipients
$jobs = collect($recipients)->map(function ($number) {
return SendMessageJob::text('my-instance', $number, 'Broadcast message!');
});
Bus::batch($jobs)
->name('broadcast-campaign')
->allowFailures()
->dispatch();
Message Types¶
text¶
Text message with optional link preview:
$job = new SendMessageJob(
instanceName: 'my-instance',
messageType: 'text',
message: [
'number' => '5511999999999',
'text' => 'Check out https://example.com',
'delay' => 1000,
'linkPreview' => true,
'mentionsEveryOne' => false,
]
);
media¶
Image, video, or document:
$job = new SendMessageJob(
instanceName: 'my-instance',
messageType: 'media',
message: [
'number' => '5511999999999',
'mediatype' => 'image', // image, video, document
'media' => 'https://example.com/file.jpg',
'mimetype' => 'image/jpeg',
'caption' => 'Optional caption',
'filename' => 'photo.jpg',
]
);
audio¶
Audio file or voice recording:
$job = new SendMessageJob(
instanceName: 'my-instance',
messageType: 'audio',
message: [
'number' => '5511999999999',
'audio' => 'https://example.com/audio.mp3',
]
);
location¶
Location with coordinates:
$job = new SendMessageJob(
instanceName: 'my-instance',
messageType: 'location',
message: [
'number' => '5511999999999',
'latitude' => -23.550520,
'longitude' => -46.633308,
'name' => 'Location Name',
'address' => 'Full address',
]
);
Job Configuration¶
Retry Behavior¶
The job automatically configures retries from config:
// From config/evolution-api.php
'queue' => [
'max_exceptions' => 3,
'backoff' => [60, 300, 900], // 1min, 5min, 15min
],
Override per-job:
$job = SendMessageJob::text('my-instance', '5511999999999', 'Hello!');
$job->tries = 5;
$job->backoff = [30, 60, 120];
Custom Configuration¶
$job = SendMessageJob::text('my-instance', '5511999999999', 'Hello!')
->onQueue('priority-messages')
->onConnection('redis')
->delay(now()->addSeconds(30));
dispatch($job);
Events¶
The job dispatches events:
MessageSent¶
On successful send:
use Lynkbyte\EvolutionApi\Events\MessageSent;
Event::listen(MessageSent::class, function ($event) {
$event->instanceName; // 'my-instance'
$event->messageType; // 'text'
$event->message; // ['number' => '...', 'text' => '...']
$event->response; // API response data
});
MessageFailed¶
On failure (including after retries):
use Lynkbyte\EvolutionApi\Events\MessageFailed;
Event::listen(MessageFailed::class, function ($event) {
$event->instanceName; // 'my-instance'
$event->messageType; // 'text'
$event->message; // ['number' => '...', 'text' => '...']
$event->exception; // The exception that occurred
// Log or notify
Log::error("Message to {$event->message['number']} failed", [
'error' => $event->exception->getMessage(),
]);
});
Error Handling¶
In the Job¶
The job handles errors internally:
public function handle(): void
{
try {
$response = match ($this->messageType) {
'text' => $service->messages()->sendText(...),
// ...
};
if ($response->isSuccessful()) {
event(new MessageSent(...));
} else {
$this->handleFailure(new Exception($response->message));
}
} catch (\Throwable $e) {
$this->handleFailure($e);
throw $e; // Re-throw for queue retry
}
}
Custom Failure Handling¶
Override the failed method:
use Lynkbyte\EvolutionApi\Jobs\SendMessageJob;
class CustomSendMessageJob extends SendMessageJob
{
public function failed(\Throwable $exception): void
{
parent::failed($exception);
// Custom logic
DB::table('failed_messages')->insert([
'instance' => $this->instanceName,
'number' => $this->message['number'] ?? null,
'type' => $this->messageType,
'error' => $exception->getMessage(),
'failed_at' => now(),
]);
}
}
Job Tags¶
Jobs are tagged for Horizon filtering:
public function tags(): array
{
return [
'evolution-api',
'message',
"instance:{$this->instanceName}",
"type:{$this->messageType}",
];
}
View in Horizon:
- Filter by evolution-api - All Evolution API jobs
- Filter by instance:my-instance - Jobs for specific instance
- Filter by type:text - Text message jobs only
Multi-Connection Support¶
Send via different Evolution API connections:
// Using secondary connection
SendMessageJob::text(
instanceName: 'my-instance',
number: '5511999999999',
text: 'Hello!',
connectionName: 'secondary'
)->dispatch();
Example: Broadcast Service¶
namespace App\Services;
use Lynkbyte\EvolutionApi\Jobs\SendMessageJob;
use Illuminate\Support\Facades\Bus;
class BroadcastService
{
public function sendCampaign(
string $instanceName,
array $recipients,
string $message,
?string $mediaUrl = null
): void {
$jobs = collect($recipients)->map(function ($number) use ($instanceName, $message, $mediaUrl) {
if ($mediaUrl) {
return SendMessageJob::media(
instanceName: $instanceName,
number: $number,
mediatype: 'image',
media: $mediaUrl,
options: ['caption' => $message]
);
}
return SendMessageJob::text(
instanceName: $instanceName,
number: $number,
text: $message
);
});
Bus::batch($jobs)
->name("campaign-{$instanceName}")
->allowFailures()
->onQueue('broadcasts')
->dispatch();
}
}
Example: Scheduled Messages¶
namespace App\Console\Commands;
use Lynkbyte\EvolutionApi\Jobs\SendMessageJob;
class SendScheduledMessages extends Command
{
public function handle(): void
{
$messages = ScheduledMessage::where('send_at', '<=', now())
->where('status', 'pending')
->get();
foreach ($messages as $message) {
SendMessageJob::text(
instanceName: $message->instance,
number: $message->number,
text: $message->content
)->dispatch();
$message->update(['status' => 'queued']);
}
}
}