mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Working on emailers
This commit is contained in:
parent
1e5b96ddad
commit
a4f2d40d75
@ -23,6 +23,7 @@ use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Services\Email\MailObject;
|
||||
use App\Transformers\CreditTransformer;
|
||||
use App\Transformers\InvoiceTransformer;
|
||||
use App\Transformers\PurchaseOrderTransformer;
|
||||
@ -127,6 +128,8 @@ class EmailController extends BaseController
|
||||
'body' => $body,
|
||||
];
|
||||
|
||||
$mo = new MailObject;
|
||||
|
||||
if(Ninja::isHosted() && !$entity_obj->company->account->account_sms_verified)
|
||||
return response(['message' => 'Please verify your account to send emails.'], 400);
|
||||
|
||||
|
@ -73,7 +73,6 @@ class TokenAuth
|
||||
| session
|
||||
*/
|
||||
app('queue')->createPayloadUsing(function () use ($company_token) {
|
||||
nlog("setting DB ". $company_token->company->db);
|
||||
return ['db' => $company_token->company->db];
|
||||
});
|
||||
|
||||
|
@ -11,19 +11,14 @@
|
||||
|
||||
namespace App\Jobs\Entity;
|
||||
|
||||
use App\Events\Invoice\InvoiceReminderWasEmailed;
|
||||
use App\Events\Invoice\InvoiceWasEmailed;
|
||||
use App\Events\Invoice\InvoiceWasEmailedAndFailed;
|
||||
use App\Jobs\Mail\EntityFailedSendMailer;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\TemplateEmail;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Company;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use App\Utils\HtmlEngine;
|
||||
@ -34,7 +29,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/*Multi Mailer implemented*/
|
||||
|
@ -351,4 +351,16 @@ class Quote extends BaseModel
|
||||
{
|
||||
return ctrans('texts.quote');
|
||||
}
|
||||
|
||||
/**
|
||||
* calculateTemplate
|
||||
*
|
||||
* @param string $entity_string
|
||||
* @return string
|
||||
*/
|
||||
public function calculateTemplate(string $entity_string): string
|
||||
{
|
||||
return $entity_string;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,7 +37,6 @@ class MultiDBProvider extends ServiceProvider
|
||||
JobProcessing::class,
|
||||
function ($event) {
|
||||
if (isset($event->job->payload()['db'])) {
|
||||
nlog("Settings DB: " . $event->job->payload()['db']);
|
||||
MultiDB::setDb($event->job->payload()['db']);
|
||||
}
|
||||
}
|
||||
|
@ -1,263 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Email;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Services\Email\MailBuild;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
use Illuminate\Contracts\Mail\Mailable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use App\DataMapper\Analytics\EmailSuccess;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
||||
class BaseMailer implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected Company $company;
|
||||
|
||||
public int $tries = 4;
|
||||
|
||||
public ?string $client_postmark_secret = null;
|
||||
|
||||
public ?string $client_mailgun_secret = null;
|
||||
|
||||
public ?string $client_mailgun_domain = null;
|
||||
|
||||
public bool $override = false;
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
public Mail $mail;
|
||||
|
||||
private string $mailer = 'default';
|
||||
|
||||
public function __construct(public mixed $invitation, private ?string $db, public MailObject $mail_object)
|
||||
{
|
||||
|
||||
$this->invitation = $invitation;
|
||||
|
||||
$this->company = $invitation->company;
|
||||
|
||||
$this->db = $db;
|
||||
|
||||
$this->mail_object = $mail_object;
|
||||
|
||||
$this->override = $mail_object->override;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function companyCheck(): void
|
||||
{
|
||||
/* Handle bad state */
|
||||
if(!$this->company)
|
||||
$this->fail();
|
||||
|
||||
/* Handle deactivated company */
|
||||
if($this->company->is_disabled && !$this->override)
|
||||
$this->fail();
|
||||
|
||||
/* To handle spam users we drop all emails from flagged accounts */
|
||||
if(Ninja::isHosted() && $this->company->account && $this->company->account->is_flagged)
|
||||
$this->fail();
|
||||
|
||||
}
|
||||
|
||||
public function configureMailer(): self
|
||||
{
|
||||
$this->setMailDriver();
|
||||
|
||||
$this->mail = Mail::mailer($this->mailer);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the mail driver to use and applies any specific configuration
|
||||
* the the mailable
|
||||
*/
|
||||
private function setMailDriver(): self
|
||||
{
|
||||
|
||||
switch ($this->mail_object->settings->email_sending_method) {
|
||||
case 'default':
|
||||
$this->mailer = config('mail.default');
|
||||
break;
|
||||
// case 'gmail':
|
||||
// $this->mailer = 'gmail';
|
||||
// $this->setGmailMailer();
|
||||
// return $this;
|
||||
// case 'office365':
|
||||
// $this->mailer = 'office365';
|
||||
// $this->setOfficeMailer();
|
||||
// return $this;
|
||||
// case 'client_postmark':
|
||||
// $this->mailer = 'postmark';
|
||||
// $this->setPostmarkMailer();
|
||||
// return $this;
|
||||
// case 'client_mailgun':
|
||||
// $this->mailer = 'mailgun';
|
||||
// $this->setMailgunMailer();
|
||||
// return $this;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(Ninja::isSelfHost())
|
||||
$this->setSelfHostMultiMailer();
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows configuration of multiple mailers
|
||||
* per company for use by self hosted users
|
||||
*/
|
||||
private function setSelfHostMultiMailer(): void
|
||||
{
|
||||
|
||||
if (env($this->email_service->company->id . '_MAIL_HOST'))
|
||||
{
|
||||
|
||||
config([
|
||||
'mail.mailers.smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'host' => env($this->email_service->company->id . '_MAIL_HOST'),
|
||||
'port' => env($this->email_service->company->id . '_MAIL_PORT'),
|
||||
'username' => env($this->email_service->company->id . '_MAIL_USERNAME'),
|
||||
'password' => env($this->email_service->company->id . '_MAIL_PASSWORD'),
|
||||
],
|
||||
]);
|
||||
|
||||
if(env($this->email_service->company->id . '_MAIL_FROM_ADDRESS'))
|
||||
{
|
||||
$this->email_mailable
|
||||
->from(env($this->email_service->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->email_service->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME')));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ensure we discard any data that is not required
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cleanUpMailers(): void
|
||||
{
|
||||
$this->client_postmark_secret = false;
|
||||
|
||||
$this->client_mailgun_secret = false;
|
||||
|
||||
$this->client_mailgun_domain = false;
|
||||
|
||||
//always dump the drivers to prevent reuse
|
||||
app('mail.manager')->forgetMailers();
|
||||
}
|
||||
|
||||
|
||||
public function trySending()
|
||||
{
|
||||
try {
|
||||
|
||||
$mailer
|
||||
->to($this->mail_object->to_user->email)
|
||||
->send($this->mail_object->mailable);
|
||||
|
||||
/* Count the amount of emails sent across all the users accounts */
|
||||
Cache::increment($this->company->account->key);
|
||||
|
||||
LightLogs::create(new EmailSuccess($this->company->company_key))
|
||||
->send();
|
||||
|
||||
}
|
||||
catch(\Symfony\Component\Mime\Exception\RfcComplianceException $e) {
|
||||
nlog("Mailer failed with a Logic Exception {$e->getMessage()}");
|
||||
$this->fail();
|
||||
$this->cleanUpMailers();
|
||||
$this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
return;
|
||||
}
|
||||
catch(\Symfony\Component\Mime\Exception\LogicException $e){
|
||||
nlog("Mailer failed with a Logic Exception {$e->getMessage()}");
|
||||
$this->fail();
|
||||
$this->cleanUpMailers();
|
||||
$this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
return;
|
||||
}
|
||||
catch (\Exception | \Google\Service\Exception $e) {
|
||||
|
||||
nlog("Mailer failed with {$e->getMessage()}");
|
||||
$message = $e->getMessage();
|
||||
|
||||
/**
|
||||
* Post mark buries the proper message in a a guzzle response
|
||||
* this merges a text string with a json object
|
||||
* need to harvest the ->Message property using the following
|
||||
*/
|
||||
if(stripos($e->getMessage(), 'code 406') || stripos($e->getMessage(), 'code 300') || stripos($e->getMessage(), 'code 413'))
|
||||
{
|
||||
|
||||
$message = "Either Attachment too large, or recipient has been suppressed.";
|
||||
|
||||
$this->fail();
|
||||
$this->logMailError($e->getMessage(), $this->company->clients()->first());
|
||||
$this->cleanUpMailers();
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
//only report once, not on all tries
|
||||
if($this->attempts() == $this->tries)
|
||||
{
|
||||
|
||||
/* If the is an entity attached to the message send a failure mailer */
|
||||
if($this->nmo->entity)
|
||||
$this->entityEmailFailed($message);
|
||||
|
||||
/* Don't send postmark failures to Sentry */
|
||||
if(Ninja::isHosted() && (!$e instanceof ClientException))
|
||||
app('sentry')->captureException($e);
|
||||
|
||||
}
|
||||
|
||||
/* Releasing immediately does not add in the backoff */
|
||||
$this->release($this->backoff()[$this->attempts()-1]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function backoff()
|
||||
{
|
||||
return [5, 10, 30, 240];
|
||||
}
|
||||
|
||||
public function failed($exception = null)
|
||||
{
|
||||
|
||||
config(['queue.failed.driver' => null]);
|
||||
|
||||
}
|
||||
}
|
@ -13,13 +13,18 @@ namespace App\Services\Email;
|
||||
|
||||
use App\Models\Task;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Account;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\VendorContact;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\VendorHtmlEngine;
|
||||
use App\Jobs\Entity\CreateRawPdf;
|
||||
use Illuminate\Support\Facades\App;
|
||||
@ -30,7 +35,6 @@ use Illuminate\Contracts\Mail\Mailable;
|
||||
use App\DataMapper\EmailTemplateDefaults;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class MailBuild
|
||||
{
|
||||
@ -118,6 +122,30 @@ class MailBuild
|
||||
|
||||
$this->vendor = $vendor_contact?->vendor;
|
||||
|
||||
if($this->mail_entity?->invitation){
|
||||
|
||||
|
||||
if($this->mail_entity->invitation?->invoice){
|
||||
$this->mail_entity->mail_object->entity_string = 'invoice';
|
||||
$this->mail_entity->mail_object->entity_class = Invoice::class;
|
||||
}
|
||||
|
||||
if($this->mail_entity->invitation?->quote){
|
||||
$this->mail_entity->mail_object->entity_string = 'quote';
|
||||
$this->mail_entity->mail_object->entity_class = Quote::class;
|
||||
}
|
||||
|
||||
if($this->mail_entity->invitation?->credit){
|
||||
$this->mail_entity->mail_object->entity_string = 'credit';
|
||||
$this->mail_entity->mail_object->entity_class = Credit::class;
|
||||
}
|
||||
|
||||
if($this->mail_entity->invitation?->puchase_order){
|
||||
$this->mail_entity->mail_object->entity_string = 'purchase_order';
|
||||
$this->mail_entity->mail_object->entity_class = PurchaseOrder::class;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -261,7 +289,7 @@ class MailBuild
|
||||
elseif(is_string($this->mail_entity->mail_object->email_template_subject) && strlen($this->settings->{$this->mail_entity->mail_object->email_template_subject}) > 3)
|
||||
$this->mail_entity->mail_object->subject = $this->settings->{$this->mail_entity->mail_object->email_template_subject};
|
||||
else
|
||||
$this->mail_entity->mail_object->subject = EmailTemplateDefaults::getDefaultTemplate($this->mail_entity->mail_object->email_template_subject, $this->locale);
|
||||
$this->mail_entity->mail_object->subject = EmailTemplateDefaults::getDefaultTemplate($this->resolveBaseEntityTemplate(), $this->locale);
|
||||
|
||||
return $this;
|
||||
|
||||
@ -282,7 +310,7 @@ class MailBuild
|
||||
$this->mail_entity->mail_object->body = $this->settings->{$this->mail_entity->mail_object->email_template_body};
|
||||
}
|
||||
else{
|
||||
$this->mail_entity->mail_object->body = EmailTemplateDefaults::getDefaultTemplate($this->mail_entity->mail_object->email_template_body, $this->locale);
|
||||
$this->mail_entity->mail_object->body = EmailTemplateDefaults::getDefaultTemplate($this->resolveBaseEntityTemplate('body'), $this->locale);
|
||||
}
|
||||
|
||||
if($this->template == 'email.template.custom'){
|
||||
@ -292,6 +320,29 @@ class MailBuild
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Where no template is explicitly passed, we need to infer by the entity type -
|
||||
* which is hopefully resolvable.
|
||||
*
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
private function resolveBaseEntityTemplate(string $type = 'subject'): string
|
||||
{
|
||||
//handle statements being emailed
|
||||
//handle custom templates these types won't have a resolvable entity_string
|
||||
if(!$this->mail_entity->mail_object->entity_string)
|
||||
return 'email_template_invoice';
|
||||
|
||||
match($type){
|
||||
'subject' => $template = "email_subject_{$this->mail_entity->mail_object->entity_string}",
|
||||
'body' => $template = "email_template_{$this->mail_entity->mail_object->entity_string}",
|
||||
default => $template = "email_template_invoice",
|
||||
};
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the attachments for the email
|
||||
@ -365,24 +416,18 @@ class MailBuild
|
||||
|
||||
}
|
||||
|
||||
if($this->mail_entity->invitation?->invoice)
|
||||
$entity = 'invoice';
|
||||
|
||||
if($this->mail_entity->invitation?->quote)
|
||||
$entity = 'quote';
|
||||
|
||||
if($this->mail_entity->invitation?->credit)
|
||||
$entity = 'credit';
|
||||
if(!$this->mail_entity->mail_object->entity_string)
|
||||
return $this;
|
||||
|
||||
$pdf = ((new CreateRawPdf($this->mail_entity->invitation, $this->mail_entity->invitation->company->db))->handle());
|
||||
|
||||
nlog($this->mail_entity->mail_object->attachments);
|
||||
|
||||
$this->mail_entity->mail_object->attachments = array_merge($this->mail_entity->mail_object->attachments, [['file' => base64_encode($pdf), 'name' => $this->mail_entity->invitation->{$entity}->numberFormatter().'.pdf']]);
|
||||
$this->mail_entity->mail_object->attachments = array_merge($this->mail_entity->mail_object->attachments, [['file' => base64_encode($pdf), 'name' => $this->mail_entity->invitation->{$this->mail_entity->mail_object->entity_string}->numberFormatter().'.pdf']]);
|
||||
|
||||
if ($this->client->getSetting('document_email_attachment') !== false && $this->mail_entity->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {
|
||||
|
||||
$this->attachDocuments($this->mail_entity->invitation->{$entity}->documents);
|
||||
$this->attachDocuments($this->mail_entity->invitation->{$this->mail_entity->mail_object->entity_string}->documents);
|
||||
|
||||
}
|
||||
|
||||
@ -391,12 +436,12 @@ class MailBuild
|
||||
|
||||
|
||||
|
||||
if($this->settings->ubl_email_attachment && $entity == 'invoice')
|
||||
if($this->settings->ubl_email_attachment && $this->mail_entity->mail_object->entity_string == 'invoice')
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
if($entity == 'invoice')
|
||||
if($this->mail_entity->mail_object->entity_string == 'invoice')
|
||||
{
|
||||
|
||||
$line_items = $this->mail_entity->invitation->invoice->line_items;
|
||||
|
@ -78,6 +78,8 @@ class MailObject
|
||||
|
||||
public ?string $entity_class = null;
|
||||
|
||||
public ?string $entity_string = null;
|
||||
|
||||
public array $variables = [];
|
||||
|
||||
public ?string $template = null;
|
||||
|
@ -13,6 +13,8 @@ namespace App\Services\Quote;
|
||||
|
||||
use App\Jobs\Entity\EmailEntity;
|
||||
use App\Models\ClientContact;
|
||||
use App\Services\Email\MailEntity;
|
||||
use App\Services\Email\MailObject;
|
||||
|
||||
class SendEmail
|
||||
{
|
||||
@ -37,15 +39,22 @@ class SendEmail
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
nlog($this->reminder_template);
|
||||
nlog("is there a template");
|
||||
|
||||
if (! $this->reminder_template) {
|
||||
$this->reminder_template = $this->quote->calculateTemplate('quote');
|
||||
}
|
||||
|
||||
|
||||
$mo = new MailObject();
|
||||
|
||||
$this->quote->service()->markSent()->save();
|
||||
|
||||
$this->quote->invitations->each(function ($invitation) {
|
||||
$this->quote->invitations->each(function ($invitation) use ($mo){
|
||||
if (! $invitation->contact->trashed() && $invitation->contact->email) {
|
||||
EmailEntity::dispatch($invitation, $invitation->company, $this->reminder_template);
|
||||
// EmailEntity::dispatch($invitation, $invitation->company, $this->reminder_template);
|
||||
|
||||
MailEntity::dispatch($invitation, $invitation->company->db, $mo);
|
||||
}
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user