3

I'm creating my first module. Inside this module, I check a few entries for some conditions, and if all conditions are met, I want to send out a personal email.

Everything worked, but I wanted to clean up my code and put the email body inside a template file so it's more organized. This is where I'm having trouble.

Whenever I run the command ./craft check-date/send-mail, I keep getting the following error:

Unable to find the template “checkdate/emails/_email.twig”.

Here is a striped down version of my module:

modules/checkdate/src/CheckDate.php

<?php

namespace modules\checkdate;

use Craft;
use yii\base\Module as BaseModule;

/**
 * CheckDate module
 *
 * @method static CheckDate getInstance()
 */
class CheckDate extends BaseModule
{
    public function init(): void
    {
        Craft::setAlias('@modules/check-date', __DIR__);

        // Set the controllerNamespace based on whether this is a console or web request
        if (Craft::$app->request->isConsoleRequest) {
            $this->controllerNamespace = 'modules\\checkdate\\console\\controllers';
        } else {
            $this->controllerNamespace = 'modules\\checkdate\\controllers';
        }

        parent::init();
    }
}

modules/checkdate/src/console/controllers/SendMailController.php

<?php

namespace modules\checkdate\console\controllers;

use Craft;
use craft\console\Controller;
use craft\elements\Entry;

use craft\helpers\Html;
use craft\helpers\Template;
use craft\mail\Message;

use yii\base\InvalidConfigException;

/**
 * Greet Controller
 */
class SendMailController extends Controller
{
    /**
     * Issues a greeting to new Craft developers.
     */
    public function actionIndex()
    {

        // Get all entries in your section
        $entries = Entry::find()
            ->section('dates')
            ->fieldSendedMail(false)
            ->all();

        foreach ($entries as $entry) {

            // Variables
            $title = $entry->title;

            // Send email with HTML body
            try {
                // Render the HTML template with variables
                $htmlBody = Craft::$app->view->renderTemplate(
                    'checkdate/emails/_email.twig',
                    [
                        'name' => $title
                    ]
                );
                $mailer = Craft::$app->getMailer();
                $message = new Message();
                $message->setTo('[email protected]');
                $message->setSubject('Subject of the Email');
                $message->setHtmlBody($htmlBody);
                $mailer->send($message);
            } catch (InvalidConfigException $e) {
                // Handle any exceptions that occur during email sending
                Craft::error('Error sending email for entry ' . $entry->id . ': ' . $e->getMessage(), 'send-mail');
            }
        }
    }
}

modules/checkdate/src/templates/emails/_email.twig

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Email Template</title>
    </head>
    <body>
        <p>{{ title }},</p>
        <p>This is your custom email content.</p>
    </body>
</html>

config/app.php

<?php
/**
 * Yii Application Config
 *
 * Edit this file at your own risk!
 *
 * The array returned by this file will get merged with
 * vendor/craftcms/cms/src/config/app.php and app.[web|console].php, when
 * Craft's bootstrap script is defining the configuration for the entire
 * application.
 *
 * You can define custom modules and system components, and even override the
 * built-in system components.
 *
 * If you want to modify the application config for *only* web requests or
 * *only* console requests, create an app.web.php or app.console.php file in
 * your config/ folder, alongside this one.
 */

use craft\helpers\App;
use modules\checkdate\CheckDate;

return [
    'id' => App::env('APP_ID') ?: 'CraftCMS',
    'modules' => [
        'check-date' => CheckDate::class
    ],
    'bootstrap' => [
        'check-date'
    ],
];

composer.json

    "autoload": {
        "psr-4": {
            "modules\\checkdate\\": "modules/checkdate/src/"
        }
    }

How do i access my template?

Thanks.

2 Answers 2

2

Unlike plugins, modules have to register their own template root.

Stick something like this in your module's init() method:

use yii\base\Event;
use craft\events\RegisterTemplateRootsEvent;
use craft\web\View;

...

Event::on(
    View::class,
    View::EVENT_REGISTER_CP_TEMPLATE_ROOTS,
    function (RegisterTemplateRootsEvent $e) {
        if (is_dir($baseDir = $this->getBasePath() . DIRECTORY_SEPARATOR . 'templates')) {
            $e->roots[$this->id] = $baseDir;
        }
    }
);

This will register a template root 'check-date' (i.e. the ID for your module, as per the config in your config/app.php file), that points to the modules/checkdate/src/templates folder.

This means that you'll also have to correct your email template path from

'checkdate/emails/_email.twig'

to

'check-date/emails/_email.twig'

It's also, generally, a good idea to be explicit about the view mode when rendering custom templates. In this case, you'll want Craft to use the 'CP' view mode, so the full View::render() method call should look like this:

use craft\web\View;

...

$htmlBody = Craft::$app->view->renderTemplate(
    'check-date/emails/_email.twig',
    [
        'name' => $title
    ],
    View::TEMPLATE_MODE_CP
);
1
  • Couldn't find that myself! Thanks for clearing that out, works as expected now.
    – Arno Ramon
    Mar 21 at 16:20
1

I believe you'll need to switch the templateMode to be TEMPLATE_MODE_SITE in your call to renderTemplate() -- otherwise it'll default to attempting to load templates from the main Craft app.

$htmlBody = Craft::$app->view->renderTemplate(
    'checkdate/emails/_email.twig',
    ['name' => $title],
    \craft\web\View::TEMPLATE_MODE_SITE
);

https://docs.craftcms.com/api/v4/craft-web-controller.html#method-rendertemplate

4
  • Hi James, I'm a step closer, but now it will look in Craft's main template folder. Is there a way to keep everything inside the module folder? So with you're snippet it looks for /templates/checkdate/emails/_email.twig -> works well tho. But can i make it so it uses /modules/checkdate/src/templates/checkdate/emails/_email.twig instead?
    – Arno Ramon
    Mar 21 at 16:07
  • This answer assumes that the email.twig template is in the site template folder (i.e. templates/checkdate/emails/_email.twig). I believe OP wants to render the template from a folder inside their module's folder, in which case TEMPLATE_MODE_SITE is wrong. Mar 21 at 16:13
  • Ah right I hadn't considered that approach. Interesting. Personally if it's a module I'd prefer to keep all my templates together (especially since emails would likely extend some shared email layout templates. Only reason I'd consider bundling it together would be if it's a plugin to be distributed. Mar 21 at 17:08
  • @JamesSmith That's completely valid IMO. I've done both; the approach typically depends on how tightly coupled the module is to the actual site/build. For modules that are mostly self-contained, I think it's nice to keep everything pertaining to that module, including templates, in the module source folder. Mar 21 at 18:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.