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

Email Crm Bundle Laravel Package

libre-informatique/email-crm-bundle

Email CRM bundle for PHP applications, offering reusable components to connect email interactions with customer records and support lightweight CRM workflows. Designed for straightforward integration into existing projects and adaptable to common contact-management needs.

View on GitHub
Deep Wiki
Context7

Getting Started

Acknowledge the framework gap on day one. libre-informatique/email-crm-bundle is a Symfony bundle. There is no ServiceProvider, no config/email-crm.php, no facade, no php artisan command exposed to Laravel. It registers through config/bundles.php, is configured via YAML under config/packages/, and is wired by Symfony's DependencyInjection compiler pass. So "day-to-day leverage" in a Laravel codebase looks like one of two things:

  1. Reading it as a reference model (the realistic path at 1 star, 0 dependents, NOASSERTION license), or
  2. Forking and porting the domain logic into Laravel services and Eloquent models (a bounded spike, weeks not months).

Do not composer require it into a Laravel app and expect anything to boot. Composer will happily install it, and nothing will happen.

Minimal steps to inspect the package:

# Do NOT add to your app's composer.json. Clone in isolation.
git clone https://github.com/sil-project/EmailCRMBundle.git ../../reference/email-crm-bundle
cd ../../reference/email-crm-bundle

# Read the manifest first — this tells you the supported PHP/Symfony range
cat composer.json

# Check whether a LICENSE file actually exists (NOASSERTION is a warning)
ls -la | grep -i licen

Where to look first, in order:

  1. composer.json — supported PHP/Symfony constraints, whether it pulls swiftmailer/swiftmailer (EOL since late 2021) or symfony/mailer. This single file decides whether the code you're reading is even portable to a modern Laravel 10/11 app.
  2. DependencyInjection/Configuration.php — the canonical list of configurable behaviors. This is your feature inventory: what entities exist, what's toggleable, what a "campaign" means to the authors.
  3. Resources/config/*.yml — service wiring and Doctrine mappings. Reveals the domain boundaries (contact / thread / message / list / campaign) and any hidden dependencies on sibling Libre Informatique bundles.
  4. Entity/*.php (or Model/*.php) — the actual data model. This is the only asset you're really harvesting.
  5. Manager/*.php / Repository/*.php — the write paths and query shapes. Read these to understand invariants (e.g., "a message belongs to exactly one thread", "a contact can appear on many lists").

First use case — a 90-minute "read and sketch":

# Dump every Doctrine column to a single file you can read in one pass
grep -rnE '@(ORM\\Column|ORM\\ManyToOne|ORM\\OneToMany|ORM\\ManyToMany|ORM\\JoinColumn)' \
  Entity/ | tee /tmp/schema-sketch.txt

Then hand-translate that into a Laravel migration sketch. That sketch is your spike deliverable: a contacts, email_lists, email_list_contacts, email_threads, email_messages, and email_campaigns set of tables. If the schema that falls out of that exercise maps cleanly onto your product's needs, you've extracted the value this bundle has to offer. If it doesn't, you've closed out the evaluation for the cost of one afternoon.

Implementation Patterns

The patterns below are how you'd use the ideas in Laravel day-to-day. There is no supported pattern that lets you use the bundle itself as a runtime dependency in a Laravel app.

Pattern 1 — Extract the domain model into Eloquent, not the bundle.

Treat the Doctrine entities as a spec sheet. Port relations one-for-one, not the annotations:

// app/Models/EmailMessage.php — ported shape, not a dependency
class EmailMessage extends Model
{
    protected $fillable = ['thread_id', 'contact_id', 'direction', 'subject', 'body', 'sent_at'];

    public function thread(): BelongsTo { return $this->belongsTo(EmailThread::class); }
    public function contact(): BelongsTo { return $this->belongsTo(Contact::class); }
}

The Doctrine mappings for ManyToOne/JoinColumn translate directly to belongsTo/hasMany + foreignId()->constrained(). Many-to-many with a join entity becomes belongsToMany with ->using(Pivot::class) if you need extra columns.

Pattern 2 — Adapter boundary around the mailer.

The bundle's send path is almost certainly SwiftMailer-era. In Laravel, do not let the bundle's sending concepts leak into your code. Route everything through an interface you own:

interface CrmMailer
{
    public function send(Campaign $campaign, Contact $contact, EmailTemplate $template): SendRecord;
}

Back it with Laravel's Mail/Notification facade and a queued Mailable. This is the seam that lets you swap in Mailgun/Postmark/SES later without touching CRM code. If you ever do port the bundle's send orchestration, wrap it in exactly this interface so it's replaceable from day one.

Pattern 3 — Sync the email flow through Laravel's pipeline, not the bundle's.

Symfony bundles like this wire up event subscribers and manager services. In Laravel, mirror the intent with Jobs and Listeners:

// The bundle concept → Laravel equivalent
// EventDispatcher + Subscriber  →  Event/Listener pairs
// Manager::send()               →  Queued Job
// Doctrine UnitOfWork flush     →  DB::transaction()

Concretely: an inbound-email webhook listener that creates a Contact if missing, resolves or creates a Thread by message-id headers, appends an EmailMessage, and dispatches CrmActivityRecorded. That's the entire "email management" value proposition, and it's ~150 lines of Laravel code.

Pattern 4 — Use the bundle's config as a requirements checklist.

Reading DependencyInjection/Configuration.php gives you an authoritative list of options the authors thought mattered. Turn each into a config entry in config/email-crm.php and a test case. This is the highest-leverage thing you can do with an undocumented, unmaintained package: it's a free feature inventory against which to scope your own build.

Pattern 5 — If you must run Symfony alongside Laravel (rare but real).

The only viable integration is data-level, not runtime-level: run the Symfony service on its own PHP runtime, share the database, and expose it via HTTP. Laravel talks to it via a Http:: client or a job queue (e.g., Redis/SQS). This is the only pattern where the bundle can actually stay as-is. Anything else means porting entities and losing the "reusable bundle" premise. Do not attempt to embed the Symfony kernel in a Laravel process.

Workflow that works for a Laravel team:

  1. Clone + read composer.json, Configuration.php, Entity/*.php (one hour).
  2. Sketch the migration set on paper (one hour).
  3. Decide: does the sketch match your product? If yes, build it natively — Laravel Mail + Notifications + two or three models. If no, close the ticket.
  4. If a stakeholder insists on "adopting" the bundle, route the decision through legal first. NOASSERTION means no identified license; shipping it into a commercial app is a risk that no engineering benefit here outweighs.

Gotchas and Tips

It's not a Laravel package — and it will never be one. The framework-fit gap is the biggest gotcha and the one most likely to be missed. Everything else in this section is a consequence of it. The bundle assumes a Symfony kernel, the DI container, and Doctrine's EntityManager. None of those exist in a Laravel request lifecycle. Anything you "get working" by manually instantiating bundle services in a Laravel service provider is unsupported and will be a maintenance burden you can't outsource.

License is NOASSERTION — stop before you write product code. That means GitHub could not identify an OSI license. There may be no LICENSE file, or a broken one, or a custom notice. Do not ship it into commercial or client-facing code until counsel has read it. This is not a caveat that can be waived with a comment in composer.json.

Version constraints will bite if you try a Composer install. Expect php: ^7.x and symfony/symfony: ^3.4 || ^4.x in the manifest. Modern Laravel requires PHP 8.1+, and Composer's solver will fail on transitive conflicts before you even reach the "it doesn't boot" stage. If you're reading the manifest and seeing swiftmailer/swiftmailer, that's the EOL mailer — any transport code you port must be rewritten against symfony/mailer or, in Laravel, Illuminate\Mail.

Doctrine annotations ≠ Eloquent attributes. If you're copying entity code, the Doctrine metadata lives in PHP 8 attributes or docblock annotations (@ORM\Column(type="string", nullable=true)), which Eloquent will silently ignore if pasted in. Strip all ORM\* metadata and hand-write the equivalent migration. Watch for:

  • nullable=true — Eloquent columns default to NOT NULL unless you say otherwise.
  • Cascade options (cascade={"remove"}) — Eloquent needs ->onDelete('cascade') on the FK.
  • Enum columns — Doctrine's type="integer" with PHP-level enum handling doesn't map to Laravel enums; you'll choose one or the other.
  • DateTime / timezone handling — Doctrine's \DateTime defaults may differ from what ->usesTimestamps() does.

Bundle config is not Laravel config. YAML Resources/config/*.yml files define service arguments. When porting to config/email-crm.php, map each argument carefully — Symfony's %env()% parameter resolution has no direct Laravel analog (it's env() / config(), and Laravel's config caching will freeze values at boot). Watch for %kernel.project_dir%-style path references that don't exist in Laravel.

Don't count on tests, docs, or CI. Star count is 1, dependents are 0, there's no visible release cadence. Treat the source as the only source of truth. Run composer audit on any fork you create, generate an SBOM, and expect to add your own PHPStan config — upstream almost certainly had none that runs today.

Deliverability and compliance are not covered. This is "email management" in the domain-modeling sense (threads, campaigns, send records), not in the "bounce handling, complaint processing, unsubscribe lists, DKIM/DMARC, GDPR retention" sense. Laravel side, that means: use Mailgun/Postmark/SES webhooks for bounces and complaints, implement your own suppression list, and add a retention_policy_days config before you ship. None of this comes free with the bundle, and none of it comes free when you port it either.

Async send is not baked in — you must add it. The bundle's send path is synchronous by design (SwiftMailer call, then persist the record). Laravel's queued Mailables are the correct replacement. Two specifics that will bite:

  • Idempotency: persist a send_record row with a unique constraint on (campaign_id, contact_id) before queuing the send. Otherwise a queue retry double-sends.
  • Fan-out cost: campaign-to-list dispatch should batch with Bus::batch([...]) or a chunked job, and rate-limit per provider. Sending 10k recipients one Mail::to() at a time from a single worker is a queue-starvation risk that has nothing to do with the bundle and everything to do with your dispatch design.

Extension points are the only thing worth keeping. If the bundle has an extension surface (custom fields, event subscribers, workflow hooks), that's a domain design signal — port the concept into Laravel's own extension mechanism (events, listeners, config-driven field maps in a JSON column). Don't port the mechanism itself; Symfony's event subscriber pattern is similar to Laravel's listeners in intent, but they're not interchangeable in code, and Laravel's contract for dispatch-order guarantees is different.

When you see LibreInformatique\...\Blast or Sylius\... in the imports, stop. The bundle belongs to the Libre Informatique Blast ERP/CRM family and likely pulls in Sylius-derived bundles (ResourceBundle, GridBundle, Registry). That's a large transitive tree you'd own permanently. If the imports show those packages, the realistic integration cost is "fork and maintain an ERP fragment," not "lift a domain model" — which is another reason to close out the evaluation at the inspection step.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core