193 lines
6.1 KiB
Markdown
193 lines
6.1 KiB
Markdown
# bounce:simulate — Bounce Testing Command
|
|
|
|
Single Artisan command for testing the full bounce/suppression pipeline in any environment. Works in two modes:
|
|
|
|
- **Inject mode** (local dev): injects a fake bounce directly into the DB — no mailbox needed
|
|
- **IMAP mode** (production): connects to the real bounce mailbox configured in `.env`, parses and records actual DSN messages
|
|
|
|
Both modes call `MailingBounce::recordBounce()` — the same code path, so threshold logic, suppression, and `email_verified_at` reset behave identically.
|
|
|
|
## Usage
|
|
|
|
```bash
|
|
php artisan bounce:simulate [options]
|
|
```
|
|
|
|
## Options
|
|
|
|
### Inject mode
|
|
| Option | Description |
|
|
|---|---|
|
|
| `--email=` | Email address to simulate bounce for **(required in inject mode)** |
|
|
| `--type=` | Bounce type: `hard`, `soft`, `complaint` (default: `hard`) |
|
|
| `--reason=` | Bounce reason string. Defaults to a definitive pattern per type |
|
|
| `--mailing-id=` | Optional: link bounce to a specific mailing ID (validated) |
|
|
| `--status` | Show current bounce state only — record nothing |
|
|
| `--clear` | Delete all existing bounce records for this address first |
|
|
| `--suppress` | Directly suppress the address, bypassing threshold logic |
|
|
|
|
### IMAP mode
|
|
| Option | Description |
|
|
|---|---|
|
|
| `--imap` | Read real bounce emails from the IMAP mailbox configured in `.env` |
|
|
| `--dry-run` | Parse and display bounces without recording them |
|
|
| `--delete` | Delete processed emails from the mailbox after recording |
|
|
|
|
## Default reasons (inject mode)
|
|
|
|
| Type | Default reason |
|
|
|---|---|
|
|
| `hard` | `550 5.1.1 user unknown` — matches a definitive pattern, counts toward thresholds |
|
|
| `soft` | `452 mailbox full` — does NOT match a definitive pattern, no threshold action |
|
|
| `complaint` | `Spam complaint` |
|
|
|
|
## IMAP configuration (.env)
|
|
|
|
IMAP mode reads these keys from `.env`. If any of `BOUNCE_HOST`, `BOUNCE_USERNAME`, or `BOUNCE_PASSWORD` are missing, the command skips gracefully with a clear message.
|
|
|
|
```env
|
|
BOUNCE_HOST=imap.yourdomain.org
|
|
BOUNCE_PORT=993
|
|
BOUNCE_USERNAME=bounces@yourdomain.org
|
|
BOUNCE_PASSWORD=your-password
|
|
BOUNCE_SSL=true
|
|
BOUNCE_PROTOCOL=imap
|
|
```
|
|
|
|
## Examples
|
|
|
|
### Inject mode (local dev)
|
|
|
|
```bash
|
|
# Check current state without recording anything
|
|
php artisan bounce:simulate --email=user@example.com --status
|
|
|
|
# Record a hard bounce (triggers suppression if suppression_threshold=1)
|
|
php artisan bounce:simulate --email=user@example.com
|
|
|
|
# Record a soft bounce — recorded but never triggers suppression
|
|
php artisan bounce:simulate --email=user@example.com --type=soft
|
|
|
|
# Reset records between tests
|
|
php artisan bounce:simulate --email=user@example.com --clear --status
|
|
|
|
# Hard bounce with custom reason, linked to mailing #3
|
|
php artisan bounce:simulate --email=user@example.com --clear --reason="user unknown" --mailing-id=3
|
|
|
|
# Directly suppress without threshold logic
|
|
php artisan bounce:simulate --email=user@example.com --suppress
|
|
```
|
|
|
|
### IMAP mode (production)
|
|
|
|
```bash
|
|
# Verify connection and parsing — record nothing
|
|
php artisan bounce:simulate --imap --dry-run
|
|
|
|
# Connect, parse, and record all bounces found
|
|
php artisan bounce:simulate --imap
|
|
|
|
# Connect, parse, record, and delete processed emails from mailbox
|
|
php artisan bounce:simulate --imap --delete
|
|
```
|
|
|
|
If IMAP credentials are not set in `.env`, the command prints a warning and exits cleanly — safe to run on local dev.
|
|
|
|
## Output (inject mode)
|
|
|
|
```
|
|
=== Before: user@example.com ===
|
|
Total bounces : 0
|
|
Recent bounces : 0 (window: 365 days)
|
|
Recent definitive hard : 0
|
|
Suppressed : no
|
|
Suppression threshold : 1
|
|
Verif. reset threshold : 1
|
|
|
|
Recording hard bounce for user@example.com
|
|
Reason: 550 5.1.1 user unknown
|
|
Matches definitive hard-bounce pattern: yes
|
|
|
|
Bounce recorded.
|
|
|
|
=== After: user@example.com ===
|
|
Total bounces : 1
|
|
Recent bounces : 1 (window: 365 days)
|
|
Recent definitive hard : 1
|
|
Suppressed : YES
|
|
Suppression threshold : 1
|
|
Verif. reset threshold : 1
|
|
```
|
|
|
|
## Output (IMAP mode)
|
|
|
|
```
|
|
Connecting to IMAP mailbox: imap.yourdomain.org as bounces@yourdomain.org
|
|
Found 2 bounce email(s).
|
|
|
|
[#1] hard bounce → faileduser@somedomain.com
|
|
Reason: 550 5.1.1 User unknown
|
|
Definitive hard-bounce pattern: yes
|
|
|
|
=== Before: faileduser@somedomain.com ===
|
|
...
|
|
|
|
=== After: faileduser@somedomain.com ===
|
|
...
|
|
|
|
Done. Processed: 2, skipped: 0, deleted from mailbox.
|
|
```
|
|
|
|
## Typical test flows
|
|
|
|
### Local dev
|
|
```bash
|
|
# 1. Check baseline
|
|
php artisan bounce:simulate --email=user@example.com --status
|
|
|
|
# 2. Simulate a hard bounce
|
|
php artisan bounce:simulate --email=user@example.com
|
|
|
|
# 3. Confirm suppression
|
|
php artisan mailings:manage-bounces stats
|
|
|
|
# 4. Reset for next test
|
|
php artisan bounce:simulate --email=user@example.com --clear
|
|
```
|
|
|
|
### Production
|
|
```bash
|
|
# 1. Dry run — verify IMAP connection and bounce parsing
|
|
php artisan bounce:simulate --imap --dry-run
|
|
|
|
# 2. Process and clear mailbox
|
|
php artisan bounce:simulate --imap --delete
|
|
|
|
# 3. Inspect results
|
|
php artisan mailings:manage-bounces stats
|
|
```
|
|
|
|
## What the command exercises
|
|
|
|
- `MailingBounce::recordBounce()` — creates the DB record
|
|
- Definitive hard-bounce pattern matching — determines whether threshold logic runs
|
|
- `checkAndApplyThresholds()` — suppression and `email_verified_at` reset logic
|
|
- `MailingBounce::isSuppressed()` — used by `UniversalBounceHandler` on every outgoing email
|
|
- IMAP connection, message fetching, DSN parsing (IMAP mode only)
|
|
|
|
## What it does NOT test
|
|
|
|
- The scheduled `mailings:process-bounces` job (same IMAP logic, different command)
|
|
- Queue processing — run `php artisan queue:work` separately
|
|
- Mailpit SMTP layer — use `copy_to_mailpit` in config to verify emails land in Mailpit UI
|
|
|
|
## Related commands
|
|
|
|
```bash
|
|
php artisan mailings:manage-bounces stats # Full suppression list and stats
|
|
php artisan mailings:manage-bounces list # List all bounced addresses
|
|
php artisan mailings:manage-bounces check-thresholds # Check all addresses against thresholds
|
|
php artisan mailings:manage-bounces unsuppress --email= # Remove suppression
|
|
php artisan mailings:manage-bounces cleanup --days=90 # Delete old soft bounces
|
|
```
|