'boolean', 'active' => 'boolean', 'authenticates' => 'boolean', 'last_verified_at' => 'datetime', ]; } public static function findByKey(string $key): ?self { return static::query()->where('key', $key)->first(); } /** Plaintext in, ciphertext to the column. */ public function setPasswordAttribute(?string $value): void { $this->attributes['password'] = ($value === null || $value === '') ? null : app(SecretCipher::class)->encrypt($value); } /** Ciphertext from the column, plaintext out. */ public function getPasswordAttribute(?string $value): ?string { return ($value === null || $value === '') ? null : app(SecretCipher::class)->decrypt($value); } /** The SMTP user: an explicit one, or the address itself. */ public function smtpUsername(): string { return $this->username !== null && $this->username !== '' ? $this->username : $this->address; } /** * last_verified_at proves a successful test against the config THIS * mailbox held at the time — an address, username or authenticates * change (or a new password) invalidates only this row. Sets the * attribute without saving: EditMailbox::save() calls this alongside * other attribute changes so the whole edit lands in one write, not two. */ public function invalidateVerification(): void { $this->last_verified_at = null; } /** * The mirror of invalidateVerification() for the server card * (Admin\Mail::saveServer()): host, port and encryption are shared by * every mailbox at once, not a per-row concept, so a change there * invalidates all of them in one write rather than each row deciding * independently — Codex R15#6, P2. Scoped to rows that still have * something to clear, so saving unrelated server settings does not bump * every mailbox's updated_at for nothing. */ public static function invalidateAllVerifications(): int { return static::query()->whereNotNull('last_verified_at')->update(['last_verified_at' => null]); } /** * Enough to send with: an address, and a password ONLY when this mailbox * actually authenticates. * * A trusted local or private-network relay can legitimately need no * password at all (Codex R15#4, P1b) — requiring one unconditionally * would refuse a relay that was never broken, just unauthenticated. */ public function isConfigured(): bool { return $this->active && $this->address !== '' && (! $this->authenticates || $this->getRawOriginal('password') !== null); } }