Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Mailbox Component Laravel Package

uvdesk/mailbox-component

uvdesk/mailbox-component provides mailbox configuration and email ingestion for UVdesk helpdesk, connecting mail accounts, fetching and parsing incoming messages, and routing them into ticket workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

This is an inbound/IMAP mailbox reader, not a sender. Laravel's Mail facade (Symfony Mailer under the hood) is outbound-only, so this package fills a real gap: fetching messages from a mailbox, parsing MIME, and turning customer emails into records. Treat it as a sibling to Laravel's mail stack, not a replacement, and never wire it near your outbound mailable code.

Install

composer require uvdesk/mailbox-component:^1.1.5

Prerequisites — check these before anything else

  • ext-imap and ext-mbstring are effectively required. The v1.1.5 encoding fixes make mbstring correctness directly relevant.
  • Verify up front, because a missing extension is the #1 install failure and manifests as opaque runtime errors:
php -m | grep -i imap
php -m | grep -i mbstring

Where to look first

  1. README.md — updated in v1.1.5 and previously thin; it is the only source for constructor/argument signatures.
  2. CHANGELOG-1.1.md — the encoding fix and the duplicate-IMAP-folder fix are documented here. Read it before assuming behavior changed elsewhere.
  3. src/ — the Server, Mailbox, Folder, Message and config value objects (Config\Imap) are the surfaces you will actually touch. There is no facade, no service provider, no Eloquent integration — you write the glue.

First use case: poll a support inbox into an Eloquent model

use Uvdesk\Mailbox\Mailbox;
use Uvdesk\Mailbox\Server;
use Uvdesk\Mailbox\Config\Imap;

$server = new Server(new Imap([
    'host'     => config('services.mailbox.host'),
    'username' => config('services.mailbox.username'),
    'password' => config('services.mailbox.password'),
    'port'     => 993,
    'flags'    => '/imap/ssl/novalidate-cert',
]));

$mailbox = new Mailbox($server);
$mailbox->connect();

foreach ($mailbox->getFolders() as $folder) {
    // inspect $folder->getName() before iterating — do not assume INBOX only
}

Confirm signatures against the README before copying; the classes are stable across 1.1.x, but the API is Symfony-flavored rather than Laravel-first, and the docs are the only contract.

Upgrade immediately if you are on 1.1.4 or earlier and have seen mojibake or =?UTF-8?B?...?= artefacts in subjects or bodies. Do not write workarounds for a bug the patch already fixes.

Implementation Patterns

1. Wrap the component in a singleton service — never call it from a controller

IMAP connections are stateful, slow, and connection-count sensitive. Bind one poller service in the container and drive it only from queued jobs.

// app/Providers/AppServiceProvider.php
$this->app->singleton(MailboxPoller::class, fn () =>
    new MailboxPoller(config('services.mailbox'))
);
// app/Jobs/PollMailbox.php
class PollMailbox implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable;

    public int $tries = 3;
    public int $timeout = 120;
    public array $backoff = [30, 120, 600];

    public function handle(MailboxPoller $poller): void
    {
        $poller->eachUnseenMessage(function ($message) {
            InboundEmail::firstOrCreate(
                ['message_id' => $message->getMessageId()],
                [
                    'subject'   => $message->getSubject(),
                    'from'      => $message->getFrom(),
                    'body_html' => $message->getHtmlBody(),
                    'body_text' => $message->getTextBody(),
                    'raw'       => $message->getHeaders()->toString(),
                ]
            );
        });
    }
}

2. Idempotency is non-negotiable — key on Message-ID

IMAP delivery is at-least-once in practice: reconnects, retried jobs, and overlapping schedulers all re-deliver. Enforce uniqueness in the database, not in the parser.

Schema::create('inbound_emails', function (Blueprint $table) {
    $table->id();
    $table->string('message_id')->unique(); // dedupe boundary
    $table->string('subject')->nullable();
    $table->string('from')->nullable();
    $table->longText('body_html')->nullable();
    $table->longText('body_text')->nullable();
    $table->timestamps();
});

Use firstOrCreate/upsert so retries are no-ops. The message-id-plus-mailbox pair is the natural key if you poll multiple accounts into one table.

3. Dedicated read-only mailbox account

Never point the poller at a shared human inbox. Fetching toggles \Seen flags and mutates folder state with user-visible consequences. Give it its own credentials and iterate explicit folders rather than using "mark all read" helpers.

4. Two-phase processing: fetch fast, parse later

The socket is the fragile resource; parsing is the CPU-heavy one. Pull the raw message and envelope inside the fetch job, persist them, then dispatch a separate ParseInboundEmail job. This decouples a slow MIME parse (or a hostile payload) from the connection loop and keeps the fetch interval stable.

5. Normalize into a domain event at the boundary

The single most important design decision: adapt the component into your own InboundEmailReceived event (or DTO) at the edge. Keep the Symfony-flavored Message/Folder objects quarantined inside the adapter. Business logic — ticket creation, contact matching, routing — should not see a single Uvdesk\Mailbox class. This is what lets you swap implementations later without touching the helpdesk workflow.

6. Partition by mailbox, not by worker

IMAP scaling does not work like HTTP scaling. One consumer per mailbox. Add capacity by adding mailboxes, not by adding workers to the same mailbox — two consumers on one mailbox duplicate messages or fight over folder state. Schedule fetches on a long interval (withoutOverlapping() on the job is essential) and put parsing on a separate queue with its own workers.

7. Configuration belongs in config/, credentials in the secrets store

Do not commit mail server details. Bind host/port/username from config, resolve the password from your existing secrets mechanism, and expose a config/services.php entry per mailbox so operators can rotate credentials without a deploy.

Gotchas and Tips

IMAP polling is stateful — design for the failure modes, not against them

  • Connection drops, expiring sessions, and provider rate limits are routine, not exceptional. Retry with backoff, and never lose the last-seen UID checkpoint — losing it either re-ingests the whole mailbox or skips messages silently.
  • Enforce idempotency at the persistence layer regardless, because a checkpoint reset will duplicate. The parser cannot prevent this.

Encoding is the bug class that will bite you

v1.1.5 fixed real encoding issues, which means output for non-ASCII headers, quoted-printable/base64 bodies, and attachment filenames may have shifted. If you had any downstream code compensating for the old broken behavior, re-validate it. This is a fix, so it changes output only where output was previously wrong — but "previously wrong" is exactly where workarounds live.

Silent parse degradation is the danger

An email that arrives but produces an empty body looks like success. Alert on your empty-body rate, not just on thrown exceptions. Log raw headers on parse failure — you cannot reproduce it without the original mailbox otherwise.

Attachments are the memory risk

MIME parsing holds message content in memory. Cap message size at the IMAP level and stream attachments to object storage rather than stuffing them into the Eloquent record. A 40MB newsletter with inline images will OOM a 128MB PHP worker.

Malformed MIME is an attack surface

You are feeding untrusted input to a parser. Sandbox HTML on render, strip remote content (prevent tracking pixels and SSRF-ish image loads), cap MIME nesting depth, and be suspicious of attachment filenames.

The Swiftmailer question

The v1.1.5 changelog mentions a Swiftmailer SVG icon. Swiftmailer has been EOL since 2021. Verify whether it is still a runtime transport dependency or purely a README asset. If any transport path routes through it, treat that as a security and longevity risk and plan a fork.

Composer and Symfony version constraints

This is a Symfony-oriented component, not a Laravel package. Pin the package explicitly and lock Symfony component versions in your root composer.json so the transitive graph cannot drift into conflict with Laravel's own pinned Symfony components. An update that happens to bump a shared Symfony library can break either side.

WAMP / Windows

The v1.1.5 release removed a duplicate IMAP folder that broke WAMP setups. If you develop on Windows and hit odd folder-listing behavior, upgrade first. This was a DX fix, not a production concern.

No service provider, no facade, no doc guarantees

There is no Laravel-first API. Signatures live in the README and the source. Budget accordingly: a competent Laravel dev gets a working parse in a day, but operating it well requires familiarity with IMAP semantics and the Symfony Mime model. Designate one owner, keep a runbook for credential rotation and checkpoint recovery, and treat a fixture corpus of real messages (weighted toward non-English and attachment-heavy samples) as living documentation — it is the only thing that catches encoding regressions on the next release.

Maintenance cadence

Releases arrive irregularly as bundles of fixes. Budget half a day per release to read the diff and run the corpus. Pin and test rather than assume a patch is safe — internal refactoring in a patch release can carry subtle behavior drift that the version number does not signal.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource