61 lines
3.1 KiB
PHP
61 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\MailFlow;
|
|
|
|
use App\Models\MailMessage;
|
|
use App\Models\User;
|
|
|
|
class AutoReplyService
|
|
{
|
|
public function prepare(MailMessage $incoming, array $headers): ?MailMessage
|
|
{
|
|
if (!Settings::get('auto_reply_enabled')) {
|
|
return null;
|
|
}
|
|
$sender = strtolower($incoming->email);
|
|
$own = [strtolower((string) Settings::get('reply_to')), strtolower((string) Settings::get('imap_username')),
|
|
strtolower((string) admin_setting('email_from_address', config('mail.from.address')))];
|
|
if (!filter_var($sender, FILTER_VALIDATE_EMAIL) || in_array($sender, $own, true)
|
|
|| preg_match('/(?:mailer-daemon|postmaster|no-?reply)@/i', $sender)
|
|
|| (isset($headers['auto-submitted']) && strtolower($headers['auto-submitted']) !== 'no')
|
|
|| isset($headers['list-id']) || isset($headers['x-auto-response-suppress'])
|
|
|| in_array(strtolower($headers['precedence'] ?? ''), ['bulk', 'list', 'junk'], true)
|
|
|| trim($headers['return-path'] ?? '') === '<>') {
|
|
return null;
|
|
}
|
|
// Binding is optional; never send to an arbitrary incoming Reply-To address.
|
|
$user = User::byEmail($sender)->first();
|
|
if ($user?->banned) {
|
|
return null;
|
|
}
|
|
if (MailMessage::where('parent_id', $incoming->id)->exists()) {
|
|
return null;
|
|
}
|
|
$day = now()->timezone('Asia/Shanghai')->startOfDay();
|
|
if (MailMessage::where('direction', 'out')->where('purpose', 'auto_reply')->where('email', $sender)
|
|
->where('created_at', '>=', $day->copy()->timezone(config('app.timezone', 'UTC')))->exists()) {
|
|
return null;
|
|
}
|
|
$outHeaders = ['Auto-Submitted' => 'auto-replied', 'X-Auto-Response-Suppress' => 'All'];
|
|
if (preg_match('/^<[^<>\r\n]{1,250}>$/', $headers['message-id'] ?? '')) {
|
|
$outHeaders['In-Reply-To'] = $headers['message-id'];
|
|
$outHeaders['References'] = $headers['message-id'];
|
|
}
|
|
$html = view('mail.default.notify', array_replace([
|
|
'name' => admin_setting('app_name', 'dpl'), 'url' => admin_setting('app_url', config('app.url')),
|
|
'content' => e((string) Settings::get('auto_reply_text')),
|
|
], Branding::values()))->render();
|
|
// The unique key reserves one reply per sender/day, including concurrent arrivals.
|
|
$reply = MailMessage::firstOrCreate(['message_key' => 'reply:' . $day->format('Y-m-d') . ':' . hash('sha256', $sender)], [
|
|
'parent_id' => $incoming->id,
|
|
'purpose' => 'auto_reply', 'user_id' => $user?->id, 'email' => $sender,
|
|
'subject' => Settings::get('auto_reply_subject'),
|
|
'expires_at' => $day->copy()->addDay()->timezone(config('app.timezone', 'UTC')),
|
|
'payload' => ['to' => $sender, 'subject' => Settings::get('auto_reply_subject'),
|
|
'html' => $html, 'attachments' => Branding::attachments(),
|
|
'reply_to' => Settings::get('reply_to'), 'headers' => $outHeaders],
|
|
]);
|
|
return $reply->wasRecentlyCreated ? $reply : null;
|
|
}
|
|
}
|