在 Laravel 项目中批量发送邮件时,单个邮件的发送失败不应该导致整个发送流程中断,我们需要实现跳过错误继续遍历剩余邮件的能力,保证大部分正常邮件可以顺利投递。

常见邮件发送错误场景
批量发送邮件时可能遇到的错误主要包括以下几类:
- 收件人邮箱地址格式错误,导致邮件无法投递
- 邮件服务商接口临时限流,单个请求发送失败
- 邮件内容包含违规内容,被服务商拒绝发送
- 网络波动导致发送请求超时
基础实现:使用 try-catch 捕获单个邮件异常
最直接的方式是在遍历发送邮件的循环中,为每个邮件发送逻辑包裹异常捕获代码,单个邮件发送抛出异常时直接跳过,继续处理下一个。
假设我们有一个用户列表,需要给每个用户发送通知邮件,基础实现代码如下:
<?php
namespace AppServices;
use AppMailUserNotifyMail;
use AppModelsUser;
use IlluminateSupportFacadesMail;
use IlluminateSupportFacadesLog;
class MailService
{
/**
* 批量发送用户通知邮件,跳过错误继续遍历
* @param array $userIds 用户ID列表
* @return array 发送结果统计
*/
public function batchSendUserMail(array $userIds): array
{
$successCount = 0;
$failCount = 0;
$failUsers = [];
foreach ($userIds as $userId) {
try {
// 查询用户信息
$user = User::find($userId);
if (!$user || !$user->email) {
// 用户不存在或无邮箱,记录失败跳过
$failCount++;
$failUsers[] = ['user_id' => $userId, 'reason' => '用户不存在或无邮箱'];
continue;
}
// 发送邮件
Mail::to($user->email)->send(new UserNotifyMail($user));
$successCount++;
} catch (Exception $e) {
// 捕获所有异常,记录失败信息后继续循环
$failCount++;
$failUsers[] = [
'user_id' => $userId,
'reason' => $e->getMessage()
];
// 记录错误日志方便后续排查
Log::error('邮件发送失败', [
'user_id' => $userId,
'error' => $e->getMessage()
]);
}
}
return [
'success_count' => $successCount,
'fail_count' => $failCount,
'fail_users' => $failUsers
];
}
}
进阶优化:自定义邮件发送失败处理
如果需要在跳过错误的同时,对失败邮件做重试或者分类处理,可以自定义异常处理器,针对不同类型的异常做差异化处理。
自定义邮件异常类
首先定义一个自定义的邮件异常类,方便区分邮件相关的错误:
<?php
namespace AppExceptions;
use Exception;
class MailSendException extends Exception
{
// 异常类型常量
const TYPE_INVALID_EMAIL = 1; // 邮箱格式错误
const TYPE_API_LIMIT = 2; // 接口限流
const TYPE_CONTENT_REJECT = 3;// 内容被拒
const TYPE_TIMEOUT = 4; // 超时
protected $type;
public function __construct(string $message = "", int $type = 0, int $code = 0, ?Exception $previous = null)
{
parent::__construct($message, $code, $previous);
$this->type = $type;
}
public function getType(): int
{
return $this->type;
}
}
在发送逻辑中抛出自定义异常
修改邮件发送逻辑,针对不同错误场景抛出对应的自定义异常:
<?php
namespace AppServices;
use AppExceptionsMailSendException;
use AppMailUserNotifyMail;
use AppModelsUser;
use IlluminateSupportFacadesMail;
use IlluminateSupportFacadesLog;
use IlluminateSupportFacadesValidator;
class MailService
{
/**
* 发送单个用户邮件,抛出自定义异常
* @param User $user
* @throws MailSendException
*/
private function sendSingleMail(User $user): void
{
// 校验邮箱格式
$validator = Validator::make(['email' => $user->email], [
'email' => 'required|email'
]);
if ($validator->fails()) {
throw new MailSendException('邮箱格式错误', MailSendException::TYPE_INVALID_EMAIL);
}
try {
Mail::to($user->email)->send(new UserNotifyMail($user));
} catch (Exception $e) {
// 这里可以根据异常信息判断错误类型,简化为示例直接归类
if (strpos($e->getMessage(), 'limit') !== false) {
throw new MailSendException('接口限流', MailSendException::TYPE_API_LIMIT, 0, $e);
} elseif (strpos($e->getMessage(), 'reject') !== false) {
throw new MailSendException('内容被拒', MailSendException::TYPE_CONTENT_REJECT, 0, $e);
} else {
throw new MailSendException('发送超时', MailSendException::TYPE_TIMEOUT, 0, $e);
}
}
}
/**
* 批量发送邮件,跳过错误继续遍历
* @param array $userIds
* @return array
*/
public function batchSendWithCustomException(array $userIds): array
{
$result = [
'success' => 0,
'fail' => 0,
'fail_detail' => []
];
foreach ($userIds as $userId) {
try {
$user = User::find($userId);
if (!$user) {
$result['fail']++;
$result['fail_detail'][] = ['user_id' => $userId, 'type' => '用户不存在'];
continue;
}
$this->sendSingleMail($user);
$result['success']++;
} catch (MailSendException $e) {
$result['fail']++;
$result['fail_detail'][] = [
'user_id' => $userId,
'type' => $e->getType(),
'message' => $e->getMessage()
];
Log::error('自定义邮件异常', [
'user_id' => $userId,
'type' => $e->getType(),
'message' => $e->getMessage()
]);
} catch (Exception $e) {
// 兜底捕获其他未预期的异常
$result['fail']++;
$result['fail_detail'][] = [
'user_id' => $userId,
'type' => '未知错误',
'message' => $e->getMessage()
];
}
}
return $result;
}
}
注意事项
- 异常捕获范围不要过大,避免隐藏真正的代码逻辑错误,建议优先捕获邮件发送相关的特定异常
- 失败信息需要做好日志记录,方便后续排查问题,尤其是批量发送场景下定位具体失败原因
- 如果邮件服务商有发送频率限制,跳过错误的同时可以加入短暂的休眠,避免频繁请求导致更多失败
- 对于失败的邮件,可以单独存入数据库,后续提供手动重试的功能,提升用户体验
总结
在 Laravel 邮件发送中实现跳过错误继续遍历的核心是通过try-catch异常捕获机制,将单个邮件的发送逻辑包裹在独立的异常处理块中,单个邮件发送抛出异常时不影响循环继续执行。通过自定义异常类还可以对不同类型的错误做差异化处理,结合日志记录和失败重试机制,能够大幅提升批量邮件发送功能的稳定性。实际开发中可以根据项目需求,灵活调整异常处理逻辑和失败后的处理策略。
Laravel邮件发送异常处理遍历发送skip_error修改时间:2026-07-20 07:18:33