How You Created a Custom Magento 2 Module to Add Tracking Info to Invoice Email

1. Module Creation Structure

Create directory structure:

app/code/JJ/TrackEmail/
├── etc
│   └── events.xml
├── Observer
│   └── AddTrackingDataToInvoiceEmail.php
├── registration.php
└── etc/module.xml

2. registration.php

Registers the module with Magento:

<?php
use MagentoFrameworkComponentComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'JJ_TrackEmail',
    __DIR__
);

3. module.xml

Declares the module and its version:

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="JJ_TrackEmail" setup_version="1.0.0"/>
</config>

4. etc/events.xml

This hooks into Magento’s email sending process. You used the email_invoice_set_template_vars_before event:

<?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="email_invoice_set_template_vars_before">
        <observer name="jj_add_tracking_data_to_invoice_email" instance="JJTrackEmailObserverAddTrackingDataToInvoiceEmail"/>
    </event>
</config>

5. Observer Class – AddTrackingDataToInvoiceEmail.php

This is your main logic to extract and inject tracking number into invoice email template:

<?php

namespace JJTrackEmailObserver;

use MagentoFrameworkEventObserver;
use MagentoFrameworkEventObserverInterface;

class AddTrackingDataToInvoiceEmail implements ObserverInterface
{
    public function execute(Observer $observer)
    {
        $transport = $observer->getData('transport');
        $order = $transport['order'];

        if (!isset($transport['invoice'])) {
            return;
        }

        $invoice = $transport['invoice'];
        $trackingRows = [];

        foreach ($order->getShipmentsCollection() as $shipment) {
            foreach ($shipment->getTracks() as $track) {
                $trackNumber = htmlspecialchars((string)($track->getTrackNumber() ?? ''));
                $trackLink = '<a href="https://www.fedex.com/fedextrack/?trknbr=' . $trackNumber . '" target="_blank">' . $trackNumber . '</a>';
                
                $trackingRows[] = '<tr><td>' . $trackLink . '</td></tr>';
            }
        }

        if (!empty($trackingRows)) {
            $trackingHtml = implode("n", $trackingRows);
            $invoice->setData('tracking_info', $trackingHtml);
            $order->setData('tracking_info', $trackingHtml); // optional
        }

        $transport['invoice'] = $invoice;
        $observer->setData('transport', $transport);
    }
}

6. Update Your Email Template

Now update your custom email template (sales_email_invoice_template) to include this:

<!-- Somewhere in your invoice email template -->
<tr>
    <td class="label">
        <strong>Tracking Info:</strong>
    </td>
    <td class="value">
        {{var invoice.tracking_info|raw}}
    </td>
</tr>

7. Enable & Test

Run:

bash

Copy

Edit
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento cache:flush

Then send a test invoice email and check the result.

Benefits

  • Adds clickable tracking number in emails.
  • Works for multiple shipments per order.
  • Safe output using htmlspecialchars.

Would you like a sample .zip module folder structure or instructions to override default Magento email templates from your theme as well?

Leave a comment

Your email address will not be published. Required fields are marked *