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 customers disappointed. But what if you could turn that frustration into an opportunity? A custom "waitlist" feature lets customers sign up to be notified when an out-of-stock product is back in stock, keeping them engaged and increasing future sales.

En esta guía,'ll walk through building a waitlist module desde cero in Magento 2, integrating email notifications, optimizing UX for sign-ups, and leveraging waitlist data for marketing. Whether you're a developer or a store owner looking to implement this feature, we’ll keep things clear and actionable.

Why a Waitlist Feature Matters

Antes de 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 customers connected to your brand even when inventory runs low.

Guía paso a paso to Creating a Waitlist Module

We’ll build a custom module named Magefine_Waitlist. Here’s cómo structure it:

1. Set Up the Module Structure

Primero, create the basic module files 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 página de producto 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 controller 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]);
    }
}

Integrating Email Notifications

Ahora, let’s notify customers when the product is back in stock. We’ll use Magento’s observer 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 content:

<!--@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>

Mejores prácticas for UX Design

A well-designed waitlist feature debería ser:

  • Visible but not intrusive: Place the waitlist form near the "Out of Stock" message.
  • Minimal fields: Only ask for email (autofill for logged-in customers).
  • Clear value proposition: Explain benefits 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 customers a 10% discount code when the product restocks.
  • Demand forecasting: Analyze which products have the most waitlist signups to prioritize inventory.
  • Personalized campaigns: Segment customers by waitlisted products for targeted emails.

Custom vs. Third-Party Extensions

Mientras building a custom solution le da full control, de terceros extensions like Magefine’s Waitlist Pro offer:

  • Faster implementation: No coding required.
  • Advanced features: Auto-import to cart, SMS notifications, etc.
  • Ongoing support: Regular updates and bug fixes.

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

Reflexiones finales

A waitlist feature turns stockouts into opportunities. By following this guide, you can implement a basic version or extend it with advanced functionality like SMS alerts or integration with your CRM. Whether you build it yourself or use an extension, the key is making it seamless for customers 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!