(Re)sending Magento order emails
01 Oct 2010, by Andrew Dubbeld
3 Comments · Posted in Magento

    I recently had a need to resend Magento order confirmation emails for orders placed within a particular time period. Magento provides an interface for resending these emails out of the box (navigate to Sales → Orders → Order and click 'Send Email' up in the top right), but this takes 15-20 seconds per order at best. If you have more than a few of these order emails to resend, this approach will quickly become tedious. Luckily, there's an alternative: it is possible to write a Magento script to do the job for you, which I'll explore in this blog post.

    If, for example, we wanted to resend the order emails for the first 100 orders (e.g. order increments 100000001 through 100000101), we would run something like this:

    <?php
    define("MAGE_BASE_DIR", "/var/www/magento");
    require_once MAGE_BASE_DIR . "/app/Mage.php";
    Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
    
    $orderIncrements = range(100000001, 100000101);
    
    foreach ($orderIncrements as $orderIncrement) {
        $order = Mage::getModel("sales/order")->loadByIncrementId($orderIncrement);
    
        if ($order->getId()) {
            try {
                $order->sendNewOrderEmail();
                echo "Order $orderIncrement successfully sentn";
            } catch (Exception $e) {
                echo $e->getMessage();
            }
        } else {
            echo "Order $orderIncrement not foundn";
        }
    
        sleep(2);
    }
    

    The first few lines allow our script access to the Magento environment, which makes it possible to use Mage::getModel() later on, and take action on the Magento orders:

    define("MAGE_BASE_DIR", "/var/www/magento");
    require_once MAGE_BASE_DIR . '/app/Mage.php';
    Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
    

    Note: You'll need to update MAGE_BASE_DIR to point to your own Magento directory, or the script will fail at Mage::app().

    Since we know that we just want the first 100 orders, ranging from 100000001 through 100000101, we write it this way. Alternatively, we could combine several sets of order increments like this:

    $orderIncrements = array_merge(
        range(100000001, 100000101),
        range(200000001, 200000101),
        range(300000001, 300000101)
    );
    

    Next, we simply iterate over the list of order increments and retrieve the corresponding order objects, one by one. We also want to print out an error message if the order cannot be found, so that we've got a record of exactly what happened after the script is finished.

    foreach ($orderIncrements as $orderIncrement) {
        $order = Mage::getModel('sales/order')->loadByIncrementId($orderIncrement);
        
        if ($order->getId()) {
            
        } else {
            echo "Order $orderIncrement not found\n";
        }
    }
    

    If this script was intended to be used more than once, or in an automated process, it would be more appropriate to log errors to file, but for this particular situation, simply echoing any errors is an acceptable option.

    To determine how to send the order emails, we look at the core modules. In the emailAction method of Mage_Adminhtml_Sales_OrderController, which is called when you click the "Send Email" button, the $order->sendNewOrderEmail() method is called to handle emails. Chances are high that we can do the same thing (though in other situations you may need to do some extra initialisation to put your script into the correct state):

    try {
        $order->sendNewOrderEmail();
        echo "Order $orderIncrement successfully sent\n";
    } catch (Exception $e) {
        echo $e->getMessage();
    }
    

    We also record any errors or successes, echoing either result, as we do when the order cannot be found.

    Finally, we want to avoid filling up the mail queue too quickly, so we'll introduce a short (2-second) delay between each iteration of the script:

    sleep(2);

    The finished script can be run with php -f filename.php, and might take something like 2-3 seconds per order to run (including the 2-second delay), and in the absence of any errors, it won't require any manual intervention. Quite an improvement.

    And that's just one possibility. You could also adjust this script slightly to send other order-related emails. For example, with the following changes, a shipment will be generated for each of the selected orders and shipment notices will be sent to the respective customers:

    <?php
    define("MAGE_BASE_DIR", "/var/www/magento");
    require_once MAGE_BASE_DIR . "/app/Mage.php";
    Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
    
    $orderIncrements = range(100000001, 100000101);
    
    foreach ($orderIncrements as $orderIncrement) {
        $order = Mage::getModel(
    

    In this instance the shipment will be created to contain all of the products, so this particular script can only be run against an order once. Details of the shipment will show up in the Shipments tab of the order screen.

    Finally, the next variation will generate invoices and send corresponding emails:

    <?php
    define("MAGE_BASE_DIR", "/var/www/magento");
    require_once MAGE_BASE_DIR . "/app/Mage.php";
    Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
    
    $orderIncrements = range(100000001, 100000101);
    
    foreach ($orderIncrements as $orderIncrement) {
        $order = Mage::getModel("sales/order")->loadByIncrementId($orderIncrement);
    
        if ($order->getId()) {
            try {
                $invoice = $order->prepareInvoice();
                $invoice->register();
    
                $invoice->setEmailSent(true);
    
                $invoice->getOrder()->setIsInProcess(true);
    
                $transactionSave = Mage::getModel("core/resource_transaction")
                    ->addObject($invoice)
                    ->addObject($invoice->getOrder())
                    ->save();
    
                $invoice->sendEmail(true, "");
                
                echo "Invoice generation for order $orderIncrement was successfuln";
            } catch (Exception $e) {
                echo $e->getMessage();
            }
        } else {
            echo "Order $orderIncrement not foundn";
        }
        
        sleep(2);
    }
    

    These examples demonstrate how you might programatically resend the main transactional emails related to an order, and of course it should be possible to take a similar approach for any other transactional emails that might need to be resent.


    Magento Compatibility

    Post originally written for Magento version: 1.4.0.1
    Tested with Magento versions: 1.3.2.4, 1.4.0.1

    Comments

    Helpful article, but one gripe: $orders and $order_id are incorrectly named. $order_id is the worst since it never contains an order's entity id, just the increment id ('order number'). Something like $orderNumber would make more sense, $incrementId would be acceptable. Not normally this much of a perfectionist, but I've spent the past few weeks sorting out some code that was loose with this distinction and at various places an $order_id variable could contain an actual order id or an increment id. Even better, at some points, $order_id, $orderId and $orderID within the same scope would refer to different combinations of entity and increment ids, along with an external customer order number (PO number actually).

    So yeah, it's an $orderNumber, not an $order_id, please help encourage good practice.

    Anyway, thanks again for continuing to crank out helpful code and information here. These snippets are already serving as the basis for some utilities one of our juniors is working on.
    Comment by Onymous - 30 Dec 2010 8:07:41 AM
    Fair point: while I agree that this is pretty minor, I've updated the article to use more appropriate variable names.

    Glad the article was helpful.
    Comment by Andrew Dubbeld - 4 Jan 2011 10:55:07 AM
    We use an extension that where the code is almost identical for mass invoice action, it does everything right >processes>changes status>emails invoice but does not tick the 'customer notified' box even though it sends it:

    Is it apparent below what the problem is?

    [php]
    if (isset($invoice) && $invoice) {
    $invoice->register();

    $invoice->setEmailSent(true);


    $invoice->getOrder()->setCustomerNoteNotify(!empty($data['send_email']));
    $invoice->getOrder()->setIsInProcess(true);

    $transactionSave = Mage::getModel('core/resource_transaction')
    ->addObject($invoice)
    ->addObject($invoice->getOrder());

    $transactionSave->save();
    $invoice->sendEmail(true, '');



    $completedOrders[] = $order->getIncreamentId();
    [/php]
    Comment by Jeff - 13 Jan 2011 9:14:00 PM
    Comments are closed for this post