phplist/core
Core library for phpList, the open-source newsletter and email marketing system. Provides shared domain logic, models, and services for managing subscribers, lists, campaigns, and message sending in PHP applications.
phpList 4 Core is not a standalone app — it is a Symfony bundle plus a domain layer (entities, repositories, services, migrations) for the phpList 4 mailing-list platform. You consume it from a Symfony application (the official phplist/base-distribution is one), not by cloning it and running it.
Minimal steps:
composer require phplist/core in an existing Symfony 4.x/5.x app (PHP 7.x). The current tagged release is v4.0.0-alpha5; no detailed release notes were published for it, so treat it as a pre-stable alpha. If dependency resolution fails against modern Symfony, pin "phplist/core": "4.0.0-alpha5" or "dev-master" against a compatible Symfony version.config/bundles.php:
PhpList\Core\Bundle\CoreBundle::class => ['all' => true],
config/packages/doctrine.yaml — Core relies on the default connection and its own mappings/migrations.Resources/config/services.yml and Doctrine mapping in its Resources/config/doctrine directory; autoloading is handled by the bundle extension).phpList:):
bin/console phpList:database:create
bin/console phpList:database:update
bin/console debug:container 'PhpList\Core' and bin/console doctrine:mapping:info.Where to look first:
src/Bundle/CoreBundle.php — the integration seam; everything else is reachable from here.src/Domain/Model/ — entities such as Subscription\Subscriber, Subscription\SubscriberList, Messaging\Campaign, Administration\Administrator. This is the API you will actually use day-to-day.src/Domain/Repository/ — Doctrine repositories with query helpers.src/Infrastructure/ — persistence, migrations, and technical services.src/Application/ — console commands and DI definitions.First use case: create a subscriber and attach it to a list programmatically.
use PhpList\Core\Domain\Model\Subscription\Subscriber;
use PhpList\Core\Domain\Model\Subscription\SubscriberList;
class OnboardingService
{
public function __construct(
private EntityManagerInterface $em,
private SubscriberRepository $subscribers,
) {}
public function subscribe(string $email, string $listName): void
{
$subscriber = new Subscriber();
$subscriber->setEmail($email);
$subscriber->setConfirmed(true);
$this->em->persist($subscriber);
$list = $this->em->getRepository(SubscriberList::class)
->findOneBy(['name' => $listName]);
$subscription = new Subscription($subscriber, $list);
$this->em->persist($subscription);
$this->em->flush();
}
}
Once that works, the second natural step is driving campaigns: create a Campaign, attach it to a list, queue messages, and let the phpList process-manager commands (phpList:processqueue, phpList:sendqueue) do the sending.
Depend on repositories and services, not the EntityManager, from your app code. Core exposes PhpList\Core\Domain\Repository\* services via the bundle's DI config. Autowire them into your controllers, commands, and Messenger handlers. This keeps your code insulated from Core's schema when it changes.
public function __construct(
private SubscriberRepository $subscribers,
private SubscriptionRepository $subscriptions,
) {}
Treat Core entities as your domain model and extend the bundle, not the class, for customization. Entity classes are Doctrine-mapped by the bundle; subclassing them means replicating mapping metadata and is fragile. Prefer:
Use the official distribution + Core for anything user-facing. The phplist/base-distribution repo wires Core into a runnable Symfony app with web UI, and phplist/rest-api exposes Core resources over HTTP. For headless integrations, install the REST API package and call it rather than reimplementing HTTP routes against Core.
Batch through console commands. The phpList model assumes queue processing runs out-of-band. Schedule phpList:processqueue / phpList:sendqueue (or your own wrapper commands) via cron or a supervisor. Do not send mail synchronously from a web request.
Migrations are the schema contract. In CI and local dev, always run phpList:database:update after pulling; Core's migrations live in the bundle and are the single source of truth. In your app, create your own migrations directory and keep it separate from Core's so upgrades do not conflict.
Testing pattern. Spin up a dedicated phpList_test database, load schema via phpList:database:update --env=test, and wrap each test in a transaction or use dama/doctrine-test-bundle. Core has its own PHPUnit suite — mirror its fixtures when you need realistic subscriber/list data.
Integration seam for other systems. Use the repositories for read paths (e.g., export subscribers by list) and write via services that enforce invariants (blacklist checks, confirmation state) rather than raw SQL. If you must write SQL, mirror the constraints in Core's migration files.
Pin and test against the alpha explicitly. v4.0.0-alpha5 is pre-1.0-quality in the SemVer sense: internal APIs and schema may still shift between alpha tags. Pin the exact version in composer.json, commit composer.lock, and re-run your test suite after any bump. Do not rely on ^4.0 ranges resolving to a compatible alpha.
v4.0.0-alpha5 is the current release, but it is a pre-stable alpha with no detailed changelog published — do not assume it fixes the compatibility issues that have dogged earlier tags. The repo (~89 stars) remains a niche project; expect friction with contemporary Symfony/PHP versions. Pin your Symfony version and your Core version exactly, and if you need dev-master, commit to tracking the branch and running its test suite after upgrades.composer require phplist/core gives you a bundle, not a working app. There is no bin/console until you create a Symfony kernel or pull in base-distribution. Newcomers lose hours here — check the distribution repo first if you just want phpList running.phpList:database:update produces confusing "table not found" errors deep in repository code.bin/console debug:container | grep PhpList rather than guessing PhpList\Core\Domain\Repository\SubscriberRepository — the bundle may alias interfaces to concrete classes. Autowiring by type is the safe path.processqueue). If mail "isn't going out," check the process manager/cron first — then check campaign status and list membership — before debugging SMTP config.doctrine config but also expects phpList-specific parameters (database prefix, bounce/administrator settings). Inspect CoreBundle::load/the bundle extension and Resources/config/*.yml for the actual parameter names; they are not documented on the README in depth.bin/console doctrine:mapping:info — confirm Core entities are discovered.bin/console debug:event-dispatcher — see which Core events you can hook.bin/console lint:yaml vendor/phplist/core/src/Resources/config — catches broken vendor config after manual edits.doctrine.dbal.logging) to see exactly which repository call is failing.Command classes registered as services, and decorator services wrapping Core repositories. Avoid forking Core unless you plan to maintain a fork — the AGPL permits it, but the low upstream activity makes divergence expensive.symfony/*, do not force-update Core; create a separate project pinned to the Symfony version the alpha supports, or contribute an upgrade. Mixing Core with Symfony 6+ components is a recipe for runtime type errors.ReturnTypeWillChange/mixed issues in vendored code; test thoroughly rather than assuming the alpha addressed compatibility.How can I help you explore Laravel packages today?