• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

Freegle / Iznik / 27880

19 Jul 2026 04:24PM UTC coverage: 72.466%. First build
27880

Pull #1122

circleci

edwh
feat(matched): use "Freegle matches for you" subject for multi-match emails

Single-match emails keep the item-naming subject ("Someone is offering:
<item>"); multi-match emails now use a fixed "Freegle matches for you".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013YqwEGSmzjUbHM3QinYAxX
Pull Request #1122: feat: matched-posts email (vector-based resurrection of "Any of these take your fancy?")

13216 of 17412 branches covered (75.9%)

Branch coverage included in aggregate %.

434 of 531 new or added lines in 10 files covered. (81.73%)

138681 of 192200 relevant lines covered (72.15%)

37.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.92
/iznik-batch/app/Console/Commands/Message/NotifyMatchedPostsCommand.php
1
<?php
2

3
namespace App\Console\Commands\Message;
4

5
use App\Mail\Matched\MatchedPosts;
6
use App\Services\EmailSpoolerService;
7
use App\Services\MatchedPostsService;
8
use App\Traits\GracefulShutdown;
9
use App\Traits\LogsBatchJob;
10
use Carbon\Carbon;
11
use Illuminate\Console\Command;
12
use Illuminate\Support\Facades\DB;
13
use Illuminate\Support\Facades\Log;
14

15
/**
16
 * Emails members the opposite-type posts near them that match their own open
17
 * Offer/Wanted — the resurrected "Any of these take your fancy?" mail, rebuilt on
18
 * vector matching (V1: cron/relevant.php). Runs every 10 minutes; the service
19
 * already applies every dedup/eligibility guard, so this command just renders,
20
 * spools, and records what was sent so nothing is mailed twice.
21
 */
22
class NotifyMatchedPostsCommand extends Command
23
{
24
    use GracefulShutdown, LogsBatchJob;
25

26
    protected $signature = 'matches:notify
27
                            {--dry-run : Build and report notifications without sending or recording anything}';
28

29
    protected $description = 'Email members opposite-type posts that match their open offers/wanteds (vector matched-posts mail)';
30

31
    public function handle(MatchedPostsService $service, EmailSpoolerService $spooler): int
3✔
32
    {
33
        $this->registerShutdownHandlers();
3✔
34

35
        $dryRun = (bool) $this->option('dry-run');
3✔
36
        if ($dryRun) {
3✔
37
            $this->warn('DRY RUN MODE — no mail sent, no ledger written.');
1✔
38
        }
39

40
        return $this->runWithLogging(function () use ($service, $spooler, $dryRun) {
3✔
41
            $now = Carbon::now();
3✔
42
            $notifications = $service->buildNotifications($now);
3✔
43

44
            $emails = 0;
3✔
45
            $matches = 0;
3✔
46

47
            foreach ($notifications as $n) {
3✔
48
                if ($this->shouldStop()) {
3✔
NEW
49
                    break;
×
50
                }
51

52
                $user = $n['user'];
3✔
53
                $items = $n['items'];
3✔
54
                $email = $user->email_preferred ?? $user->email ?? null;
3✔
55
                if (! $email) {
3✔
NEW
56
                    continue;
×
57
                }
58

59
                if ($dryRun) {
3✔
60
                    $emails++;
1✔
61
                    $matches += count($items);
1✔
62

63
                    continue;
1✔
64
                }
65

66
                $mail = new MatchedPosts($user, $email, $items);
2✔
67
                $spooler->spool($mail);
2✔
68

69
                // Record every matched post as mailed to this user (never re-send),
70
                // and bump the per-user cooldown watermark.
71
                $rows = array_map(fn ($i) => [
2✔
72
                    'msgid' => (int) $i['message']->id,
2✔
73
                    'userid' => (int) $user->id,
2✔
74
                    'mailed_at' => $now->toDateTimeString(),
2✔
75
                ], $items);
2✔
76
                DB::table('messages_matched_notified')->insertOrIgnore($rows);
2✔
77
                DB::table('users')->where('id', $user->id)->update(['lastrelevantcheck' => $now->toDateTimeString()]);
2✔
78

79
                $emails++;
2✔
80
                $matches += count($items);
2✔
81
            }
82

83
            $prefix = $dryRun ? 'Would ' : '';
3✔
84
            $this->newLine();
3✔
85
            $this->table(
3✔
86
                ['Operation', 'Count'],
3✔
87
                [
3✔
88
                    [$prefix . 'Members emailed', number_format($emails)],
3✔
89
                    [$prefix . 'Matched posts included', number_format($matches)],
3✔
90
                ]
3✔
91
            );
3✔
92

93
            Log::info('Matched-posts notify completed', [
3✔
94
                'emails' => $emails,
3✔
95
                'matches' => $matches,
3✔
96
                'dry_run' => $dryRun,
3✔
97
            ]);
3✔
98

99
            return Command::SUCCESS;
3✔
100
        });
3✔
101
    }
102
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc