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

Mail Mime Parser Laravel Package

zbateson/mail-mime-parser

A testable, PSR-compliant PHP mail MIME parser for RFC 822/2822/5322 messages. An alternative to imap* functions and Pear libraries, it parses strings, resources, or PSR-7 streams into messages with forgiving, standards-based handling. Requires PHP 8.1+.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. zbateson/mail-mime-parser is a pure-PHP, framework-agnostic MIME decoding library — no daemon, no extension, no database. It occupies one narrow slot in an inbound-email pipeline: raw RFC 822/5322 bytes in → structured IMessage object out (headers, address parts, text/HTML bodies, attachment parts with decoded streams). It is explicitly not a mail transport — there is no IMAP/POP/SMTP client here — so it complements rather than replaces a fetcher (IMAP extension, webklex/php-imap, a provider webhook, or a raw-file drop). For a Laravel codebase this is a clean fit: Composer install, singleton bound in a service provider, PSR-3 logger injected so parse warnings land in Log::channel(), PSR-7 streams so attachments flow straight to S3/Flysystem without temp files. The only framework coupling we introduce is our own wrapper — the library itself stays swappable.

Integration feasibility. High. PHP 8.1–8.5 requirement matches Laravel 10/11/12; dependencies are guzzlehttp/psr7, php-di, and PSR interfaces (v3 of Guzzle supported since 4.0.3). No ext-imap or ext-mailparse dependency is a genuine operational win — it removes a common shared-hosting/Docker image constraint. The API surface covers what a TPM cares about: subject/from/to/cc extraction with person-name vs. address separation, getTextContent()/getHtmlContent(), attachment enumeration with getContentStream() and saveContent() for streamed persistence. The procedural Message::from() entry point plus file/resource/stream inputs makes it trivially testable with fixture .eml files.

Technical risk. Three items dominate:

Risk Severity Notes
Security history on untrusted input High 3.0.6, 4.0.2 and 4.0.6 each patched CRLF header injection and CPU/memory exhaustion (CVE-2026-61815/61816, GHSA-gmgm-r6fh-fq6g, GHSA-fcgh-j754-jh42). Parsing attacker-controlled mail is the threat model here. Must pin ≥ 4.0.6 and set explicit DI limits.
Single-maintainer / bus factor Medium-High ~552 stars, one primary author, no commercial support. Responsiveness on security has been good, but there's no SLA.
4.x immaturity + defaults churn Medium 4.0 is a recent major (API cleanup, PHP-DI switch). 4.0.6 lowered maxMessagePartCount from 10,000 → 1,000 — a legitimate large thread or digest could now silently truncate.

Secondary risks: the library does not throw on malformed input — it records errors on an ErrorBag and returns partial results. That is the right design for resilience but dangerous if nobody reads getErrors(); silent body/attachment loss is a plausible production failure. Companion S/MIME and PGP/MIME plugins (mmp-crypt-smime, mmp-crypt-gpg) add OpenSSL or PEAR dependencies — treat as separate scope decisions, not defaults. Reported "0 dependents" is almost certainly a dataset artifact, not evidence of disuse; verify via Packagist before citing it.

Key questions for the team.

  1. What is inbound volume, and what are p50/p99 message size and attachment size in our real corpus?
  2. Who owns the transport layer (fetching raw MIME)? Is that in scope or a separate vendor/component?
  3. Are we replacing imap_* functions, webklex/php-imap, or PEAR Mail_mime? What does the current parser get wrong that this fixes?
  4. Do we need S/MIME or PGP verification for compliance? If yes, that's a second dependency and a second threat surface.
  5. What is our attachment retention policy — object storage with TTL, or DB blobs?
  6. Who is on point for tracking upstream advisories, and what's the patch SLA for a mail-path CVE?
  7. Is BSD-2-Clause acceptable to legal (it is, in almost all cases — attribution only)?

Integration Approach

Stack fit. Laravel 10+ / PHP 8.1+, Composer. Bind MailMimeParser as a singleton in a dedicated service provider, passing Log::getLogger() (or a channel-scoped adapter) and a config array that overrides the DI defaults for maxMimePartDepth, maxHeaderCount, maxHeaderSizeBytes, maxMessageHeaderCount, maxMessageHeaderSizeBytes, maxMessageHeaderTokenCount, maxMessagePartCount. Keep every ZBateson\ type behind our own interface — controllers, jobs, and domain models should never type-hint IMessage.

Suggested seam:

App\Mail\Inbound\Contracts\MessageParser        // parse(StreamInterface|string): ParsedMessage
App\Mail\Inbound\MimeParserAdapter              // wraps MailMimeParser, translates ErrorBag → warnings
App\Mail\Inbound\ParsedMessage (DTO)            // subject, from[], to[], text, html, attachments[]

Migration path (phased).

  • Phase 0 — Validate (1 sprint). Composer-install in a branch, run it against a fixture corpus of ~200 real production messages (including known-bad: truncated MIME, 8-bit headers, TNEF, nested multipart, non-UTF8 charsets, 40 MB attachment). Diff subject/from/body/attachment counts against the incumbent parser. This is the go/no-go gate.
  • Phase 1 — Introduce the seam. Add the MessageParser port + adapter with the existing implementation behind it. Ship to production. No behavior change, but the swap point now exists.
  • Phase 2 — Shadow mode. Route a configurable percentage of inbound through both parsers; log divergences and ErrorBag output to a dashboard. Tune the limit config against p99 real traffic.
  • Phase 3 — Cut over. Flip the binding, keep the flag for one release cycle, then delete the legacy path and its transitive dependencies.

Compatibility notes. PSR-7 streams mean Guzzle v2/v3 both work; verify against whatever our HTTP client and Flysystem adapters already pull in to avoid a duplicate psr/http-message major. If we convert to Symfony\Mime\Email for forwarding/relay, do it in the adapter, not in shared code. Avoid holding IMessage objects across queue boundaries — serialize a DTO plus the raw MIME reference.

Sequencing. (1) dependency + container binding + config limits → (2) wrapper + fixture-driven tests in CI → (3) streamed attachment persistence (getContentStream() → S3, never getContent() on large parts) → (4) queue job wiring with per-message timeouts → (5) parse-error metrics and alerting → (6) flag flip → (7) legacy removal. Each step is independently shippable and reversible.

Operational Impact

Maintenance. Low surface, low churn: this is a small library with a documented upgrade path (1.x→4.x guides) and no infrastructure footprint — nothing to provision, patch at the OS level, or scale as a service. The real maintenance obligation is dependency vigilance: Dependabot/Renovate on ^4.0.6, a named owner for GitHub security advisories, and a rule that mail-path CVE patches ship within days, not sprints. Budget roughly a few hours per quarter plus reactive patching.

Support. No vendor, no SLA — GitHub issues and a sponsorship model. That's acceptable for a component at this blast radius, provided we retain an exit. The port interface from Phase 1 is precisely that exit: webklex/php-imap (has its own parser), symfony/mime, or a hosted inbound-email API can slot in behind the same contract. Document that fallback in the ADR so it survives staff turnover.

Scaling. Parsing is single-threaded CPU per message and horizontal by nature — scale with queue workers, not bigger boxes. Two levers matter: (a) streams — getContentStream()/saveContent() keep large attachments off the PHP heap, so a 25 MB PDF doesn't touch memory_limit; (b) explicit limits — the 4.0.x DI caps are the primary defense against pathological input, and they must be tuned to observed p99, not left at defaults (especially the 1,000-part default). Persist attachment bytes to object storage with an extraction TTL; never blob them into MySQL.

Failure modes to instrument.

Failure Symptom Mitigation
Malformed/truncated MIME Empty body or 0 attachments, ErrorBag populated, no exception Read getErrors() on every parse; route to a "quarantine" queue that preserves raw MIME for reprocessing
Limit trip (parts/headers/depth) Silent truncation on large threads or digests Alert on error-rate delta at deploy; tune caps to real corpus
Oversized attachment via getContent() Memory exhaustion kills the worker Code-review ban on getContent() for attachments; enforce getContentStream()
Encrypted S/MIME or PGP Body unreadable, no plugin installed Detect and tag; decide explicitly whether to add companion packages
Charset / RFC 2047 edge cases Mojibake in subject or sender name Fixture regression suite; sample-audit production output weekly post-launch
Single-maintainer abandonment No upstream fixes Port interface + named fallback; periodic health check

Ramp-up. Expect 1–2 developer-days for a mid-level PHP engineer to become productive with the IMessage/IHeader/part model and the PHP-DI configuration — the concepts are standard MIME, but the limit configuration and ErrorBag semantics are non-obvious and usually learned the hard way. Add 2–3 days for whoever builds the fixture corpus, which is the highest-leverage artifact here and should be treated as a first-class deliverable, not test scaffolding. Operations needs roughly half a day: what a parse-error spike looks like, what a truncation alert means, and how to replay a quarantined raw message. Mitigate knowledge concentration by keeping the adapter thin, documented, and covered by fixtures committed to the repo.

Bottom line for a TPM. The score (40.7 overall, opportunity 62.8) reads correctly: this is a solid, narrowly-scoped component with a real security history that is managed, not eliminated by pinning 4.0.6+ and configuring limits. Adopt it behind a wrapper interface, fund the fixture corpus, and keep the exit open. It is a dependency decision, not a platform decision — and the integration cost is dominated by attachment/queue plumbing, not by the parser itself.

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.
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules
loco/loco
starfederation/datastar-php
yupmin/modern-phystrix
stephenjude/extended-artisan-commands
lumphp/laravel-macroable
lumphp/laravel-collections
lumphp/laravel-contracts
sermepa/sermepa
arkecosystem/fortify