Skip to main content

Overview

Scheduled Messages allow you to automate Discord communication by scheduling messages for future delivery. The system supports:
  • One-time messages: Send once at a specific date and time
  • Recurring messages: Repeat daily, weekly, or monthly
  • File attachments: Include images and videos (up to 10MB)
  • Timezone support: Schedule in your local timezone
  • Pause/resume: Control recurring message execution
Scheduled messages are processed every minute by Laravel’s scheduler and executed by queue workers.

Architecture

The automation system uses multiple Laravel components:
1

Cron Job

Executes php artisan schedule:run every minute
2

Scheduler

Launches the scheduled-messages:process command
3

Command

Finds messages where next_send_at <= now()
4

Job Queue

Dispatches SendScheduledMessage jobs to workers
5

Worker Execution

  • Sends message to Discord API
  • Updates next_send_at for recurring messages
  • Marks one-time messages as completed
  • Auto-deletes attached files after sending

Creating Scheduled Messages

One-Time Messages

Schedule a message to send once at a specific date and time:
1

Navigate to Scheduled Messages

Click Scheduled Messages in the sidebar, then Create New
2

Select Webhook

Choose which webhook will deliver the message
You can select from your owned webhooks or those shared with you.
3

Configure Message Content

Compose your message using the visual editor:
  • Text content (max 2000 characters)
  • Up to 10 embeds with full customization
  • Optional template loading
4

Set Schedule Type

Select One-time and choose:
  • Date and time
  • Timezone (default: Europe/Madrid)
5

Attach Files (Optional)

Upload images or videos:
  • Max 10 files per message
  • 10MB per file limit
  • Supported formats: JPG, PNG, GIF, WebP, MP4, MOV, AVI
Code Reference: app/app/Controllers/ScheduledMessageController.php:68-137

Recurring Messages

Create messages that repeat on a schedule:
{
  "schedule_type": "recurring",
  "recurrence_pattern": {
    "frequency": "daily",
    "time": "09:00"
  },
  "timezone": "America/New_York"
}
For weekly schedules, days are numbered 0-6 where 0 = Sunday, 1 = Monday, etc.

Setting Send Limits

Control how many times a recurring message will be sent:
'max_sends' => 10 // Stop after 10 deliveries
Leave max_sends null for unlimited recurring messages.

File Attachments

Upload Process

Files are stored temporarily during scheduling (app/app/Controllers/ScheduledMessageController.php:120-133):
$path = $file->store("scheduled_messages/{$scheduledMessage->id}", 'local');

$scheduledMessage->files()->create([
    'filename' => $filename,
    'stored_path' => $path,
    'mime_type' => $file->getMimeType(),
    'size' => $file->getSize(),
]);
Files are stored in: storage/app/scheduled_messages/{message_id}/

Auto-Deletion

Files are automatically deleted after successful delivery to save storage space.
This happens in the SendScheduledMessage job after Discord confirms receipt. Storage Path: Files are stored using Laravel’s local disk:
'local' => [
    'driver' => 'local',
    'root' => storage_path('app'),
],

Managing Scheduled Messages

View All Messages

The index page shows all your scheduled messages with filters:
  • Status: Pending, Completed, Failed, Paused
  • Type: One-time, Recurring
  • Pagination: 20 messages per page
Code Reference: app/app/Controllers/ScheduledMessageController.php:17-42

Pause and Resume

Control recurring message execution: Pause a Message (app/app/Models/ScheduledMessage.php:179-184):
public function pause(): void
{
    if ($this->schedule_type === 'recurring' && $this->status === 'pending') {
        $this->update(['status' => 'paused']);
    }
}
Resume a Message (app/app/Models/ScheduledMessage.php:186-195):
public function resume(): void
{
    if ($this->status === 'paused') {
        $nextSendTime = $this->calculateNextSendTime();
        $this->update([
            'status' => 'pending',
            'next_send_at' => $nextSendTime,
        ]);
    }
}
One-time messages cannot be paused since they execute once and complete.

Edit Scheduled Messages

You can edit scheduled messages only if they haven’t been sent yet (app/app/Controllers/ScheduledMessageController.php:172-175):
if ($scheduled->send_count > 0) {
    return back()->withErrors(['error' => 'Cannot edit a message that has already been sent']);
}
Editable fields:
  • Webhook selection
  • Message content
  • Schedule time/pattern
  • Timezone
  • Max sends limit
  • File attachments

Delete Messages

Deleting a scheduled message:
  1. Removes the database record
  2. Deletes all associated files via observer
  3. Cannot be undone
Code Reference: app/app/Controllers/ScheduledMessageController.php:252-261

Timezone Handling

The system converts all schedules to UTC for storage (app/app/Controllers/ScheduledMessageController.php:85-88):
$nextSendAt = Carbon::parse($validated['scheduled_at'], $validated['timezone'])
    ->setTimezone('UTC');
When calculating next send times for recurring messages, it converts back to the user’s timezone, calculates the next occurrence, then converts to UTC for storage. Supported Timezones: Any valid PHP timezone identifier (e.g., America/New_York, Europe/Paris, Asia/Tokyo)

Message States

Scheduled messages progress through several states:
StateDescriptionNext Actions
pendingWaiting to be sentAutomatically processes when next_send_at arrives
processingCurrently being sentTransitions to completed or failed
completedSuccessfully sent (one-time) or reached max_sendsNo further action
failedDelivery failedCheck error_message field, can be deleted
pausedManually paused (recurring only)Can be resumed
Code Reference: app/app/Models/ScheduledMessage.php:145-169 (markAsSent method)

Recurrence Calculation

The system calculates next send times based on frequency (app/app/Models/ScheduledMessage.php:88-143): Daily:
$next = $now->copy()->setTime((int)$hour, (int)$minute, 0);
if ($next <= $now) {
    $next->addDay();
}
Weekly:
for ($i = 0; $i < 7; $i++) {
    if (in_array($next->dayOfWeek, $days) && $next > $now) {
        $found = true;
        break;
    }
    $next->addDay();
}
Monthly:
$next = $now->copy()->setTime((int)$hour, (int)$minute, 0)->day($day);
if ($next <= $now) {
    $next->addMonth();
}

Integration with Templates

Load saved templates when creating scheduled messages:
'template_id' => $validated['template_id'],
The template relationship is stored but the actual content is copied into message_content. This ensures the scheduled message remains unchanged even if the template is modified later. Code Reference: app/app/Controllers/ScheduledMessageController.php:109

Worker Configuration

Production Requirement: You must run queue workers continuously using Supervisor.

Supervisor Configuration

[program:discord-webhook-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /path/to/app/artisan queue:work --queue=default --sleep=3 --tries=3
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/path/to/app/storage/logs/worker.log

Cron Configuration

Add to your crontab:
* * * * * php /path/to/app/artisan schedule:run >> /dev/null 2>&1
Run php artisan queue:work in development mode for immediate processing.

Monitoring and Debugging

Check Queue Status

php artisan queue:listen

View Failed Jobs

php artisan queue:failed

Retry Failed Jobs

php artisan queue:retry all

Logs Location

Scheduled message processing logs appear in:
  • storage/logs/laravel.log - General application logs
  • storage/logs/worker.log - Queue worker output (if using Supervisor)

Troubleshooting

Messages Not Sending

1

Verify Cron is Running

grep CRON /var/log/syslog
2

Check Queue Workers

ps aux | grep "queue:work"
3

Inspect Failed Jobs Table

SELECT * FROM failed_jobs ORDER BY failed_at DESC LIMIT 10;
4

Review Error Messages

Check the error_message field on failed scheduled messages

File Upload Issues

Problem: Files not attaching to messages Solutions:
  • Verify file size is under 10MB
  • Check file format is supported
  • Ensure storage/app/scheduled_messages/ is writable
  • Review upload_max_filesize and post_max_size in php.ini

Timezone Confusion

Problem: Messages sending at wrong times Solutions:
  • Verify timezone setting matches your location
  • Check server timezone: date command
  • Confirm timezone in .env: APP_TIMEZONE=Europe/Madrid
  • Ensure database stores UTC timestamps

Best Practices

Test First

Create one-time messages in the near future to verify configuration before setting up recurring messages.

Set Max Sends

For recurring messages, always set max_sends to prevent infinite loops during testing.

Monitor Storage

If using many file attachments, monitor storage usage even though files auto-delete.

Use Templates

Save frequently used messages as templates for quick scheduling.

Next Steps

Message History

View delivery history and analytics

AI Generation

Generate message content automatically