How to Build a Custom "Waitlist" Feature for Out-of-Stock Products in Magento 2

How to Build a Custom "Waitlist" Feature for Out-of-Stock Products in Magento 2

Running an eCommerce store means dealing with stock fluctuations—some products sell out faster than expected, leaving potential clients disappointed. But what if you could turn that frustration into an opportunity? A custom "waitlist" fonctionnalité lets clients sign up to be notified when an out-of-stock product is back in stock, keeping them engaged and increasing future sales.

Dans ce guide, nous'll walk through building a waitlist module à partir de zéro in Magento 2, integnote e-mail notifications, optimizing UX for sign-ups, and leveraging waitlist data for marketing. Whether you're a développeur or a propriétaire de boutique looking to implement this fonctionnalité, we’ll keep things clear and actionable.

Why a Waitlist Feature Matters

Avant diving into the code, let’s quickly cover why a waitlist is worth the effort:

  • Reduce lost sales: Customers who sign up are more likely to purchase when the product restocks.
  • Gather demand insights: Track which products have high waitlist demand to prioritize restocking.
  • Boost engagement: Keep clients connected to your brand even when inventaire runs low.

Guide étape par étape to Creating a Waitlist Module

We’ll build a custom module named Magefine_Waitlist. Here’s comment structure it:

1. Set Up the Module Structure

Premièrement, create the basic module fichiers in app/code/Magefine/Waitlist:

app/code/Magefine/Waitlist/
├── etc/
│   ├── module.xml
│   ├── di.xml
│   └── events.xml
├── Controller/
│   └── Index/
│       └── Add.php
├── Model/
│   ├── Waitlist.php
│   └── ResourceModel/
│       ├── Waitlist.php
│       └── Waitlist/
│           └── Collection.php
├── view/
│   └── frontend/
│       ├── layout/
│       │   └── waitlist_index_add.xml
│       └── templates/
│           └── form.phtml
└── registration.php

2. Define the Module

In registration.php:

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Magefine_Waitlist',
    __DIR__
);

In 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="Magefine_Waitlist" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Catalog"/>
            <module name="Magento_Customer"/>
        </sequence>
    </module>
</config>

3. Create the Database Table

Set up Setup/InstallSchema.php to create a table for storing waitlist entries:

<?php
namespace Magefine\Waitlist\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;

class InstallSchema implements InstallSchemaInterface
{
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $installer = $setup;
        $installer->startSetup();

        $table = $installer->getConnection()->newTable(
            $installer->getTable('magefine_waitlist')
        )->addColumn(
            'waitlist_id',
            \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
            null,
            ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
            'Waitlist ID'
        )->addColumn(
            'product_id',
            \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
            null,
            ['unsigned' => true, 'nullable' => false],
            'Product ID'
        )->addColumn(
            'customer_id',
            \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
            null,
            ['unsigned' => true, 'nullable' => true],
            'Customer ID'
        )->addColumn(
            'email',
            \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
            255,
            ['nullable' => false],
            'Customer Email'
        )->addColumn(
            'created_at',
            \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
            null,
            ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT],
            'Creation Time'
        )->addForeignKey(
            $installer->getFkName(
                'magefine_waitlist',
                'product_id',
                'catalog_product_entity',
                'entity_id'
            ),
            'product_id',
            $installer->getTable('catalog_product_entity'),
            'entity_id',
            \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE
        )->addForeignKey(
            $installer->getFkName(
                'magefine_waitlist',
                'customer_id',
                'customer_entity',
                'entity_id'
            ),
            'customer_id',
            $installer->getTable('customer_entity'),
            'entity_id',
            \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE
        );

        $installer->getConnection()->createTable($table);
        $installer->endSetup();
    }
}

4. Build the Model and Resource Model

In Model/Waitlist.php:

<?php
namespace Magefine\Waitlist\Model;

use Magento\Framework\Model\AbstractModel;

class Waitlist extends AbstractModel
{
    protected function _construct()
    {
        $this->_init(\Magefine\Waitlist\Model\ResourceModel\Waitlist::class);
    }
}

In Model/ResourceModel/Waitlist.php:

<?php
namespace Magefine\Waitlist\Model\ResourceModel;

use Magento\Framework\Model\ResourceModel\Db\AbstractDb;

class Waitlist extends AbstractDb
{
    protected function _construct()
    {
        $this->_init('magefine_waitlist', 'waitlist_id');
    }
}

5. Create the Frontend Form

Add a form to the page produit when a product is out of stock. In view/frontend/templates/form.phtml:

<div class="waitlist-form">
    <form action="<?= $block->escapeUrl($block->getFormAction()) ?>" method="post">
        <input type="email" name="email" placeholder="your@email.com" required />
        <button type="submit">Notify Me When Available</button>
    </form>
</div>

6. Handle Form Submission

Create a contrôleur to process waitlist submissions (Controller/Index/Add.php):

<?php
namespace Magefine\Waitlist\Controller\Index;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magefine\Waitlist\Model\WaitlistFactory;

class Add extends Action
{
    protected $waitlistFactory;

    public function __construct(
        Context $context,
        WaitlistFactory $waitlistFactory
    ) {
        $this->waitlistFactory = $waitlistFactory;
        parent::__construct($context);
    }

    public function execute()
    {
        $productId = $this->getRequest()->getParam('product_id');
        $email = $this->getRequest()->getParam('email');

        $waitlist = $this->waitlistFactory->create();
        $waitlist->setProductId($productId);
        $waitlist->setEmail($email);
        $waitlist->save();

        $this->messageManager->addSuccessMessage(__('You’ve been added to the waitlist!'));
        $this->_redirect('catalog/product/view', ['id' => $productId]);
    }
}

Integnote Email Notifications

Maintenant, let’s notify clients when the product is back in stock. We’ll use Magento’s observateur system.

1. Set Up an Observer

In etc/events.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="catalog_product_save_after">
        <observer name="magefine_waitlist_notify" instance="Magefine\Waitlist\Observer\ProductSaveAfter"/>
    </event>
</config>

2. Create the Observer

In Observer/ProductSaveAfter.php:

<?php
namespace Magefine\Waitlist\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magefine\Waitlist\Model\ResourceModel\Waitlist\CollectionFactory;
use Magento\Framework\Mail\Template\TransportBuilder;
use Magento\Store\Model\StoreManagerInterface;

class ProductSaveAfter implements ObserverInterface
{
    protected $collectionFactory;
    protected $transportBuilder;
    protected $storeManager;

    public function __construct(
        CollectionFactory $collectionFactory,
        TransportBuilder $transportBuilder,
        StoreManagerInterface $storeManager
    ) {
        $this->collectionFactory = $collectionFactory;
        $this->transportBuilder = $transportBuilder;
        $this->storeManager = $storeManager;
    }

    public function execute(Observer $observer)
    {
        $product = $observer->getEvent()->getProduct();
        if ($product->getStockData()['is_in_stock']) {
            $waitlistEntries = $this->collectionFactory->create()
                ->addFieldToFilter('product_id', $product->getId());

            foreach ($waitlistEntries as $entry) {
                $this->sendNotificationEmail($entry->getEmail(), $product);
                $entry->delete();
            }
        }
    }

    protected function sendNotificationEmail($email, $product)
    {
        $storeId = $this->storeManager->getStore()->getId();
        $templateVars = [
            'product_name' => $product->getName(),
            'product_url' => $product->getProductUrl()
        ];

        $transport = $this->transportBuilder
            ->setTemplateIdentifier('waitlist_notification')
            ->setTemplateOptions(['area' => 'frontend', 'store' => $storeId])
            ->setTemplateVars($templateVars)
            ->setFrom(['email' => 'sales@example.com', 'name' => 'Store Support'])
            ->addTo($email)
            ->getTransport();

        $transport->sendMessage();
    }
}

3. Create the Email Template

In your Magento admin, go to Marketing > Email Templates and create a new template with this contenu:

<!--@subject Your Waitlisted Product is Back in Stock! @-->
<!--@vars {
"var product_name":"Product Name",
"var product_url":"Product URL"
} @-->

<p>Great news! The product you waitlisted is back in stock:</p>
<p><a href="{{var product_url}}">{{var product_name}}</a></p>
<p>Hurry—quantities may be limited!</p>

Bonnes pratiques for UX Design

A well-designed waitlist fonctionnalité devrait être:

  • Visible but not intrusive: Place the waitlist form near the "Out of Stock" message.
  • Minimal champs: Only ask for e-mail (autofill for logged-in clients).
  • Clear valeur proposition: Explain avantages like "First access when restocked."
  • Mobile-friendly: Ensure the form works well on all devices.

Leveraging Waitlist Data for Marketing

Your waitlist isn’t just about restock notifications—it’s a goldmine for marketing:

  • Exclusive offers: Send waitlist clients a 10% discount code when the product restocks.
  • Demand forecasting: Analyze which products have the most waitlist signups to prioritize inventaire.
  • Personalized campaigns: Segment clients by waitlisted products for targeted e-mails.

Custom vs. Third-Party Extensions

Tandis que building a custom solution vous donne full control, tiers extensions like Magefine’s Waitlist Pro offer:

  • Faster implémentation: No coding required.
  • Advanced fonctionnalités: Auto-import to cart, SMS notifications, etc.
  • Ongoing support: Regular updates and bug correctifes.

Cependant, if you need a lightweight, tailored solution, building your own module (as shown above) is a great option.

Réflexions finales

A waitlist fonctionnalité turns stockouts into opportunities. By following this guide, you can implement a basic version or extend it with advanced fonctionality like SMS alerts or integration with your CRM. Whether you build it yourself or use an extension, the clé is making it seamless for clients to stay engaged with your store.

Got questions or need help optimizing your Magento store? Reach out to our team—we’re happy to help!