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?