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

Build docs developers (and LLMs) love