1
0
mirror of https://github.com/freescout-helpdesk/freescout.git synced 2024-11-24 19:33:07 +01:00

Logging send errors

This commit is contained in:
FreeScout 2018-08-22 00:26:42 -07:00
parent 372ab66809
commit 0b9c93e00e
9 changed files with 102 additions and 75 deletions

View File

@ -59,7 +59,9 @@ class SendNotificationToUsers implements ShouldQueue
$headers['In-Reply-To'] = '<'.$prev_message_id.'>';
$headers['References'] = '<'.$prev_message_id.'>';
$all_failures = [];
// We throw an exception if any of the send attempts throws an exception (connection error, etc)
$global_exception = null;
foreach ($this->users as $user) {
$message_id = \App\Mail\Mail::MESSAGE_ID_PREFIX_NOTIFICATION.'-'.$last_thread->id.'-'.$user->id.'-'.time().'@'.$mailbox->getEmailDomain();
$headers['Message-ID'] = $message_id;
@ -77,43 +79,66 @@ class SendNotificationToUsers implements ShouldQueue
}
$from = ['address' => $mailbox->email, 'name' => $from_name];
Mail::to([['name' => $user->getFullName(), 'email' => $user->email]])
->send(new UserNotification($user, $this->conversation, $this->threads, $headers, $from));
$exception = null;
$failures = Mail::failures();
try {
Mail::to([['name' => $user->getFullName(), 'email' => $user->email]])
->send(new UserNotification($user, $this->conversation, $this->threads, $headers, $from));
} catch (\Exception $e) {
// We come here in case SMTP server unavailable for example
activity()
->causedBy($user)
->withProperties([
'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')',
])
->useLog(\App\ActivityLog::NAME_EMAILS_SENDING)
->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_USER);
// Save to send log
if (!empty($failures) && in_array($user->email, $failures)) {
$status = SendLog::STATUS_SEND_ERROR;
} else {
$status = SendLog::STATUS_ACCEPTED;
$exception = $e;
$global_exception = $e;
}
SendLog::log($last_thread->id, $message_id, $user->email, $status, null, $user->id);
$all_failures = array_merge($all_failures, $failures);
if ($exception) {
$status = SendLog::STATUS_SEND_ERROR;
$status_message = $exception->getMessage();
} else {
$failures = Mail::failures();
// Save to send log
if (!empty($failures) && in_array($user->email, $failures)) {
$status = SendLog::STATUS_SEND_ERROR;
} else {
$status = SendLog::STATUS_ACCEPTED;
}
}
SendLog::log($last_thread->id, $message_id, $user->email, $status, null, $user->id, $status_message);
}
if (!empty($all_failures)) {
throw new \Exception('Could not send email to: '.implode(', ', $all_failures));
if ($global_exception) {
throw $global_exception;
}
}
/**
* The job failed to process.
* This method is called after attempts had finished.
* At this stage method has access only to variables passed in constructor.
*
* @param Exception $exception
*
* @return void
*/
public function failed(\Exception $e)
{
// Write to activity log
activity()
//->causedBy($this->customer)
->withProperties([
'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')',
//'to' => $this->customer->getMainEmail(),
])
->useLog(\App\ActivityLog::NAME_EMAILS_SENDING)
->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_USER);
}
// public function failed(\Exception $e)
// {
// // Write to activity log
// activity()
// //->causedBy($this->customer)
// ->withProperties([
// 'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')',
// //'to' => $this->customer->getMainEmail(),
// ])
// ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING)
// ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_USER);
// }
}

View File

@ -93,17 +93,21 @@ class SendReplyToCustomer implements ShouldQueue
->bcc($bcc_array)
->send(new ReplyToCustomer($this->conversation, $this->threads, $headers));
} catch (\Exception $e) {
// We come here in case SMTP server unavailable for example
activity()
//->causedBy()
->withProperties([
->causedBy($this->customer)
->withProperties([
'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')',
])
->useLog(\App\ActivityLog::NAME_EMAILS_SENDING)
->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_CUSTOMER);
])
->useLog(\App\ActivityLog::NAME_EMAILS_SENDING)
->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_CUSTOMER);
// Failures will be save to send log when retry attempts will finish
// Failures will be saved to send log when retry attempts will finish
$this->failures = $this->recipients;
// Save to send log
$this->saveToSendLog($e->getMessage());
throw $e;
}
@ -117,51 +121,50 @@ class SendReplyToCustomer implements ShouldQueue
// Save to send log
$this->saveToSendLog();
if (!empty($failures)) {
throw new \Exception('Could not send email to: '.implode(', ', $failures));
}
}
/**
* The job failed to process.
* This method is called after number of attempts had finished.
* This method is called after attempts had finished.
* At this stage method has access only to variables passed in constructor.
*
* @param Exception $exception
*
* @return void
*/
public function failed(\Exception $e)
{
activity()
->causedBy($this->customer)
->withProperties([
'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')',
'to' => $this->customer->getMainEmail(),
])
->useLog(\App\ActivityLog::NAME_EMAILS_SENDING)
->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_CUSTOMER);
// public function failed(\Exception $e)
// {
// activity()
// ->causedBy($this->customer)
// ->withProperties([
// 'error' => $e->getMessage().'; File: '.$e->getFile().' ('.$e->getLine().')',
// 'to' => $this->customer->getMainEmail(),
// ])
// ->useLog(\App\ActivityLog::NAME_EMAILS_SENDING)
// ->log(\App\ActivityLog::DESCRIPTION_EMAILS_SENDING_ERROR_TO_CUSTOMER);
$this->saveToSendLog();
}
// $this->saveToSendLog();
// }
/**
* Save failed email to send log.
* Save emails to send log.
*/
public function saveToSendLog()
public function saveToSendLog($error_message = '')
{
foreach ($this->recipients as $recipient) {
if (in_array($recipient, $this->failures)) {
$status = SendLog::STATUS_SEND_ERROR;
$status_message = $error_message;
} else {
$status = SendLog::STATUS_ACCEPTED;
$status_message = '';
}
if ($this->customer_email == $recipient) {
$customer_id = $this->customer->id;
} else {
$customer_id = null;
}
SendLog::log($this->last_thread->id, $this->message_id, $recipient, $status, $customer_id);
SendLog::log($this->last_thread->id, $this->message_id, $recipient, $status, $customer_id, null, $status_message);
}
}
}

View File

@ -27,7 +27,7 @@ class SendNotificationToUsers
// Detect event type by event class
switch (get_class($event)) {
case 'App\Events\UserReplied':
$caused_by_user_id = $event->conversation->created_by_user_id;
$caused_by_user_id = $event->thread->created_by_user_id;
$event_type = Subscription::EVENT_TYPE_USER_REPLIED;
break;
case 'App\Events\UserCreatedConversation':

View File

@ -281,6 +281,7 @@ class Subscription extends Model
if (!$users_to_notify) {
continue;
}
foreach ($users_to_notify as $medium => $medium_users_to_notify) {
// Remove current user from recipients if action caused by current user
@ -291,17 +292,6 @@ class Subscription extends Model
}
if (count($medium_users_to_notify)) {
// Remove duplicate users
// $user_ids = [];
// foreach ($users as $i => $user) {
// if (in_array($user->id, $user_ids)) {
// unset($users[$i]);
// } else {
// $user_ids[] = $user->id;
// }
// }
$notify[$medium][$event['conversation']->id] = [
// Users subarray contains all users who need to receive notification for all events
'users' => array_unique(array_merge($users, $medium_users_to_notify)),

View File

@ -156,12 +156,12 @@ return [
/*
|--------------------------------------------------------------------------
| Parameters used to run queued jobs processing.
| Check for new jobs every 5 seconds.
| Retry failed jobs max 7 times (for example no connection to the mail server)
| Tries happen every minute.
| Checks for new jobs every 5 seconds.
| Do not set more than 1 retry, as it may lead to sending repeated emails if one recipient fails
| and another succeeds.
|-------------------------------------------------------------------------
*/
'queue_work_params' => ['--queue' => 'emails', '--sleep' => '5', '--tries' => '7'],
'queue_work_params' => ['--queue' => 'emails', '--sleep' => '5', '--tries' => '1'],
/*
|--------------------------------------------------------------------------

5
public/js/main.js vendored
View File

@ -732,8 +732,13 @@ function showModal(a, onshow)
var modal_class = a.attr('data-modal-class');
// Fit modal body into the screen
var fit = a.attr('data-modal-fit');
var size = a.attr('data-modal-size'); // lg or sm
var modal;
if (size) {
modal_class += ' modal-'+size;
}
var html = [
'<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="jsmodal-label" aria-hidden="true">',
'<div class="modal-dialog '+modal_class+'">',

View File

@ -1,5 +1,5 @@
@if (!$customers_log && !$users_log)
<div class="alert alert-warning">{{ __("Log is empty") }}</div>
<div class="alert alert-warning">{{ __("There were no send attempts yet") }}</div>
@else
@if (!empty($customers_log))
<h5>{{ __("Emails to customers") }}</h5>
@ -16,7 +16,7 @@
@if ($loop->index == 0)
<td rowspan="{{ count($logs) }}">{{ $email }}</td>
@endif
<td class="small">{{ App\User::dateFormat($log->created_at) }}</td>
<td>{{ App\User::dateFormat($log->created_at, 'M j, Y H:i:s') }}</td>
<td>
<span class="@if ($log->isErrorStatus())text-danger @elseif ($log->isSuccessStatus()) text-success @endif">{{ $log->getStatusName() }}</span>
@if ($log->status_message)
@ -44,7 +44,7 @@
@if ($loop->index == 0)
<td rowspan="{{ count($logs) }}">{{ $email }}</td>
@endif
<td class="small">{{ App\User::dateFormat($log->created_at) }}</td>
<td>{{ App\User::dateFormat($log->created_at) }}</td>
<td>
<span class="@if ($log->isErrorStatus())text-danger @elseif ($log->isSuccessStatus()) text-success @endif">{{ $log->getStatusName() }}</span>
@if ($log->status_message)

View File

@ -188,7 +188,7 @@
@if (Auth::user()->isAdmin())
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li><a href="{{ route('conversations.ajax_html', ['action' =>
'send_log']) }}?thread_id={{ $thread->id }}" title="{{ __("View outgoing emails") }}" data-trigger="modal" data-modal-title="{{ __("Outgoing Emails") }}">{{ __("Outgoing Emails") }}</a></li>
'send_log']) }}?thread_id={{ $thread->id }}" title="{{ __("View outgoing emails") }}" data-trigger="modal" data-modal-title="{{ __("Outgoing Emails") }}" data-modal-size="lg">{{ __("Outgoing Emails") }}</a></li>
</ul>
@endif
</div>
@ -293,11 +293,13 @@
<li><a href="#" title="" class="thread-edit-trigger">{{ __("Edit") }} (todo)</a></li>
<li><a href="javascript:alert('todo: implement hiding threads');void(0);" title="" class="thread-hide-trigger">{{ __("Hide") }} (todo)</a></li>
<li><a href="javascript:alert('todo: implement creating new conversation from thread');void(0);" title="{{ __("Start a conversation from this thread") }}" class="new-conv">{{ __("New Conversation") }}</a></li>
<li><a href="{{ route('conversations.ajax_html', ['action' =>
'show_original']) }}?thread_id={{ $thread->id }}" title="{{ __("Show original message headers") }}" data-trigger="modal" data-modal-title="{{ __("Original Message Headers") }}" data-modal-fit="true">{{ __("Show Original") }}</a></li>
@if ($thread->headers)
<li><a href="{{ route('conversations.ajax_html', ['action' =>
'show_original']) }}?thread_id={{ $thread->id }}" title="{{ __("Show original message headers") }}" data-trigger="modal" data-modal-title="{{ __("Original Message Headers") }}" data-modal-fit="true" data-modal-size="lg">{{ __("Show Original") }}</a></li>
@endif
@if (Auth::user()->isAdmin())
<li><a href="{{ route('conversations.ajax_html', ['action' =>
'send_log']) }}?thread_id={{ $thread->id }}" title="{{ __("View outgoing emails") }}" data-trigger="modal" data-modal-title="{{ __("Outgoing Emails") }}">{{ __("Outgoing Emails") }}</a></li>
'send_log']) }}?thread_id={{ $thread->id }}" title="{{ __("View outgoing emails") }}" data-trigger="modal" data-modal-title="{{ __("Outgoing Emails") }}" data-modal-size="lg">{{ __("Outgoing Emails") }}</a></li>
@endif
</ul>
</div>

View File

@ -34,10 +34,12 @@
@foreach ($logs as $row)
<tr>
@foreach ($cols as $col)
<td>
<td class="break-words">
@if (isset($row[$col]))
@if ($col == 'user' || $col == 'customer')
<a href="{{ $row[$col]->url() }}">{{ $row[$col]->getFullName(true) }}</a>
@elseif ($col == 'date')
{{ App\User::dateFormat(new Illuminate\Support\Carbon($row[$col]), 'M j, H:i:s') }}
@else
{{ $row[$col] }}
@endif