Concrete CMS email command
August 7, 2025 · AI Conversations
—
### ✅ 1. **Create the Command Class**
Create a new class like `SendEmailCommand.php` inside your package, for example in:
packages/my_package/src/Command/SendEmailCommand.php
<?php
namespace ConcretePackageMyPackageCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
use ConcreteCoreMailService as MailService;
use Core;
class SendEmailCommand extends Command
{
protected static $defaultName = 'my-package:send-email';
protected function configure()
{
$this->setDescription('Send a test email from your package.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
/** @var MailService $mail */
$mail = Core::make('mail');
$mail->to('example@example.com');
$mail->setSubject('Test Email from Custom Command');
$mail->setBody('This is a test email sent from a Concrete CMS custom command.');
$mail->from('admin@example.com', 'ConcreteCMS Bot');
try {
$mail->sendMail();
$output->writeln('<info>Email sent successfully.</info>');
} catch (Exception $e) {
$output->writeln('<error>Failed to send email: ' . $e->getMessage() . '</error>');
}
return Command::SUCCESS;
}
}
—
### ✅ 2. **Register the Command in Your Package**
In your `controller.php` of the package:
use ConcreteCoreConsoleApplication as ConsoleApplication;
use ConcretePackageMyPackageCommandSendEmailCommand;
public function on_start()
{
if ($this->app->bound(ConsoleApplication::class)) {
$this->app->extend(ConsoleApplication::class, function ($cli) {
$cli->add($this->app->make(SendEmailCommand::class));
return $cli;
});
}
}
—
### ✅ 3. **Run the Command**
After installing or refreshing the package, run:
./concrete/bin/concrete my-package:send-email
—
Let me know if you want to:
– Pass the recipient email via CLI options
– Use a queueable mail job
– Pull recipient addresses from the database
// File: packages/shopping_feed_manager/controller.php
namespace ConcretePackageShoppingFeedManager;
packages/shopping_feed_manager/controller.php
namespace ConcretePackageShoppingFeedManager;
I'll now guide you on how to register a custom CLI command (e.g. `shopping-feed:send-email`) in your **Concrete CMS package** that **sends an email**.
—
## ✅ Step-by-Step Setup
—
### 📁 1. Create Command File
Create a directory `src/Command` in your package:
packages/shopping_feed_manager/src/Command/SendEmailCommand.php
Then add this content:
<?php
namespace ConcretePackageShoppingFeedManagerCommand;
use ConcreteCoreMailService as MailService;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
use Core;
class SendEmailCommand extends Command
{
protected static $defaultName = 'shopping-feed:send-email';
protected function configure()
{
$this->setDescription('Sends a test email from Shopping Feed Manager package.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
/** @var MailService $mail */
$mail = Core::make('mail');
$mail->to('you@example.com');
$mail->from('admin@example.com', 'Shopping Feed Bot');
$mail->setSubject('Shopping Feed Test Email');
$mail->setBody('Hello, this is a test email from Shopping Feed Manager.');
try {
$mail->sendMail();
$output->writeln('<info>Email sent successfully.</info>');
} catch (Exception $e) {
$output->writeln('<error>Error: ' . $e->getMessage() . '</error>');
}
return Command::SUCCESS;
}
}
—
### 🧠 2. Register Command in Your Package Controller
Open:
packages/shopping_feed_manager/controller.php
And update the `on_start()` method:
<?php
namespace ConcretePackageShoppingFeedManager;
use ConcreteCorePackagePackage;
use ConcreteCoreConsoleApplication as ConsoleApplication;
use ConcretePackageShoppingFeedManagerCommandSendEmailCommand;
class Controller extends Package
{
protected $pkgHandle = 'shopping_feed_manager';
protected $appVersionRequired = '9.0.0';
protected $pkgVersion = '1.0.0';
public function getPackageName()
{
return t('Shopping Feed Manager');
}
public function getPackageDescription()
{
return t('Manages shopping feeds.');
}
public function on_start()
{
if ($this->app->bound(ConsoleApplication::class)) {
$this->app->extend(ConsoleApplication::class, function ($cli) {
$cli->add($this->app->make(SendEmailCommand::class));
return $cli;
});
}
}
}
—
### 🚀 3. Run the Command
After installation or refreshing the package:
./concrete/bin/concrete shopping-feed:send-email
You should see:
Email sent successfully.
—
Let me know if you want:
– Email recipient as a CLI argument (`–to=abc@xyz.com`)
– HTML email body
– Queue the mail job
– Log emails to a file for debug
I can update the code accordingly.
> `There are no commands defined in the "shopping-feed" namespace.`
then Concrete CMS isn't registering your custom command properly. Let's fix this **step-by-step**.
—
## ✅ Checklist to Debug and Fix
### ✅ 1. **Autoloading for `src/Command`**
Make sure your package is telling Concrete how to autoload classes.
In your `shopping_feed_manager/controller.php`, add this to your package class:
public function getPackageAutoloaderRegistries()
{
return [
'src/Command' => 'ConcretePackageShoppingFeedManagerCommand',
];
}
This tells Concrete to autoload your command classes.
—
### ✅ 2. **Confirm Folder/File Structure**
Make sure your file is here:
packages/shopping_feed_manager/src/Command/SendEmailCommand.php
And it starts with:
namespace ConcretePackageShoppingFeedManagerCommand;
And that the class is named correctly:
class SendEmailCommand extends Command
—
### ✅ 3. **Check Command Registration in `on_start()`**
In your `controller.php`'s `on_start()` method, it should be exactly:
use ConcreteCoreConsoleApplication as ConsoleApplication;
use ConcretePackageShoppingFeedManagerCommandSendEmailCommand;
public function on_start()
{
if ($this->app->bound(ConsoleApplication::class)) {
$this->app->extend(ConsoleApplication::class, function ($cli) {
$cli->add($this->app->make(SendEmailCommand::class));
return $cli;
});
}
}
—
### ✅ 4. **Clear Cache & Reinstall Package**
After making the changes:
./concrete/bin/concrete c5:clear-cache
If still not working, **uninstall and reinstall the package**:
./concrete/bin/concrete c5:package-uninstall shopping_feed_manager
./concrete/bin/concrete c5:package-install shopping_feed_manager
—
### ✅ 5. **Test It Again**
Then try:
./concrete/bin/concrete list
You should see:
shopping-feed
shopping-feed:send-email Sends a test email from Shopping Feed Manager package.
Then test:
./concrete/bin/concrete shopping-feed:send-email
—
Let me know if you're still getting the error after these exact steps — I’ll help debug further with your actual file structure.
admin1@concrete-cms-web:/var/www/html$ ./concrete/bin/concrete shopping-feed:send-email
There are no commands defined in the "shopping-feed" namespace.
—
## ✅ 1. **Minimal Working Example (Reproduce + Fix)**
### ✅ Create this file:
**📄 `packages/shopping_feed_manager/src/Command/SendEmailCommand.php`**
<?php
namespace ConcretePackageShoppingFeedManagerCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
class SendEmailCommand extends Command
{
protected static $defaultName = 'shopping-feed:send-email';
protected function configure()
{
$this->setDescription('Test email command from Shopping Feed Manager.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('<info>Command is working ✅</info>');
return Command::SUCCESS;
}
}
—
### ✅ Update your package controller:
**📄 `packages/shopping_feed_manager/controller.php`**
<?php
namespace ConcretePackageShoppingFeedManager;
use ConcreteCorePackagePackage;
use ConcreteCoreConsoleApplication as ConsoleApplication;
use ConcretePackageShoppingFeedManagerCommandSendEmailCommand;
class Controller extends Package
{
protected $pkgHandle = 'shopping_feed_manager';
protected $appVersionRequired = '9.0.0';
protected $pkgVersion = '1.0.0';
public function getPackageName()
{
return t('Shopping Feed Manager');
}
public function getPackageDescription()
{
return t('Handles shopping feeds');
}
public function getPackageAutoloaderRegistries()
{
return [
'src/Command' => 'Concrete\Package\ShoppingFeedManager\Command',
];
}
public function on_start()
{
if ($this->app->bound(ConsoleApplication::class)) {
$this->app->extend(ConsoleApplication::class, function ($cli) {
$cli->add($this->app->make(SendEmailCommand::class));
return $cli;
});
}
}
}
—
### ✅ Clear cache and **reinstall your package**:
./concrete/bin/concrete c5:clear-cache
./concrete/bin/concrete c5:package-uninstall shopping_feed_manager
./concrete/bin/concrete c5:package-install shopping_feed_manager
—
### ✅ Confirm command is now available:
./concrete/bin/concrete list | grep shopping
If you see:
shopping-feed:send-email Test email command from Shopping Feed Manager.
Then try:
./concrete/bin/concrete shopping-feed:send-email
✅ It should print: `Command is working ✅`
—
### ⛔ If Still Fails
Please share the output of this:
tree packages/shopping_feed_manager
And confirm:
– PHP version (`php -v`)
– Concrete CMS version (`./concrete/bin/concrete -V`)
I’ll debug it directly from your structure.
admin1@concrete-cms-web:/var/www/html$
—
## 🔍 Let’s Break It Down Step by Step
### ✅ 1. Confirm Folder and File Structure
Run this from your root (`/var/www/html`) and paste the output:
tree packages/shopping_feed_manager
I need to confirm that:
– `controller.php` is inside `packages/shopping_feed_manager/`
– `SendEmailCommand.php` is inside `packages/shopping_feed_manager/src/Command/`
—
### ✅ 2. Confirm Namespace and Class Match File Path
Open `SendEmailCommand.php` and check it starts like this **exactly**:
<?php
namespace ConcretePackageShoppingFeedManagerCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
class SendEmailCommand extends Command
{
protected static $defaultName = 'shopping-feed:send-email';
protected function configure()
{
$this->setDescription('Test email command from Shopping Feed Manager.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('<info>Command is working ✅</info>');
return Command::SUCCESS;
}
}
—
### ✅ 3. Hardcode Class Path in Controller for Debugging
Open your `controller.php` and **temporarily replace** the command registration with this (to rule out autoloading problems):
public function on_start()
{
if ($this->app->bound(ConsoleApplication::class)) {
$this->app->extend(ConsoleApplication::class, function ($cli) {
require_once __DIR__ . '/src/Command/SendEmailCommand.php';
$cli->add(new ConcretePackageShoppingFeedManagerCommandSendEmailCommand());
return $cli;
});
}
}
This hardcoded `require_once` skips autoloading and directly loads the file — great for debugging.
—
### ✅ 4. Clear Cache & Reinstall
./concrete/bin/concrete c5:clear-cache
./concrete/bin/concrete c5:package-uninstall shopping_feed_manager
./concrete/bin/concrete c5:package-install shopping_feed_manager
Then test again:
./concrete/bin/concrete list | grep shopping
./concrete/bin/concrete shopping-feed:send-email
—
### ✅ 5. Bonus: Check PHP Errors
If it’s **still not working**, run:
php -d display_errors=1 ./concrete/bin/concrete list
This may show hidden PHP errors (e.g., class not found, parse error, etc.).
—
Let me know:
– Output of `tree packages/shopping_feed_manager`
– Whether the hardcoded `require_once` works
Once it works, we’ll switch back to clean autoload-based registration.
packages/shopping_feed_manager
├── controller.php
├── controllers
│ ├── api
│ │ └── feed_api.php
│ ├── merchant_feed.php
│ └── single_page
│ └── dashboard
│ ├── shopping_feed
│ │ ├── add.php
│ │ ├── edit.php
│ │ ├── settings.php
│ │ └── tracklog.php
│ └── shopping_feed.php
├── css
│ └── admin-feed.css
├── icon.png
├── js
│ └── admin-feed.js
├── single_pages
│ └── dashboard
│ └── shopping_feed
│ ├── add.php
│ ├── edit.php
│ ├── settings.php
│ ├── tracklog.php
│ └── view.php
└── src
└── Command
├── GenerateFeedCommand.php
└── SendEmailCommand.php
13 directories, 18 files
admin1@concrete-cms-web:/v
– **Autoloader not picking up your `src/Command/` classes**, or
– The **command not being correctly registered**.
Let’s get this working immediately by using a **safe fallback**: hardcoding the `require_once` to bypass autoloading.
—
## ✅ Fix: Use `require_once` in `on_start()` Temporarily
Edit `packages/shopping_feed_manager/controller.php` and update your `on_start()` like this:
<?php
namespace ConcretePackageShoppingFeedManager;
use ConcreteCorePackagePackage;
use ConcreteCoreConsoleApplication as ConsoleApplication;
class Controller extends Package
{
protected $pkgHandle = 'shopping_feed_manager';
protected $appVersionRequired = '9.0.0';
protected $pkgVersion = '1.0.0';
public function getPackageName()
{
return t('Shopping Feed Manager');
}
public function getPackageDescription()
{
return t('Handles shopping feeds');
}
public function on_start()
{
if ($this->app->bound(ConsoleApplication::class)) {
$this->app->extend(ConsoleApplication::class, function ($cli) {
// Directly include the command file to avoid autoload issues
require_once __DIR__ . '/src/Command/SendEmailCommand.php';
$cli->add(new ConcretePackageShoppingFeedManagerCommandSendEmailCommand());
return $cli;
});
}
}
}
> ✅ This **manually loads** the command file and registers it, ignoring autoloader issues.
—
## 🔃 Then: Refresh and Test
### Run:
./concrete/bin/concrete c5:clear-cache
./concrete/bin/concrete list | grep shopping
If successful, you’ll see:
shopping-feed:send-email Test email command from Shopping Feed Manager.
Then test the command:
./concrete/bin/concrete shopping-feed:send-email
You should get:
Command is working ✅
—
## ✅ Next Step: Fix Autoloader Permanently (Optional)
Once you confirm the command is working, we can clean up and properly autoload like this:
In `controller.php`:
public function getPackageAutoloaderRegistries()
{
return [
'src/Command' => 'Concrete\Package\ShoppingFeedManager\Command',
];
}
And **remove `require_once`** once confirmed autoloading works.
—
Let me know the output of the command after this. If this works, we’ll finalize and clean it up.
admin1@concrete-cms-web:/var/www/html$
> 🔴 The `on_start()` method is **not being executed at all**, meaning the package might not be loaded properly by Concrete CMS.
Let’s **verify and fix that now.**
—
## ✅ Step-by-Step Final Fix Plan
### ✅ 1. **Confirm Package Is Installed and Active**
Run this:
./concrete/bin/concrete c5:package-list
Look for:
shopping_feed_manager (Shopping Feed Manager)
If it's not listed or shows as *uninstalled*, install it:
./concrete/bin/concrete c5:package-install shopping_feed_manager
If already installed, **uninstall and reinstall** to trigger `on_start()`:
./concrete/bin/concrete c5:package-uninstall shopping_feed_manager
./concrete/bin/concrete c5:package-install shopping_feed_manager
—
### ✅ 2. **Put Debug Output in `on_start()`**
To confirm `on_start()` is even running, modify `controller.php` like this:
public function on_start()
{
file_put_contents('/tmp/c5_onstart.log', "on_start() hit at " . date('Y-m-d H:i:s') . "n", FILE_APPEND);
if ($this->app->bound(ConsoleApplication::class)) {
$this->app->extend(ConsoleApplication::class, function ($cli) {
require_once __DIR__ . '/src/Command/SendEmailCommand.php';
$cli->add(new ConcretePackageShoppingFeedManagerCommandSendEmailCommand());
return $cli;
});
}
}
Then run:
./concrete/bin/concrete c5:clear-cache
./concrete/bin/concrete
Then check if log file was created:
cat /tmp/c5_onstart.log
—
### 🔁 If `/tmp/c5_onstart.log` is **not created**:
That confirms Concrete CMS is **not loading your package**.
**Fix:** Rename your package folder to use **lowercase with underscores only**.
mv packages/shopping_feed_manager packages/shopping_feed_manager_fixed
Update the class in `controller.php`:
namespace ConcretePackageShoppingFeedManagerFixed;
class Controller extends Package
{
protected $pkgHandle = 'shopping_feed_manager_fixed';
...
Then reinstall:
./concrete/bin/concrete c5:package-uninstall shopping_feed_manager
./concrete/bin/concrete c5:package-install shopping_feed_manager_fixed
Then re-run:
./concrete/bin/concrete list | grep shopping
—
### ✅ Once It Appears
You’ll see:
shopping-feed:send-email Test email command from Shopping Feed Manager.
Let me know what happens with:
– `package-list`
– `/tmp/c5_onstart.log`
– and `list | grep shopping`
We’ll finalize from there.
admin1@concrete-cms-web:/var/www/html$
– Added the `require_once` in `on_start()`
– Added `file_put_contents('/tmp/c5_onstart.log', …)` for debugging
– Cleared cache and reinstalled the package
But **`/tmp/c5_onstart.log` was NOT created**, then we can now **conclude:**
> 🔥 **Concrete CMS is not loading your package at all**, or it’s failing silently.
Let’s fix this once and for all.
—
## ✅ FINAL FIX PLAN
### ✅ 1. Rename the Package Folder (100% Critical in Concrete CMS)
Concrete CMS is **case-sensitive and naming-strict**.
Please rename the folder to match the expected package handle exactly.
Run this:
mv packages/shopping_feed_manager packages/shopping_feed_manager_fixed
Now update the `controller.php`:
<?php
namespace ConcretePackageShoppingFeedManagerFixed;
use ConcreteCorePackagePackage;
use ConcreteCoreConsoleApplication as ConsoleApplication;
class Controller extends Package
{
protected $pkgHandle = 'shopping_feed_manager_fixed';
protected $appVersionRequired = '9.0.0';
protected $pkgVersion = '1.0.0';
public function getPackageName()
{
return t('Shopping Feed Manager');
}
public function getPackageDescription()
{
return t('Handles shopping feeds');
}
public function on_start()
{
// Debug log to confirm this is working
file_put_contents('/tmp/c5_onstart.log', "on_start() hit at " . date('Y-m-d H:i:s') . "n", FILE_APPEND);
if ($this->app->bound(ConsoleApplication::class)) {
$this->app->extend(ConsoleApplication::class, function ($cli) {
require_once __DIR__ . '/src/Command/SendEmailCommand.php';
$cli->add(new ConcretePackageShoppingFeedManagerFixedCommandSendEmailCommand());
return $cli;
});
}
}
}
Also update the namespace in your command:
// src/Command/SendEmailCommand.php
namespace ConcretePackageShoppingFeedManagerFixedCommand;
—
### ✅ 2. Reinstall Package Completely
./concrete/bin/concrete c5:clear-cache
./concrete/bin/concrete c5:package-uninstall shopping_feed_manager_fixed || true
./concrete/bin/concrete c5:package-install shopping_feed_manager_fixed
—
### ✅ 3. Test If It Works
cat /tmp/c5_onstart.log
./concrete/bin/concrete list | grep shopping
./concrete/bin/concrete shopping-feed:send-email
You should now get:
Command is working ✅
—
If this still fails after following these exact steps, I will give you a **minimal sample working package zip** to upload and try instead. But this should fix it.
Please confirm results of:
– `cat /tmp/c5_onstart.log`
– `./concrete/bin/concrete list | grep shopping`
Still not showing custom command
– ✅ Registers a CLI command successfully
– ✅ Outputs proof via log
– ✅ Uses no autoload complications
—
## 🧪 ✅ Step-by-Step: Minimal Working Concrete CMS CLI Command Package
### 🧱 1. Create Folder
mkdir -p packages/test_cli/src/Command
—
### 🧾 2. Create the Controller
**📄 `packages/test_cli/controller.php`**
<?php
namespace ConcretePackageTestCli;
use ConcreteCorePackagePackage;
use ConcreteCoreConsoleApplication as ConsoleApplication;
class Controller extends Package
{
protected $pkgHandle = 'test_cli';
protected $appVersionRequired = '9.0.0';
protected $pkgVersion = '1.0.0';
public function getPackageName()
{
return t('Test CLI');
}
public function getPackageDescription()
{
return t('Testing custom CLI command');
}
public function on_start()
{
file_put_contents('/tmp/testcli_onstart.log', "✔ on_start() calledn", FILE_APPEND);
if ($this->app->bound(ConsoleApplication::class)) {
$this->app->extend(ConsoleApplication::class, function ($cli) {
require_once __DIR__ . '/src/Command/TestCommand.php';
$cli->add(new ConcretePackageTestCliCommandTestCommand());
return $cli;
});
}
}
}
—
### 🧾 3. Create the CLI Command
**📄 `packages/test_cli/src/Command/TestCommand.php`**
<?php
namespace ConcretePackageTestCliCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
class TestCommand extends Command
{
protected static $defaultName = 'test:cli';
protected function configure()
{
$this->setDescription('Test CLI command');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('<info>✅ TestCommand is working!</info>');
return Command::SUCCESS;
}
}
—
### 🧪 4. Install and Run
./concrete/bin/concrete c5:clear-cache
./concrete/bin/concrete c5:package-install test_cli
Then test:
cat /tmp/testcli_onstart.log
./concrete/bin/concrete list | grep test
./concrete/bin/concrete test:cli
—
### ✅ Expected Output
✔ on_start() called
test:cli Test CLI command
✅ TestCommand is working!
—
### ✅ Next
Once this works:
– Go back to your `shopping_feed_manager`
– Copy this working structure
– Replace `TestCommand` with your `SendEmailCommand`
—
Would you like me to generate a `.zip` of this working package so you can upload and test directly?