Magento 2 is a robust platform that allows for extensive customization. A common requirement for many store owners is to send automated email notifications for orders that have been in “Pending” status for over 30 days. In this blog post, we’ll walk through the process of creating a custom module to achieve this functionality.
Key Features of the Module
- Enable or disable the reminder feature via admin configuration.
- Specify multiple recipient email addresses.
- Use a custom email template to send order details.
- Set up a cron job to trigger email notifications for pending orders.
Module Registration:
Create a registration.php file and a module.xml file to define and register the module:app/code/Klizer/OrderEmailReminder/registration.php
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Klizer_OrderEmailReminder',
__DIR__
);
app/code/Klizer/OrderEmailReminder/etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Klizer_OrderEmailReminder" setup_version="1.0.0">
</module>
</config>
Admin Configuration:
Define system configuration fields using system.xml to allow admins to enable or disable the feature and specify recipient email addresses:
app/code/Klizer/OrderEmailReminder/etc/adminhtml/system.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
<system>
<tab id="klizer" translate="label" sortOrder="400">
<label>Klizer</label>
</tab>
<section id="order_email" translate="label" type="text" sortOrder="1" showInDefault="1"
showInWebsite="1" showInStore="1">
<label>Order Email</label>
<tab>klizer</tab>
<resource>Klizer_OrderEmailReminder::orderemail_resource</resource>
<group id="orderemail_gorup_reminder" translate="label" type="text" sortOrder="1" showInDefault="1"
showInWebsite="1" showInStore="1">
<label>Order Email Reminder for 30 Days</label>
<field id="orderemail_reminder_status" translate="label" type="select" sortOrder="1" showInDefault="1"
showInWebsite="1" showInStore="1">
<label>Enabled</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<field id="orderemail_reminder_send_to" showInDefault="1" showInWebsite="1" showInStore="1" translate="label" sortOrder="10" type="textarea">
<label>Order Email Reminder Send to (Comma-Separated)</label>
<comment>Enter multiple email addresses separated by commas</comment>
</field>
</group>
</section>
</system>
</config>
Helper Class:
The helper class fetches configuration values.
app/code/Klizer/OrderEmailReminder/Helper/Data.php
<?php
namespace Klizer\OrderEmail\Helper;
use \Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
class Data extends AbstractHelper
{
/**
* @var \Magento\Store\Model\StoreManagerInterface
*/
protected $_storeManager;
/**
* @var \Magento\Framework\App\Config\ScopeConfigInterface
*/
protected $scopeConfig;
/**
* Data constructor.
* @param Context $context
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
*/
public function __construct(
Context $context,
\Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig)
{
$this->_storeManager = $storeManager;
$this->scopeConfig = $scopeConfig;
parent::__construct($context);
}
/**
* Get the order email status for a specific store code.
*
* @return string|null
*/
public function getOrderEmailStatus()
{
return $this->scopeConfig->getValue(
'order_email/orderemail_gorup_reminder/orderemail_reminder_status',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
);
}
/**
* Get the order email for a specific store code.
*
* @return string|null
*/
public function getOrderEmail()
{
$emails = $this->scopeConfig->getValue( 'order_email/orderemail_gorup_reminder/orderemail_reminder_send_to',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
);
if ($emails) {
return array_map('trim', explode(',', $emails)); // Split by comma and trim spaces
}
return [];
}
}
Cron:
Define the cron job to run periodically and trigger email notifications.
app/code/Klizer/OrderEmailReminder/etc/cron_groups.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/cron_groups.xsd">
<group id="order_email_reminder">
<schedule_generate_every>1</schedule_generate_every>
<schedule_ahead_for>4</schedule_ahead_for>
<schedule_lifetime>2</schedule_lifetime>
<history_cleanup_every>10</history_cleanup_every>
<history_success_lifetime>60</history_success_lifetime>
<history_failure_lifetime>600</history_failure_lifetime>
<use_separate_process>1</use_separate_process>
</group>
</config>
app/code/Klizer/OrderEmailReminder/etc/crontab.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
<group id="order_email_reminder">
<job name="order_reminder_email_send" instance="Klizer\OrderEmailReminder\Cron\OrderEmailReminder" method="execute">
<schedule>* * * * *</schedule>
</job>
</group>
</config>
Email Template:
Declare the email template in email_templates.xml:
app/code/Klizer/OrderEmail/etc/email_templates.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../Email/etc/email_templates.xsd">
<template id="send_custom_order_email_reminder" label="Contact Form" file="order_reminder.html" type="html"
module="Klizer_OrderEmailReminder" area="frontend"/>
</config>
Define the email template in order_reminder.html:
app/code/Klizer/OrderEmailReminder/view/frontend/email/order_reminder.html
<!--
/**
* Copyright (c) Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<!--@subject {{trans "30 Days Reminder for Order #%increment_id" increment_id=$order.increment_id}} @-->
<!--@vars {
"var formattedBillingAddress|raw":"Billing Address",
"var order_data.email_customer_note|escape|nl2br":"Email Order Note",
"var order.increment_id":"Order Id",
"layout handle=\"sales_email_order_items\" order=$order area=\"frontend\"":"Order Items Grid",
"var payment_html|raw":"Payment Details",
"var formattedShippingAddress|raw":"Shipping Address",
"var order.shipping_description":"Shipping Description",
"var shipping_msg":"Shipping message",
"var created_at_formatted":"Order Created At (datetime)",
"var store.frontend_name":"Store Frontend Name",
"var store_phone":"Store Phone",
"var store_email":"Store Email",
"var store_hours":"Store Hours",
"var this.getUrl($store,'customer/account/',[_nosid:1])":"Customer Account URL",
"var order_data.is_not_virtual":"Order Type",
"var order":"Order",
"var order_id": "Order DB Id",
"var company_name": "Company Name",
"var order_data.customer_name":"Customer Name"
} @-->
{{template config_path="design/email/header_template"}}
<table>
<tr class="email-intro">
<td>
<p>{{trans "Reminder: Order #%increment_id was initiated 30 days ago." increment_id=$order.increment_id |raw}}</p>
<p class="greeting">{{trans "%customer_name," customer_name=$order_data.customer_name}}</p>
<p>
{{trans "Thank you for your order from %store_name." store_name=$store.frontend_name}}
{{trans "Once your package ships we will send you a tracking number."}}
{{trans 'You can check the status of your order by <a href="%account_url">logging into your account</a>.' account_url=$this.getUrl($store,'customer/account/',[_nosid:1]) |raw}}
</p>
<p>
{{trans 'If you have questions about your order, you can email us at <a href="mailto:%store_email">%store_email</a>' store_email=$store_email |raw}}{{depend store_phone}} {{trans 'or call us at <a href="tel:%store_phone">%store_phone</a>' store_phone=$store_phone |raw}}{{/depend}}.
{{depend store_hours}}
{{trans 'Our hours are <span class="no-link">%store_hours</span>.' store_hours=$store_hours |raw}}
{{/depend}}
</p>
</td>
</tr>
<tr class="email-summary">
<td>
<h1>{{trans 'Your Order <span class="no-link">#%increment_id</span>' increment_id=$order.increment_id |raw}}</h1>
<p>{{trans 'Placed on : <span class="no-link">%created_at</span>' created_at=$created_at_formatted |raw}}</p>
</td>
</tr>
<tr class="email-information">
<td>
{{depend order_data.email_customer_note}}
<table class="message-info">
<tr>
<td>
{{var order_data.email_customer_note|escape|nl2br}}
</td>
</tr>
</table>
{{/depend}}
<table class="order-details">
<tr>
<td class="address-details">
<h3>{{trans "Billing Info"}}</h3>
<p>{{var formattedBillingAddress|raw}}</p>
</td>
{{depend order_data.is_not_virtual}}
<td class="address-details">
<h3>{{trans "Shipping Info"}}</h3>
<p>{{var formattedShippingAddress|raw}}</p>
</td>
{{/depend}}
</tr>
<tr>
<td class="method-info">
<h3>{{trans "Payment Method"}}</h3>
{{var payment_html|raw}}
</td>
{{depend order_data.is_not_virtual}}
<td class="method-info">
<h3>{{trans "Shipping Method"}}</h3>
<p>{{var order.shipping_description}}</p>
{{if shipping_msg}}
<p>{{var shipping_msg}}</p>
{{/if}}
</td>
{{/depend}}
</tr>
</table>
{{layout handle="sales_email_order_items" order_id=$order_id area="frontend"}}
</td>
</tr>
</table>
{{template config_path="design/email/footer_template"}}
Cron Class :
The cron class OrderEmailReminder is responsible for executing the scheduled task that checks for pending orders older than 30 days and triggers reminder emails accordingly.
app/code/Klizer/OrderEmail/Observer/OrderEmail.php
<?php
namespace Klizer\OrderEmail\Cron;
use Magento\Framework\Mail\Template\TransportBuilder;
use Magento\Framework\Translate\Inline\StateInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Payment\Helper\Data as PaymentHelper;
use Magento\Sales\Model\Order\Address\Renderer as AddressRenderer;
use Psr\Log\LoggerInterface;
use Magento\Company\Api\CompanyRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Klizer\OrderEmail\Helper\Data as OrderEmailHelper;
class OrderEmailReminder
{
/**
* @var TransportBuilder
*/
protected $transportBuilder;
/**
* @var StoreManagerInterface
*/
protected $storeManager;
/**
* @var StateInterface
*/
protected $inlineTranslation;
/**
* @var ScopeConfigInterface
*/
protected $scopeConfig;
/**
* @var OrderRepositoryInterface
*/
protected $orderRepository;
/**
* @var PaymentHelper
*/
protected $paymentHelper;
/**
* @var AddressRenderer
*/
protected $addressRenderer;
/**
* @var SearchCriteriaBuilder
*/
protected $searchCriteriaBuilder;
/**
* @var OrderEmailHelper
*/
protected $orderEmailHelper;
/**
* @var LoggerInterface
*/
private $logger;
/**
* OrderEmailReminder constructor.
* @param TransportBuilder $transportBuilder
* @param StoreManagerInterface $storeManager
* @param StateInterface $state
* @param ScopeConfigInterface $scopeConfig
* @param OrderRepositoryInterface $orderRepository
* @param PaymentHelper $paymentHelper
* @param AddressRenderer $addressRenderer
* @param SearchCriteriaBuilder $searchCriteriaBuilder
* @param OrderEmailHelper $orderEmailHelper
* @param LoggerInterface $logger
*/
public function __construct(
TransportBuilder $transportBuilder,
StoreManagerInterface $storeManager,
StateInterface $state,
ScopeConfigInterface $scopeConfig,
OrderRepositoryInterface $orderRepository,
PaymentHelper $paymentHelper,
AddressRenderer $addressRenderer,
SearchCriteriaBuilder $searchCriteriaBuilder,
OrderEmailHelper $orderEmailHelper,
LoggerInterface $logger
) {
$this->transportBuilder = $transportBuilder;
$this->storeManager = $storeManager;
$this->inlineTranslation = $state;
$this->scopeConfig = $scopeConfig;
$this->orderRepository = $orderRepository;
$this->paymentHelper = $paymentHelper;
$this->addressRenderer = $addressRenderer;
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
$this->orderEmailHelper = $orderEmailHelper;
$this->logger = $logger;
}
public function execute()
{
$orderEmailstatus=$this->orderEmailHelper->getOrderEmail();
if ($orderEmailstatus == 1) { // Check if the status is enabled
try {
$storeId=1; //set store id
$date = new \DateTimeImmutable();
$thirtyDaysAgo = $date->modify('-30 days')->format('Y-m-d');
// Construct start and end of the day
$thirtyDaysAgoStart = $thirtyDaysAgo . ' 00:00:00';
$thirtyDaysAgoEnd = $thirtyDaysAgo . ' 23:59:59';
$searchCriteria = $this->searchCriteriaBuilder
->addFilter('store_id', $storeId, 'eq')
->addFilter('status', 'pending', 'eq')
->addFilter('created_at', $thirtyDaysAgoStart, 'gteq')
->addFilter('created_at', $thirtyDaysAgoEnd, 'lteq')
->create();
$orderList = $this->orderRepository->getList($searchCriteria)->getItems();
$templateVars=[];
if(count($orderList)>0){
foreach ($orderList as $orders) {
$orderId = $orders->getEntityId();
$order = $this->orderRepository->get($orderId);
$billingAddress = $order->getBillingAddress();
$shippingAddress = $order->getShippingAddress();
// Build email template variables
$templateVars = [
'order' => $order,
'order_id' => $order->getId(),
'billing' => $billingAddress ? $billingAddress->getData() : [],
'shipping' => $shippingAddress ? $shippingAddress->getData() : [],
'store' => $order->getStore(),
'increment_id' => $order->getIncrementId(),
'created_at_formatted' => $order->getCreatedAtFormatted(2),
'formattedShippingAddress' => $this->getFormattedShippingAddress($order),
'formattedBillingAddress' => $this->getFormattedBillingAddress($order),
'payment_html' => $order->getPayment() ? $this->getPaymentHtml($order) : '',
'customer_email' => $order->getCustomerEmail(),
'customer_name' => $order->getCustomerFirstname() . ' ' . $order->getCustomerLastname(),
'order_data' => [
'customer_name' => $order->getCustomerName(),
'is_not_virtual' => $order->getIsNotVirtual(),
'email_customer_note' => $order->getEmailCustomerNote(),
'frontend_status_label' => $order->getFrontendStatusLabel()
]
];
$this->inlineTranslation->suspend();
$templateOptions = [
'area' => \Magento\Framework\App\Area::AREA_FRONTEND,
'store' => $this->storeManager->getStore()->getId(),
];
$orderEmailAddresses = $this->orderEmailHelper->getOrderEmail();
if (!empty($orderEmailAddresses)) {
foreach ($orderEmailAddresses as $email) {
try {
$transport = $this->transportBuilder ->setTemplateIdentifier('send_custom_order_email_reminder')
->setTemplateOptions($templateOptions)
->setTemplateVars($templateVars)
->setFromByScope($this->getStoreConfig('sales_email/order/identity'))
->addTo($email)
->getTransport();
$transport->sendMessage();
} catch (\Exception $e) {
$this->logger->error('Mail Not Sent to ' . $email . ': ' . $e->getMessage());
}
}
}
$this->inlineTranslation->resume();
}
}else{
return; }
} catch (\Exception $e) {
$this->logger->critical('Mail Not Sent: ' . $e->getMessage());
}
}else{
return; // Skip email sending if status is not enabled
}
}
/**
* @param string $storeConfigName
* @param null $storeId
* @return mixed
*/
protected function getStoreConfig(string $storeConfigName, $storeId = null)
{
return $this->scopeConfig->getValue(
$storeConfigName,
\Magento\Store\Model\ScopeInterface::SCOPE_STORE,
$storeId
);
}
/**
* @param $order
* @return string|null
*/
protected function getFormattedBillingAddress($order)
{
return $this->addressRenderer->format($order->getBillingAddress(), 'html');
}
/**
* @param $order
* @return string|null
*/
protected function getFormattedShippingAddress($order)
{
return $this->addressRenderer->format($order->getShippingAddress(), 'html');
}
/**
* @param \Magento\Sales\Model\Order $order
* @return string
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
protected function getPaymentHtml(\Magento\Sales\Model\Order $order)
{
return $this->paymentHelper->getInfoBlockHtml(
$order->getPayment(),
$this->storeManager->getStore()->getId()
);
}
}
ON THIS PAGE
Steps To Configure the New Order Email Notifications in Magento Admin Panel
Follow the steps below to configure the New order email Notification settings in the Magento Admin panel.
Step 1:
- Navigate to Stores → Configuration.
- Go to Klizer → Order Email → Order Email Reminder.
Step 2:
- Enable the Order Email by setting it to Yes.
Step 3:
- Enter the email address(es) to receive reminder order emails.
- If providing multiple email addresses, separate them with commas.
Step 4:
- Click Save button to save the configuration
- Run or Setup cron scheduler.
Conclusion
By following these steps, you can successfully set up an automated email notification system for pending orders in Magento 2. This ensures that the store administrators are informed about pending orders, improving the overall order management process.
Reach out to Klizer, if you have any questions or need an assistant for Magento 2 development services. Contact us to learn how our Magento experts can help you implement advanced features to upgrade your ecommerce store.