# Mailbox Examples ## Authorized frontend inbox ```php isLoggedin() || !$modules->isInstalled('Mailbox')) { throw new Wire404Exception(); } /** @var Mailbox $mailbox */ $mailbox = $modules->get('Mailbox'); $api = $mailbox->api($user); if(!$api->canRead()) throw new WirePermissionException(); $result = $api->messages('INBOX', 1, 20); foreach($result['messages'] as $summary) { echo '

' . $sanitizer->entities($summary['subject']) . '

'; } ``` Grant `mailbox-api` only to roles that may read the configured shared mailbox. `isLoggedin()` by itself is not authorization. ## Review mail settings discovered for an address ```php $api = $mailbox->api($user); if(!$api->canRead()) throw new WirePermissionException(); $discovery = $api->discover('automation@example.com'); if($discovery['credentials_used'] || $discovery['applied']) { throw new WireException('Discovery crossed its review-only boundary.'); } foreach($discovery['candidates'] as $candidate) { // Display source, confidence, warnings, and escaped settings for review. // Saving a candidate and testing credentials are separate admin actions. } ``` ## Authorized plain-text send ```php CSRF->validate(); $api = $mailbox->api($user); if(!$api->canSend()) throw new WirePermissionException(); $result = $api->send([ 'to' => [['email' => 'recipient@example.com', 'name' => 'Recipient']], 'subject' => 'Status update', 'body' => "The job completed successfully.\n", 'attachments' => [[ 'name' => 'report.txt', 'type' => 'text/plain', 'content' => "Bounded report contents\n", // REST/CLI use content_base64 ]], ]); ``` Enable SMTP first and grant both `mailbox-api` and `mailbox-send`. Do not accept an arbitrary From address from request input; Mailbox uses the configured account identity or administrator-configured alias. ## Search and read a text attachment ```php $api = $mailbox->api($user); $result = $api->search([ 'from' => 'billing@example.com', 'subject' => 'invoice', 'since' => '2026-01-01', 'has_attachment' => 'yes', ], null, 1, 25); // null searches all selectable folders if($result['truncated']) { // Refine filters before treating absence as meaningful. } if($api->canReadAttachments()) { $text = $api->attachmentText('INBOX', 123, '2.1'); } ``` The role needs `mailbox-attachments` in addition to `mailbox-api`. `attachmentText()` accepts text-like MIME types only; use the forced-download endpoint for other bounded files. ## Observe indexed new-message notifications ```php $wire->addHookAfter('Mailbox::messageIndexed', function(HookEvent $event) { $notification = $event->arguments(0); // Enqueue a small, idempotent site-owned job. Do not send externally here. }); $api = $mailbox->api($user); $notifications = $api->notifications(25, true); ``` Enable background synchronization first. Initial seeding intentionally emits no historical notifications. The hook payload contains mailbox metadata and must be treated as sensitive. ## Propose a confirmation link Use a POST endpoint, validate CSRF, and pass the link hash rather than accepting an arbitrary URL: ```php CSRF->validate(); $folder = $sanitizer->text($input->post('folder')); $uid = $input->post('uid', 'int'); $hash = $sanitizer->text($input->post('hash')); $proposal = $mailbox->api($user)->proposeConfirmation($folder, $uid, $hash); ``` For a reviewed email-code form, enable advanced confirmations and declare only workflow shape—not the code or arbitrary fields: ```php $proposal = $mailbox->api($user)->proposeConfirmation($folder, $uid, $hash, [ 'mode' => 'code_form', 'max_steps' => 1, 'code_field' => 'otp', // optional when the field has a recognized name ]); ``` Mailbox extracts exactly one code from the message, stores only an account-keyed fingerprint, and re-reads it during execution. A different authorized user must still approve the proposal. Never accept `workflow`, mode, or step count directly from an untrusted public request without a site-owned allowlist. ## Human approval endpoint ```php $session->CSRF->validate(); $id = $sanitizer->text($input->post('proposal_id')); $approved = $mailbox->api($user)->approveConfirmation($id); ``` Execution should normally be a separate action after review. It uses the same CSRF and permission boundary: ```php $session->CSRF->validate(); $result = $mailbox->api($user)->executeConfirmation($id); ``` ## Verk/Kontor adapter seam ```php $mailbox->registerApprovalProvider('kontor', function(array $proposal, Mailbox $mailbox) use ($modules) { if(!$modules->isInstalled('KontorAI')) return; // Map the proposal into the exact installed Kontor approval API. // Do not approve it from this notification callback. }); ``` Feature-detect the target module and verify its installed API before implementing the adapter. Mailbox deliberately has no hard dependency on Verk or Kontor. ## Squad AI analysis and bounded agent Squad support is separately opt-in. Use the permission-gated facade so the logged-in requester and selected account remain explicit: ```php isInstalled('Mailbox') && $modules->isInstalled('Squad')) { /** @var Mailbox $mailbox */ $mailbox = $modules->get('Mailbox'); $api = $mailbox->api($user, 1); if($api->canUseSquad()) { $analysis = $api->analyzeWithSquad('INBOX', 123, 'Summarize the request and list any deadlines.', [ 'provider' => 'openai', 'model' => 'gpt-5.4-mini', 'maxTokens' => 600, ]); } } ``` For a bounded tool-use flow, call `$api->runSquadAgent('Find the newest account-verification email and create a confirmation proposal for human review.')`. The agent can list/read mail, inspect redacted link hashes, and create a pending proposal. It cannot approve or execute one. Do not bypass the facade for frontend code, and do not treat a successful Squad response as proof that a sender, link, or requested action is safe.