Laravel 5.5+Mailgun批量发送数千营销邮件失败求助
Hey there! Let's figure out why your current loop is only sending ~530 emails and how to scale this to thousands without issues.
First, the problem with your synchronous loop: when you call send() in a web request, you're tying up the server process until every email is sent. This leads to timeouts (web servers usually kill requests after 30-60 seconds) and you're probably hitting Mailgun's rate limits (they restrict how many emails you can send per minute/hour based on your plan). Plus, sending hundreds of emails in sequence can eat up server memory and cause failures mid-loop.
Here are two solid solutions to fix this:
1. Use Laravel Queues (Asynchronous Sending)
This is the Laravel-native way to handle bulk tasks—instead of sending emails immediately, you queue them to be processed in the background. This keeps your web request fast and avoids timeouts, plus you can control how many emails are sent at once to stay under Mailgun's limits.
Step 1: Update Your Mailable to Support Queuing
Add the ShouldQueue interface and the Queueable trait to your EmailInstance class:
<?php namespace App\Mail; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; use Illuminate\Contracts\Queue\ShouldQueue; class EmailInstance extends Mailable implements ShouldQueue { use Queueable, SerializesModels; public $variable; public $email; public $baseUrl; public function __construct($variable, $email, $baseUrl) { $this->variable = $variable; $this->email = $email; $this->baseUrl = $baseUrl; } public function build() { // Keep your existing view/template logic here return $this->view('emails.your-campaign-template'); } }
Step 2: Modify Your Controller to Queue Emails
Replace send() with queue() (or later() if you want to delay sending):
public function mailCampaign() { // Code to get all email data in JSON $emails = json_decode($data->getBody()); $baseUrl = config('constants.base_url'); foreach($emails as $email){ // Queue the email instead of sending it immediately Mail::to($email)->queue(new EmailInstance($variable, $email, $baseUrl)); // Optional: Add a small delay between jobs to avoid spamming Mailgun's API // Mail::to($email)->later(now()->addSeconds(1), new EmailInstance($variable, $email, $baseUrl)); } $data = ['message' => 'Email campaign has been queued for sending. We\'ll process them in the background!']; return response()->view('emails.mail', $data, 200); }
Step 3: Set Up & Run the Queue Worker
- First, create the queue jobs table (we'll use the database driver for simplicity):
php artisan queue:table php artisan migrate - Update your
.envfile to use the database queue:QUEUE_CONNECTION=database - Start the queue worker to process the jobs:
php artisan queue:work - Production Tip: Use a process manager like Supervisor to keep the queue worker running 24/7 (it'll restart if it crashes).
2. Use Mailgun's Bulk Send API (Even More Efficient)
Mailgun has a dedicated bulk send endpoint that lets you send to hundreds/thousands of recipients in a single API call. This reduces the number of requests you make to Mailgun, which is faster and less likely to trigger rate limits.
Example Code for Bulk Sending
public function mailCampaign() { $emails = json_decode($data->getBody()); $baseUrl = config('constants.base_url'); $mailgunDomain = config('services.mailgun.domain'); $mailgunSecret = config('services.mailgun.secret'); // Prepare personalized data for each recipient (if needed) $recipientVariables = []; foreach ($emails as $email) { $recipientVariables[$email] = [ 'variable' => $variable, 'baseUrl' => $baseUrl ]; } // Split emails into chunks if you hit Mailgun's single-request limit (usually 1000 recipients) $emailChunks = array_chunk($emails, 1000); foreach ($emailChunks as $chunk) { try { $client = new \GuzzleHttp\Client(); $client->post("https://api.mailgun.net/v3/{$mailgunDomain}/messages", [ 'auth' => ['api', $mailgunSecret], 'form_params' => [ 'from' => 'Your Brand <noreply@your-domain.com>', 'to' => $chunk, 'subject' => 'Your Awesome Campaign Subject', 'html' => view('emails.your-campaign-template')->render(), 'recipient-variables' => json_encode($recipientVariables), // Optional: Control sending rate to stay under limits 'o:rate' => '150/min' // Adjust based on your Mailgun plan ] ]); } catch (\Exception $e) { // Log failed chunks for later retry \Log::error("Failed to send bulk email chunk: " . $e->getMessage()); } } $data = ['message' => 'Bulk email campaign has been sent via Mailgun\'s API.']; return response()->view('emails.mail', $data, 200); }
Key Notes to Avoid Future Issues
- Test First: Always test with a small batch (10-20 emails) before sending to thousands.
- Handle Failures: For queues, use
php artisan queue:failedto check failed jobs andqueue:retryto reprocess them. For bulk API, log failed chunks and retry manually. - Check Mailgun Limits: Log into your Mailgun dashboard to view your plan's sending limits (rate limits, daily totals) and adjust your queue/batch size accordingly.
- Personalization: Both solutions support personalized content—use the
recipient-variablesin Mailgun's API or pass data to your mailable for queueing.
内容的提问来源于stack exchange,提问作者DukesNuz

