Skip to content
Rapcsány Krisztián
hu
← All articles
2 min read

How not to send the wrong email to the wrong person

Email Laravel Marketing

Sending a campaign is easy. The hard part is making sure the wrong person does not get the wrong email. That is why most of the code in my campaign system is defensive.

One decision point

Every recommendation passes through a single place. The decision does not stop at the first reason for skipping someone, it collects all of them. In a preview that makes it clear why someone drops off the list.

RecommendationPolicy.php
public function evaluate(User $user, string $channel, ?CarbonInterface $now = null): PolicyDecision
{
    $now = ($now ?? Carbon::now())->copy();
    $reasons = []; // collect ALL reasons, not just the first, so a preview can explain every skip

    if (! (bool) $this->cfg('enabled', false)) {
        $reasons[] = 'kill_switch_off';
    }

    if ($user->is_banned || $user->deleted_at) {
        $reasons[] = 'account_blocked';
    }

    if ($channel === self::CHANNEL_EMAIL) {
        if (! (bool) $user->is_subscribed) {
            $reasons[] = 'no_marketing_consent';
        }
        if ($this->emailSuppressed(trim((string) $user->email))) {
            $reasons[] = 'email_suppressed';
        }
    } elseif ($channel === self::CHANNEL_PUSH) {
        if ($this->isQuietHour($now)) {
            $reasons[] = 'quiet_hours';
        }
    }

    // ... weekly cap from the shared marketing ledger, then the holdout control group
}

An excerpt of the live code, anonymised.

The safeguards

  • A switch for every campaign, off by default.
  • A dry run with no sending.
  • A recipient limit per run.
  • A cool-down for the same recipient.
  • A suppression list and consent check.
  • A shared weekly cap, and quiet hours for push notifications.

A control group

I measure a campaign's effect with a control group: the control group gets a calculated recommendation but not the email. The difference shows what the sending actually brought.

Working on a similar problem?

If you are stuck, or want a second look at your solution, write to me. I am happy to look.

Write to me →
Write to me →