refactor(drip): extract base mailable and job for the drip email family (#641)
## Why
The drip email family was near copy-paste. Each of the eight mailables
repeated the same constructor, `$tries`/`$backoff`, `drip_from`
envelope, `markdown` + `userName` content, and `RateLimited` middleware
— differing only in subject and template (plus a reply-to on one, an
extra view var on another). Each of the eight jobs repeated the
`canReceiveEmails` / `hasReceivedEmail` guards, the `Mail::send` call,
and the `UserMailLog::create` block — differing only in the email type,
the mailable, and a handful of per-email eligibility checks.
## What
Two abstract bases, so each subclass declares only what varies:
- **`DripMail`** — owns the shared mailable shape. Subclasses declare
`dripSubject()` and `template()`; optional `contentData()` (PromoCode's
`FOUNDER` var) and `repliesToSender()` (PaywallFollowUp) hooks. The
hooks are named `dripSubject()`/`template()` deliberately to avoid
colliding with `Illuminate\Mail\Mailable`'s existing public
`subject()`/`markdown()` methods.
- **`SendDripEmailJob`** — owns the shared `handle()` flow (shared
guards → send → `UserMailLog`). Subclasses declare `emailType()` and
`buildMail()`; an optional `shouldSend()` hook carries the per-email
eligibility rules (pro-plan, onboarding, transactions, bank connections,
AI consent).
Each mailable drops from ~68 to ~13 lines and each job from ~48 to ~18.
Net **−459 lines**, with the send/log logic and envelope construction
now in one place.
## Behavior
No functional change. Subjects, `from`/`reply-to`, queue name, rate
limiting, templates, view data, and every per-job eligibility guard are
preserved. Notably, passing `replyTo: []` for the seven non-reply mails
is identical to the previous omission — `Envelope`'s `replyTo` defaults
to `[]`. Verified: `pest --exclude-testsuite=Browser` **1872 passing, 0
failing**; `pint --test`, `phpstan` (level 5), `format`, `lint` clean.
## Review-driven follow-up commits
Two review agents (architecture/duplication/coverage, and
behavior/regressions) ran against the first commit. The behavior agent
verified all eight jobs' guard logic invert correctly (order +
negations) and that `replyTo: []` ≡ omitted — no regressions. The
coverage agent found the two hook-driven behaviors were untested;
applied, each as its own commit:
- Extended the drip-sender parity test to all eight mailables and
asserted `PaywallFollowUpEmail`'s reply-to (and that a default drip mail
sets none).
- Asserted `PromoCodeEmail` injects the `FOUNDER` promo code into its
view data.
## Deliberately out of scope
- Non-drip mailables (`Waitlist*`, `Banking*`, `UpdateEmail`, …) share
the same `RateLimited('emails')->releaseAfter(1)` middleware and could
later share a queued-mail base too — separate concern.
- **Pre-existing** (not introduced here, noted by review): the job sends
the mail before writing `UserMailLog`, so a failure between the two can
re-enqueue a duplicate; there is no lock/unique constraint on `(user_id,
email_type)`. Worth a separate hardening pass.
This commit is contained in:
parent
845f51abb5
commit
eccfaa5a7a
|
|
@ -4,45 +4,22 @@ namespace App\Jobs\Drip;
|
|||
|
||||
use App\Enums\DripEmailType;
|
||||
use App\Mail\Drip\AiConsentFollowUpEmail;
|
||||
use App\Models\User;
|
||||
use App\Models\UserMailLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Mail\Mailable;
|
||||
|
||||
class SendAiConsentFollowUpEmailJob implements ShouldQueue
|
||||
class SendAiConsentFollowUpEmailJob extends SendDripEmailJob
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function emailType(): DripEmailType
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return DripEmailType::AiConsentFollowUp;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
protected function buildMail(): Mailable
|
||||
{
|
||||
if (! $this->user->canReceiveEmails()) {
|
||||
return;
|
||||
}
|
||||
return new AiConsentFollowUpEmail($this->user);
|
||||
}
|
||||
|
||||
if ($this->user->hasReceivedEmail(DripEmailType::AiConsentFollowUp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->user->aiConsents()->active()->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mail::to($this->user)->send(new AiConsentFollowUpEmail($this->user));
|
||||
|
||||
UserMailLog::create([
|
||||
'user_id' => $this->user->id,
|
||||
'email_type' => DripEmailType::AiConsentFollowUp,
|
||||
'email_identifier' => DripEmailType::AiConsentFollowUp->value,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
protected function shouldSend(): bool
|
||||
{
|
||||
return $this->user->aiConsents()->active()->exists();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\Drip;
|
||||
|
||||
use App\Enums\DripEmailType;
|
||||
use App\Models\User;
|
||||
use App\Models\UserMailLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
abstract class SendDripEmailJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
}
|
||||
|
||||
abstract protected function emailType(): DripEmailType;
|
||||
|
||||
abstract protected function buildMail(): Mailable;
|
||||
|
||||
/**
|
||||
* Per-email eligibility checks beyond the shared "can receive" and
|
||||
* "not already sent" guards.
|
||||
*/
|
||||
protected function shouldSend(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (! $this->user->canReceiveEmails()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->user->hasReceivedEmail($this->emailType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->shouldSend()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mail::to($this->user)->send($this->buildMail());
|
||||
|
||||
UserMailLog::create([
|
||||
'user_id' => $this->user->id,
|
||||
'email_type' => $this->emailType(),
|
||||
'email_identifier' => $this->emailType()->value,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,45 +4,22 @@ namespace App\Jobs\Drip;
|
|||
|
||||
use App\Enums\DripEmailType;
|
||||
use App\Mail\Drip\FeedbackEmail;
|
||||
use App\Models\User;
|
||||
use App\Models\UserMailLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Mail\Mailable;
|
||||
|
||||
class SendFeedbackEmailJob implements ShouldQueue
|
||||
class SendFeedbackEmailJob extends SendDripEmailJob
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function emailType(): DripEmailType
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return DripEmailType::Feedback;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
protected function buildMail(): Mailable
|
||||
{
|
||||
if (! $this->user->canReceiveEmails()) {
|
||||
return;
|
||||
}
|
||||
return new FeedbackEmail($this->user);
|
||||
}
|
||||
|
||||
if ($this->user->hasReceivedEmail(DripEmailType::Feedback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->user->hasProPlan()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mail::to($this->user)->send(new FeedbackEmail($this->user));
|
||||
|
||||
UserMailLog::create([
|
||||
'user_id' => $this->user->id,
|
||||
'email_type' => DripEmailType::Feedback,
|
||||
'email_identifier' => DripEmailType::Feedback->value,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
protected function shouldSend(): bool
|
||||
{
|
||||
return ! $this->user->hasProPlan();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,53 +4,24 @@ namespace App\Jobs\Drip;
|
|||
|
||||
use App\Enums\DripEmailType;
|
||||
use App\Mail\Drip\ImportHelpEmail;
|
||||
use App\Models\User;
|
||||
use App\Models\UserMailLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Mail\Mailable;
|
||||
|
||||
class SendImportHelpEmailJob implements ShouldQueue
|
||||
class SendImportHelpEmailJob extends SendDripEmailJob
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function emailType(): DripEmailType
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return DripEmailType::ImportHelp;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
protected function buildMail(): Mailable
|
||||
{
|
||||
if (! $this->user->canReceiveEmails()) {
|
||||
return;
|
||||
}
|
||||
return new ImportHelpEmail($this->user);
|
||||
}
|
||||
|
||||
if ($this->user->hasReceivedEmail(DripEmailType::ImportHelp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->user->isOnboarded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->user->transactions()->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->user->hasProPlan()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mail::to($this->user)->send(new ImportHelpEmail($this->user));
|
||||
|
||||
UserMailLog::create([
|
||||
'user_id' => $this->user->id,
|
||||
'email_type' => DripEmailType::ImportHelp,
|
||||
'email_identifier' => DripEmailType::ImportHelp->value,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
protected function shouldSend(): bool
|
||||
{
|
||||
return $this->user->isOnboarded()
|
||||
&& ! $this->user->transactions()->exists()
|
||||
&& ! $this->user->hasProPlan();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,45 +4,22 @@ namespace App\Jobs\Drip;
|
|||
|
||||
use App\Enums\DripEmailType;
|
||||
use App\Mail\Drip\OnboardingReminderEmail;
|
||||
use App\Models\User;
|
||||
use App\Models\UserMailLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Mail\Mailable;
|
||||
|
||||
class SendOnboardingReminderEmailJob implements ShouldQueue
|
||||
class SendOnboardingReminderEmailJob extends SendDripEmailJob
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function emailType(): DripEmailType
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return DripEmailType::OnboardingReminder;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
protected function buildMail(): Mailable
|
||||
{
|
||||
if (! $this->user->canReceiveEmails()) {
|
||||
return;
|
||||
}
|
||||
return new OnboardingReminderEmail($this->user);
|
||||
}
|
||||
|
||||
if ($this->user->hasReceivedEmail(DripEmailType::OnboardingReminder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->user->isOnboarded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mail::to($this->user)->send(new OnboardingReminderEmail($this->user));
|
||||
|
||||
UserMailLog::create([
|
||||
'user_id' => $this->user->id,
|
||||
'email_type' => DripEmailType::OnboardingReminder,
|
||||
'email_identifier' => DripEmailType::OnboardingReminder->value,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
protected function shouldSend(): bool
|
||||
{
|
||||
return ! $this->user->isOnboarded();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,49 +4,23 @@ namespace App\Jobs\Drip;
|
|||
|
||||
use App\Enums\DripEmailType;
|
||||
use App\Mail\Drip\PaywallFollowUpEmail;
|
||||
use App\Models\User;
|
||||
use App\Models\UserMailLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Mail\Mailable;
|
||||
|
||||
class SendPaywallFollowUpEmailJob implements ShouldQueue
|
||||
class SendPaywallFollowUpEmailJob extends SendDripEmailJob
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function emailType(): DripEmailType
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return DripEmailType::PaywallFollowUp;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
protected function buildMail(): Mailable
|
||||
{
|
||||
if (! $this->user->canReceiveEmails()) {
|
||||
return;
|
||||
}
|
||||
return new PaywallFollowUpEmail($this->user);
|
||||
}
|
||||
|
||||
if ($this->user->hasReceivedEmail(DripEmailType::PaywallFollowUp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->user->hasProPlan()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->user->bankingConnections()->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mail::to($this->user)->send(new PaywallFollowUpEmail($this->user));
|
||||
|
||||
UserMailLog::create([
|
||||
'user_id' => $this->user->id,
|
||||
'email_type' => DripEmailType::PaywallFollowUp,
|
||||
'email_identifier' => DripEmailType::PaywallFollowUp->value,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
protected function shouldSend(): bool
|
||||
{
|
||||
return ! $this->user->hasProPlan()
|
||||
&& $this->user->bankingConnections()->exists();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,53 +4,24 @@ namespace App\Jobs\Drip;
|
|||
|
||||
use App\Enums\DripEmailType;
|
||||
use App\Mail\Drip\PromoCodeEmail;
|
||||
use App\Models\User;
|
||||
use App\Models\UserMailLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Mail\Mailable;
|
||||
|
||||
class SendPromoCodeEmailJob implements ShouldQueue
|
||||
class SendPromoCodeEmailJob extends SendDripEmailJob
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function emailType(): DripEmailType
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return DripEmailType::PromoCode;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
protected function buildMail(): Mailable
|
||||
{
|
||||
if (! $this->user->canReceiveEmails()) {
|
||||
return;
|
||||
}
|
||||
return new PromoCodeEmail($this->user);
|
||||
}
|
||||
|
||||
if ($this->user->hasReceivedEmail(DripEmailType::PromoCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->user->isOnboarded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->user->transactions()->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->user->hasProPlan()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mail::to($this->user)->send(new PromoCodeEmail($this->user));
|
||||
|
||||
UserMailLog::create([
|
||||
'user_id' => $this->user->id,
|
||||
'email_type' => DripEmailType::PromoCode,
|
||||
'email_identifier' => DripEmailType::PromoCode->value,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
protected function shouldSend(): bool
|
||||
{
|
||||
return $this->user->isOnboarded()
|
||||
&& $this->user->transactions()->exists()
|
||||
&& ! $this->user->hasProPlan();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,41 +4,17 @@ namespace App\Jobs\Drip;
|
|||
|
||||
use App\Enums\DripEmailType;
|
||||
use App\Mail\Drip\SubscriptionCancelledEmail;
|
||||
use App\Models\User;
|
||||
use App\Models\UserMailLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Mail\Mailable;
|
||||
|
||||
class SendSubscriptionCancelledEmailJob implements ShouldQueue
|
||||
class SendSubscriptionCancelledEmailJob extends SendDripEmailJob
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function emailType(): DripEmailType
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return DripEmailType::SubscriptionCancelled;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
protected function buildMail(): Mailable
|
||||
{
|
||||
if (! $this->user->canReceiveEmails()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->user->hasReceivedEmail(DripEmailType::SubscriptionCancelled)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mail::to($this->user)->send(new SubscriptionCancelledEmail($this->user));
|
||||
|
||||
UserMailLog::create([
|
||||
'user_id' => $this->user->id,
|
||||
'email_type' => DripEmailType::SubscriptionCancelled,
|
||||
'email_identifier' => DripEmailType::SubscriptionCancelled->value,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
return new SubscriptionCancelledEmail($this->user);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,41 +4,17 @@ namespace App\Jobs\Drip;
|
|||
|
||||
use App\Enums\DripEmailType;
|
||||
use App\Mail\Drip\WelcomeEmail;
|
||||
use App\Models\User;
|
||||
use App\Models\UserMailLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Mail\Mailable;
|
||||
|
||||
class SendWelcomeEmailJob implements ShouldQueue
|
||||
class SendWelcomeEmailJob extends SendDripEmailJob
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function emailType(): DripEmailType
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return DripEmailType::Welcome;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
protected function buildMail(): Mailable
|
||||
{
|
||||
if (! $this->user->canReceiveEmails()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->user->hasReceivedEmail(DripEmailType::Welcome)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mail::to($this->user)->send(new WelcomeEmail($this->user));
|
||||
|
||||
UserMailLog::create([
|
||||
'user_id' => $this->user->id,
|
||||
'email_type' => DripEmailType::Welcome,
|
||||
'email_identifier' => DripEmailType::Welcome->value,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
return new WelcomeEmail($this->user);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,67 +2,15 @@
|
|||
|
||||
namespace App\Mail\Drip;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\Middleware\RateLimited;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class AiConsentFollowUpEmail extends Mailable implements ShouldQueue
|
||||
class AiConsentFollowUpEmail extends DripMail
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
/**
|
||||
* The number of seconds to wait before retrying the job.
|
||||
*
|
||||
* @var array<int, int>
|
||||
*/
|
||||
public $backoff = [2, 5, 10, 30];
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function dripSubject(): string
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return __('Putting AI to work on your finances');
|
||||
}
|
||||
|
||||
public function envelope(): Envelope
|
||||
protected function template(): string
|
||||
{
|
||||
return new Envelope(
|
||||
from: new Address(
|
||||
config('mail.drip_from.address', 'hi@whisper.money'),
|
||||
config('mail.drip_from.name', 'Álvaro and Víctor'),
|
||||
),
|
||||
subject: __('Putting AI to work on your finances'),
|
||||
);
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'mail.drip.ai-consent-follow-up',
|
||||
with: [
|
||||
'userName' => $this->user->name,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new RateLimited('emails'))->releaseAfter(1)];
|
||||
return 'mail.drip.ai-consent-follow-up';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail\Drip;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\Middleware\RateLimited;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
abstract class DripMail extends Mailable implements ShouldQueue
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
/**
|
||||
* The number of seconds to wait before retrying the job.
|
||||
*
|
||||
* @var array<int, int>
|
||||
*/
|
||||
public $backoff = [2, 5, 10, 30];
|
||||
|
||||
public function __construct(public User $user)
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
}
|
||||
|
||||
abstract protected function dripSubject(): string;
|
||||
|
||||
abstract protected function template(): string;
|
||||
|
||||
/**
|
||||
* Extra view data merged alongside the recipient's name.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function contentData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether replies should route back to the drip sender address.
|
||||
*/
|
||||
protected function repliesToSender(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
$from = new Address(
|
||||
config('mail.drip_from.address', 'hi@whisper.money'),
|
||||
config('mail.drip_from.name', 'Álvaro and Víctor'),
|
||||
);
|
||||
|
||||
return new Envelope(
|
||||
from: $from,
|
||||
replyTo: $this->repliesToSender() ? [$from] : [],
|
||||
subject: $this->dripSubject(),
|
||||
);
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: $this->template(),
|
||||
with: [
|
||||
'userName' => $this->user->name,
|
||||
...$this->contentData(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new RateLimited('emails'))->releaseAfter(1)];
|
||||
}
|
||||
}
|
||||
|
|
@ -2,67 +2,15 @@
|
|||
|
||||
namespace App\Mail\Drip;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\Middleware\RateLimited;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class FeedbackEmail extends Mailable implements ShouldQueue
|
||||
class FeedbackEmail extends DripMail
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
/**
|
||||
* The number of seconds to wait before retrying the job.
|
||||
*
|
||||
* @var array<int, int>
|
||||
*/
|
||||
public $backoff = [2, 5, 10, 30];
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function dripSubject(): string
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return __("How's Your Experience So Far?");
|
||||
}
|
||||
|
||||
public function envelope(): Envelope
|
||||
protected function template(): string
|
||||
{
|
||||
return new Envelope(
|
||||
from: new Address(
|
||||
config('mail.drip_from.address', 'hi@whisper.money'),
|
||||
config('mail.drip_from.name', 'Álvaro and Víctor'),
|
||||
),
|
||||
subject: __("How's Your Experience So Far?"),
|
||||
);
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'mail.drip.feedback',
|
||||
with: [
|
||||
'userName' => $this->user->name,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new RateLimited('emails'))->releaseAfter(1)];
|
||||
return 'mail.drip.feedback';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,67 +2,15 @@
|
|||
|
||||
namespace App\Mail\Drip;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\Middleware\RateLimited;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ImportHelpEmail extends Mailable implements ShouldQueue
|
||||
class ImportHelpEmail extends DripMail
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
/**
|
||||
* The number of seconds to wait before retrying the job.
|
||||
*
|
||||
* @var array<int, int>
|
||||
*/
|
||||
public $backoff = [2, 5, 10, 30];
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function dripSubject(): string
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return __("Let's Import Your Transactions");
|
||||
}
|
||||
|
||||
public function envelope(): Envelope
|
||||
protected function template(): string
|
||||
{
|
||||
return new Envelope(
|
||||
from: new Address(
|
||||
config('mail.drip_from.address', 'hi@whisper.money'),
|
||||
config('mail.drip_from.name', 'Álvaro and Víctor'),
|
||||
),
|
||||
subject: __("Let's Import Your Transactions"),
|
||||
);
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'mail.drip.import-help',
|
||||
with: [
|
||||
'userName' => $this->user->name,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new RateLimited('emails'))->releaseAfter(1)];
|
||||
return 'mail.drip.import-help';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,67 +2,15 @@
|
|||
|
||||
namespace App\Mail\Drip;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\Middleware\RateLimited;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class OnboardingReminderEmail extends Mailable implements ShouldQueue
|
||||
class OnboardingReminderEmail extends DripMail
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
/**
|
||||
* The number of seconds to wait before retrying the job.
|
||||
*
|
||||
* @var array<int, int>
|
||||
*/
|
||||
public $backoff = [2, 5, 10, 30];
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function dripSubject(): string
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return __('Need Help Getting Started?');
|
||||
}
|
||||
|
||||
public function envelope(): Envelope
|
||||
protected function template(): string
|
||||
{
|
||||
return new Envelope(
|
||||
from: new Address(
|
||||
config('mail.drip_from.address', 'hi@whisper.money'),
|
||||
config('mail.drip_from.name', 'Álvaro and Víctor'),
|
||||
),
|
||||
subject: __('Need Help Getting Started?'),
|
||||
);
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'mail.drip.onboarding-reminder',
|
||||
with: [
|
||||
'userName' => $this->user->name,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new RateLimited('emails'))->releaseAfter(1)];
|
||||
return 'mail.drip.onboarding-reminder';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,73 +2,20 @@
|
|||
|
||||
namespace App\Mail\Drip;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\Middleware\RateLimited;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PaywallFollowUpEmail extends Mailable implements ShouldQueue
|
||||
class PaywallFollowUpEmail extends DripMail
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
/**
|
||||
* The number of seconds to wait before retrying the job.
|
||||
*
|
||||
* @var array<int, int>
|
||||
*/
|
||||
public $backoff = [2, 5, 10, 30];
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function dripSubject(): string
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return __('What stopped you from getting started?');
|
||||
}
|
||||
|
||||
public function envelope(): Envelope
|
||||
protected function template(): string
|
||||
{
|
||||
return new Envelope(
|
||||
from: new Address(
|
||||
config('mail.drip_from.address', 'hi@whisper.money'),
|
||||
config('mail.drip_from.name', 'Álvaro and Víctor'),
|
||||
),
|
||||
replyTo: [
|
||||
new Address(
|
||||
config('mail.drip_from.address', 'hi@whisper.money'),
|
||||
config('mail.drip_from.name', 'Álvaro and Víctor'),
|
||||
),
|
||||
],
|
||||
subject: __('What stopped you from getting started?'),
|
||||
);
|
||||
return 'mail.drip.paywall-follow-up';
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
protected function repliesToSender(): bool
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'mail.drip.paywall-follow-up',
|
||||
with: [
|
||||
'userName' => $this->user->name,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new RateLimited('emails'))->releaseAfter(1)];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,68 +2,20 @@
|
|||
|
||||
namespace App\Mail\Drip;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\Middleware\RateLimited;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PromoCodeEmail extends Mailable implements ShouldQueue
|
||||
class PromoCodeEmail extends DripMail
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
/**
|
||||
* The number of seconds to wait before retrying the job.
|
||||
*
|
||||
* @var array<int, int>
|
||||
*/
|
||||
public $backoff = [2, 5, 10, 30];
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function dripSubject(): string
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return __('Your Founder Discount - 80% Off First Period');
|
||||
}
|
||||
|
||||
public function envelope(): Envelope
|
||||
protected function template(): string
|
||||
{
|
||||
return new Envelope(
|
||||
from: new Address(
|
||||
config('mail.drip_from.address', 'hi@whisper.money'),
|
||||
config('mail.drip_from.name', 'Álvaro and Víctor'),
|
||||
),
|
||||
subject: __('Your Founder Discount - 80% Off First Period')
|
||||
);
|
||||
return 'mail.drip.promo-code';
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
protected function contentData(): array
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'mail.drip.promo-code',
|
||||
with: [
|
||||
'userName' => $this->user->name,
|
||||
'promoCode' => 'FOUNDER',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new RateLimited('emails'))->releaseAfter(1)];
|
||||
return ['promoCode' => 'FOUNDER'];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,67 +2,15 @@
|
|||
|
||||
namespace App\Mail\Drip;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\Middleware\RateLimited;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SubscriptionCancelledEmail extends Mailable implements ShouldQueue
|
||||
class SubscriptionCancelledEmail extends DripMail
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
/**
|
||||
* The number of seconds to wait before retrying the job.
|
||||
*
|
||||
* @var array<int, int>
|
||||
*/
|
||||
public $backoff = [2, 5, 10, 30];
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function dripSubject(): string
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return __("We're sorry to see you go");
|
||||
}
|
||||
|
||||
public function envelope(): Envelope
|
||||
protected function template(): string
|
||||
{
|
||||
return new Envelope(
|
||||
from: new Address(
|
||||
config('mail.drip_from.address', 'hi@whisper.money'),
|
||||
config('mail.drip_from.name', 'Álvaro and Víctor'),
|
||||
),
|
||||
subject: __("We're sorry to see you go"),
|
||||
);
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'mail.drip.subscription-cancelled',
|
||||
with: [
|
||||
'userName' => $this->user->name,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new RateLimited('emails'))->releaseAfter(1)];
|
||||
return 'mail.drip.subscription-cancelled';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,67 +2,15 @@
|
|||
|
||||
namespace App\Mail\Drip;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\Middleware\RateLimited;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class WelcomeEmail extends Mailable implements ShouldQueue
|
||||
class WelcomeEmail extends DripMail
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 5;
|
||||
|
||||
/**
|
||||
* The number of seconds to wait before retrying the job.
|
||||
*
|
||||
* @var array<int, int>
|
||||
*/
|
||||
public $backoff = [2, 5, 10, 30];
|
||||
|
||||
public function __construct(public User $user)
|
||||
protected function dripSubject(): string
|
||||
{
|
||||
$this->onQueue('emails');
|
||||
return __('Welcome to Whisper Money - Your Privacy-First Finance App');
|
||||
}
|
||||
|
||||
public function envelope(): Envelope
|
||||
protected function template(): string
|
||||
{
|
||||
return new Envelope(
|
||||
from: new Address(
|
||||
config('mail.drip_from.address', 'hi@whisper.money'),
|
||||
config('mail.drip_from.name', 'Álvaro and Víctor'),
|
||||
),
|
||||
subject: __('Welcome to Whisper Money - Your Privacy-First Finance App'),
|
||||
);
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'mail.drip.welcome',
|
||||
with: [
|
||||
'userName' => $this->user->name,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new RateLimited('emails'))->releaseAfter(1)];
|
||||
return 'mail.drip.welcome';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@
|
|||
use App\Mail\BankingConnectionAuthFailedEmail;
|
||||
use App\Mail\BankTransactionsSyncedEmail;
|
||||
use App\Mail\BrokenBankLogosReportEmail;
|
||||
use App\Mail\Drip\AiConsentFollowUpEmail;
|
||||
use App\Mail\Drip\FeedbackEmail;
|
||||
use App\Mail\Drip\ImportHelpEmail;
|
||||
use App\Mail\Drip\OnboardingReminderEmail;
|
||||
use App\Mail\Drip\PaywallFollowUpEmail;
|
||||
use App\Mail\Drip\PromoCodeEmail;
|
||||
use App\Mail\Drip\SubscriptionCancelledEmail;
|
||||
use App\Mail\Drip\WelcomeEmail;
|
||||
|
|
@ -84,24 +86,44 @@ test('drip mailables use the drip sender', function (string $mailableClass) {
|
|||
$user = User::factory()->create();
|
||||
|
||||
$mailable = match ($mailableClass) {
|
||||
AiConsentFollowUpEmail::class => new AiConsentFollowUpEmail($user),
|
||||
WelcomeEmail::class => new WelcomeEmail($user),
|
||||
FeedbackEmail::class => new FeedbackEmail($user),
|
||||
ImportHelpEmail::class => new ImportHelpEmail($user),
|
||||
OnboardingReminderEmail::class => new OnboardingReminderEmail($user),
|
||||
PaywallFollowUpEmail::class => new PaywallFollowUpEmail($user),
|
||||
PromoCodeEmail::class => new PromoCodeEmail($user),
|
||||
SubscriptionCancelledEmail::class => new SubscriptionCancelledEmail($user),
|
||||
};
|
||||
|
||||
expect($mailable->envelope()->from)->toEqual(new Address('hi@whisper.money', 'Álvaro and Víctor'));
|
||||
})->with([
|
||||
AiConsentFollowUpEmail::class,
|
||||
WelcomeEmail::class,
|
||||
FeedbackEmail::class,
|
||||
ImportHelpEmail::class,
|
||||
OnboardingReminderEmail::class,
|
||||
PaywallFollowUpEmail::class,
|
||||
PromoCodeEmail::class,
|
||||
SubscriptionCancelledEmail::class,
|
||||
]);
|
||||
|
||||
test('only the paywall follow-up sets a reply-to, back to the drip sender', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
expect((new PaywallFollowUpEmail($user))->envelope()->replyTo)
|
||||
->toEqual([new Address('hi@whisper.money', 'Álvaro and Víctor')]);
|
||||
|
||||
expect((new WelcomeEmail($user))->envelope()->replyTo)->toBe([]);
|
||||
});
|
||||
|
||||
test('promo code email adds the founder promo code to its view data', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
expect((new PromoCodeEmail($user))->content()->with)
|
||||
->toEqual(['userName' => $user->name, 'promoCode' => 'FOUNDER']);
|
||||
});
|
||||
|
||||
test('default sender is used for active non-drip mailables', function (string $mailableClass) {
|
||||
$user = User::factory()->create();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue