monicahq/monica · error · ModelNotFoundException
The user does not belong to the vault's account.
Error message
The user does not belong to the vault's account.
What it means
ScheduleContactReminderForUser is an internal service (never called from HTTP clients) that schedules a reminder notification for one specific user of a vault. It loads the ContactReminder and User by id, then asserts user.account_id equals the reminder's contact vault account_id; a mismatch aborts with ModelNotFoundException, which Laravel renders as a 404.
Source
Thrown at app/Domains/Contact/ManageReminders/Services/ScheduleContactReminderForUser.php:47
'user_id' => 'required|uuid|exists:users,id',
];
}
/**
* Schedule a contact reminder for the given user, on his timezone.
* For each user in the vault, a scheduled reminder is created.
* This service SHOULD NOT BE CALLED FROM THE CLIENTS, ever.
* It is called by other services.
*/
public function execute(array $data): void
{
$this->validateRules($data);
$this->data = $data;
$this->contactReminder = ContactReminder::findOrFail($this->data['contact_reminder_id']);
$this->user = User::findOrFail($this->data['user_id']);
if ($this->user->account_id != $this->contactReminder->contact->vault->account_id) {
throw new ModelNotFoundException('The user does not belong to the vault\'s account.');
}
$this->getDate();
$this->schedule();
}
/**
* A ContactReminder can be either a complete date, or only a day/month.
* If it is only a day/month, we need to add a fake year so we can still
* manipulate the date as a Carbon object.
*/
private function getDate(): void
{
if (! $this->contactReminder->year) {
$this->upcomingDate = Carbon::parse('1900-'.$this->contactReminder->month.'-'.$this->contactReminder->day);
} else {
$this->upcomingDate = Carbon::parse($this->contactReminder->year.'-'.$this->contactReminder->month.'-'.$this->contactReminder->day);
}View on GitHub (pinned to e08e917341)
Solutions
- Pass a user belonging to the same account as the reminder's contact vault: derive candidates from $reminder->contact->vault->users
- Never feed Auth::id() into this service without verifying the authenticated user is a member of that vault
- In tests, create account, vault, contact, reminder and user under one account so account_id matches
Example fix
// before
$data = [
'contact_reminder_id' => $reminder->id,
'user_id' => Auth::id(), // may belong to another account -> throws
];
// after
$vault = $reminder->contact->vault;
abort_unless($vault->users->contains(Auth::id()), 403, 'User is not a member of this vault.');
$data = [
'contact_reminder_id' => $reminder->id,
'user_id' => Auth::id(), // now guaranteed same account
]; Defensive patterns
Strategy: validation
Validate before calling
// Validate before calling: user must belong to the reminder's vault (same account)
$reminder = ContactReminder::with('contact.vault')->findOrFail($data['contact_reminder_id']);
$vault = $reminder->contact->vault;
if (! $vault->users()->where('users.id', $data['user_id'])->exists()) {
throw new InvalidArgumentException('user_id does not belong to the vault of contact_reminder_id.');
} Type guard
function isValidReminderRecipient(string $userId, ContactReminder $reminder): bool
{
return $reminder->contact->vault
->users()
->where('users.id', $userId)
->exists(); // membership implies same account
} Try / catch
use Illuminate\Database\Eloquent\ModelNotFoundException;
try {
app(ScheduleContactReminderForUser::class)->execute($data);
} catch (ModelNotFoundException $e) {
// 404-shaped: one of the ids is wrong or crosses accounts — log and re-dispatch without that user
report($e);
} Prevention
- Derive recipient ids from the vault's user list, never from the authenticated user unchecked
- Never call this service from client code; keep it behind the reminder-scheduling services
- Create related factory data under one account in tests
- Treat ModelNotFoundException from this service as a data-integrity signal worth alerting on
When it happens
Trigger: Calling execute() with a user_id whose account differs from the account owning contact_reminder_id — e.g. passing the currently authenticated user instead of a member of the vault, or ids produced by factories/seeds belonging to two different accounts.
Common situations: Calling code deriving user_id from Auth::user() instead of the vault's member list, test data created across two accounts, or stale ids after account restructuring.
Related errors
- Could not get address book data.
- No address book found
- $e->getMessage()
- The password is not valid.
- Only email can be sent.
AI-assisted analysis of monicahq/monica@e08e917341 (2026-08-17).
Data as JSON: /api/errors/d58c3e3fb3a7014c.
Report an issue: GitHub.