I'm currently working on a school project for my finals using craft cms.

One of the targets I have to achieve are simply sending emails when someone fills in his email in an input mail and submits that form.

However, I've been looking for a way to this for two days and I just can't seem to figure it out how this whole email sending thing works.

At the moment I have a twig template for my homepage, a custom module for the mailing and a route to make it work. However everytime I fill out the form and submit, my route can't be founded whilst I've registered it.. I think it's because I don't get how craft works exactly.

My Email Template:

<form class="cta__mail__input" method="POST" id="emailForm" action="{{ url('subscribe') }}">
    {{ csrfInput() }}
    <input type="text" placeholder="Email" name="email" id="emailInput">
    <button type="submit" id="submitButton">Confirm </button>

I've read so many topics but it's all unclear so if anyone knows how to start, or has a link to a tutorial. Please let me know..


The code above me was replaced and that one works now, but I'm stuck with a same situation and whatever I try, I can't seem to fix it..

Whenever a user clicks on the button, the action method from the custom plugins controller should be called, but I always get a 404, page not found caused by invalid route. The weird fact is that this used to work for a long time and no it just gave up..

When I published the module to my production server, I also get the "page not found". Here's the code:

The form that is included on another template:

<form class="inquiry" method="post" accept-charset="UTF-8" action="{{ url('tester/inquiry') }}">
    {{ csrfInput() }}
    <input type="hidden" name="product_name" value="{{ entry.title }}">
    <button type="submit" id="submitButton">{{ entry.productDetailInquiryButton }}</button>
    <a href="{{ url('tester/inquiry') }}">testing</a>


The Module.php. I can confirm the module is loaded bc of the dd.

namespace modules\tester;
use craft\base\Event;
use craft\events\RegisterUrlRulesEvent;
use craft\web\UrlManager;

class Module extends \yii\base\Module
    public function init()
        // dd('this module is loaded');

            function(RegisterUrlRulesEvent $event) {
                $event->rules['tester/inquiry'] = 'tester/inquiry/send';


The controller


namespace modules\tester\controllers;

use Craft;
use craft\web\Controller;

class InquiryController extends Controller 

    protected bool|array $allowAnonymous = true;

    public function actionSend() {



  "autoload": {
    "psr-4": {
      "modules\\": "modules/",
      "subscribe\\": "modules/subscribe/",
      "tester\\": "modules/tester/"

Folder structure

I've also tried making the C for the controllers folder a capital letter but it had no effect.

  • What happens when the form is POSTed to the /subscribe URL? Is it just a Twig template (i.e. templates/subscribe.twig)? Commented Jul 17, 2023 at 18:06
  • @MatsMikkelRummelhoff well in my logic when I submit that form the function in a controller should be called so it will send an email to the one who subscribed and to my that someone did subscribee. However that controller code is not given because I just can't find the right example.. :/ Commented Jul 17, 2023 at 19:52
  • Are you bootstrapping your module in app.php? It may not have an automatic ID of tester. Check out this section in the Modules documentation. Commented Aug 10, 2023 at 19:36

1 Answer 1


You should checkout this article on Craft Docs about Controller actions. Inside your form you can put {{ actionInput('my-plugin/widgets/save') }} instead of using the action attribute on a form.

Can you share your controller code?

  • I will do that, but I will change my form as well because I've made some changes.. Commented Aug 10, 2023 at 14:07
  • For POST requests, this is the recommended way of ensuring your form data arrives at the desired controller. It will work regardless of what vanity URL rules are set up in the project! Commented Aug 10, 2023 at 19:39

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.