diff --git a/app/Exceptions/Common/TooManyEmailsSent.php b/app/Exceptions/Common/TooManyEmailsSent.php
new file mode 100644
index 000000000..2831835f5
--- /dev/null
+++ b/app/Exceptions/Common/TooManyEmailsSent.php
@@ -0,0 +1,10 @@
+middleware('permission:delete-sales-invoices')->only('destroy');
}
- /**
- * Show the form for creating a new resource.
- *
- * @param Document $invoice
- *
- * @return Response
- */
- public function create(Document $invoice)
+ public function create(Document $invoice): JsonResponse
{
$notification = new Notification($invoice, 'invoice_new_customer', true);
@@ -58,16 +55,9 @@ class InvoiceEmails extends Controller
]);
}
- /**
- * Store a newly created resource in storage.
- *
- * @param Request $request
- *
- * @return Response
- */
- public function store(Request $request)
+ public function store(Request $request): JsonResponse
{
- $response = $this->ajaxDispatch(new SendDocumentAsCustomMail($request, 'invoice_new_customer'));
+ $response = $this->sendEmail(new SendDocumentAsCustomMail($request, 'invoice_new_customer'));
if ($response['success']) {
$invoice = Document::find($request->get('document_id'));
diff --git a/app/Http/Controllers/Modals/TransactionEmails.php b/app/Http/Controllers/Modals/TransactionEmails.php
index b40eb6e3c..e4672906b 100644
--- a/app/Http/Controllers/Modals/TransactionEmails.php
+++ b/app/Http/Controllers/Modals/TransactionEmails.php
@@ -8,9 +8,13 @@ use App\Notifications\Portal\PaymentReceived as PaymentReceivedNotification;
use App\Notifications\Banking\Transaction as TransactionNotification;
use App\Jobs\Banking\SendTransactionAsCustomMail;
use App\Http\Requests\Common\CustomMail as Request;
+use Illuminate\Http\JsonResponse;
+use App\Traits\Emails;
class TransactionEmails extends Controller
{
+ use Emails;
+
/**
* Instantiate a new controller instance.
*/
@@ -23,17 +27,10 @@ class TransactionEmails extends Controller
$this->middleware('permission:delete-banking-transactions')->only('destroy');
}
- /**
- * Show the form for creating a new resource.
- *
- * @param Transaction $transaction
- *
- * @return Response
- */
- public function create(Transaction $transaction)
- {
+ public function create(Transaction $transaction): JsonResponse
+ {
$email_template = config('type.transaction.' . $transaction->type . '.email_template');
-
+
if (request()->get('email_template')) {
$email_template = request()->get('email_template');
}
@@ -42,7 +39,7 @@ class TransactionEmails extends Controller
case 'invoice_payment_customer':
$notification = new PaymentReceivedNotification($transaction->document, $transaction, $email_template, true);
break;
-
+
default:
$notification = new TransactionNotification($transaction, $email_template, true);
break;
@@ -73,20 +70,13 @@ class TransactionEmails extends Controller
]);
}
- /**
- * Store a newly created resource in storage.
- *
- * @param Request $request
- *
- * @return Response
- */
- public function store(Request $request)
+ public function store(Request $request): JsonResponse
{
$transaction = Transaction::find($request->get('transaction_id'));
$email_template = config('type.transaction.' . $transaction->type . '.email_template');
- $response = $this->ajaxDispatch(new SendTransactionAsCustomMail($request, $email_template));
+ $response = $this->sendEmail(new SendTransactionAsCustomMail($request, $email_template));
if ($response['success']) {
$route = config('type.transaction.' . $transaction->type . '.route.prefix');
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index f7cc49ecc..8d320b7a7 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -134,6 +134,10 @@ class Kernel extends HttpKernel
'import' => [
'throttle:import',
],
+
+ 'email' => [
+ 'throttle:email',
+ ],
];
/**
diff --git a/app/Providers/Route.php b/app/Providers/Route.php
index 952966034..69034ef4c 100644
--- a/app/Providers/Route.php
+++ b/app/Providers/Route.php
@@ -289,5 +289,12 @@ class Route extends Provider
RateLimiter::for('import', function (Request $request) {
return Limit::perMinute(config('app.throttles.import'));
});
+
+ RateLimiter::for('email', function (Request $request) {
+ return [
+ Limit::perDay(config('app.throttles.email.month'), 30),
+ Limit::perMinute(config('app.throttles.email.minute')),
+ ];
+ });
}
}
diff --git a/app/Traits/Emails.php b/app/Traits/Emails.php
new file mode 100644
index 000000000..ce8388a80
--- /dev/null
+++ b/app/Traits/Emails.php
@@ -0,0 +1,55 @@
+id;
+ $limit_per_month = config('app.throttles.email.month');
+ $decay_per_month = 60 * 60 * 24 * 30;
+
+ $can_send = RateLimiter::attempt($key_per_month, $limit_per_month, fn() => '', $decay_per_month);
+
+ if ($can_send) {
+ // Check if the user has reached the limit of emails per minute
+ $key_per_minute = 'email-minute:' . user()->id;
+ $limit_per_minute = config('app.throttles.email.minute');
+
+ $can_send = RateLimiter::attempt($key_per_minute, $limit_per_minute, fn() => '');
+ }
+
+ if ($can_send) {
+ $this->dispatch($job);
+
+ $response = [
+ 'success' => true,
+ 'error' => false,
+ 'data' => '',
+ 'message' => '',
+ ];
+
+ return $response;
+ }
+
+ $response = [
+ 'success' => false,
+ 'error' => true,
+ 'data' => null,
+ 'message' => 'Too many emails sent!',
+ ];
+
+ report(new TooManyEmailsSent('Too many emails sent!'));
+
+ return $response;
+ }
+}
diff --git a/config/app.php b/config/app.php
index dbf827b95..a01930e41 100644
--- a/config/app.php
+++ b/config/app.php
@@ -25,6 +25,10 @@ return [
'throttles' => [
'api' => env('APP_THROTTLES_API', '60'),
'import' => env('APP_THROTTLES_IMPORT', '1'),
+ 'email' => [
+ 'minute' => env('APP_THROTTLES_EMAIL_MINUTE', '3'),
+ 'month' => env('APP_THROTTLES_EMAIL_MONTH', '500'),
+ ],
],
/*
diff --git a/resources/views/components/layouts/admin/menu.blade.php b/resources/views/components/layouts/admin/menu.blade.php
index e63cb67e6..208aaf334 100644
--- a/resources/views/components/layouts/admin/menu.blade.php
+++ b/resources/views/components/layouts/admin/menu.blade.php
@@ -19,7 +19,7 @@