Uniform send email from controller not managing the form


I moved the logic to send an email through Uniform from the controller building the form, to another controller. I’m waiting for a banking transaction to succeed before creating the order as a subpage (as of now working fine) and send out an email.

I’m worried if the email does not trigger anymore is because it’s not part of the controller with the form? I saved the form with the data in a session, which I then am able to correctly call up.

I called the uniform plugin as usual at the top of the page, but before that I’m calling another plugin


  require_once 'vendor/autoload.php';
  use \GingerPayments\Payment\Ginger;
  use Uniform\Form;

  return function($site, $pages, $page) {

    foreach($order->transactions() as $transaction) {
      if($transaction->status() == 'completed') {
        try {
          $new_order = page('shop/orders')->children()->create('order-' . time(), 'order', $rules);
        } catch(Exception $e) {
          echo $e->getMessage();

        try {
             'to' => 'andre@andrefincato.info',
             'from' => 'work@andrefincato.info',
             'subject' => 'Sandberg Series order',
             'snippet' => 'email',
             'service' => 'phpmailer'
        } catch(Exception $e) {
          echo $e->getMessage();

  return compact('order', 'userform');

One more thing, upon redirect from the external payment page to the kirby website, at the end of the controller, I added these two to delete the cookies holding the order + user infos. I am able to use the cookies before in the controller and retrieve data, but wondering if they cause any problem at the end of the controller?

//++ destroy everything


OK progress, I simply forgot to make a $form = new Form ($array).

Now, I get the error

The CSRF token was invalid

which seems to be a topic already on the forum. Before moving the Uniform function that sends the email, I was able to double check for the token to be valid by doing && get('payment_method') === 'check'

1. Add this to the Uniform form:

<input type="hidden" name="payment_method" value="check">

2. Update the controller

if (r::is('POST') && get('payment_method') === 'check') {

see Cartkit & Uniform payment system

The reason the token becomes invalid (I think), is because I’m doing a page redirect to an external website, then redirecting back to a landing page and only then I trigger a bunch of functions, including the one to send an email through Uniform.

But I also read that as of kirby 2.5.5 tokens are valid for an entire session (How to reset form and send again?)

How to go with this?

Could this be related? Excluding URIs From CSRF Protection

Sometimes you may wish to exclude a set of URIs from CSRF protection. For example, if you are using Stripe to process payments and are utilizing their webhook system, you will need to exclude your Stripe webhook handler route from CSRF protection since Stripe will not know what CSRF token to send to your routes.

In my case I am redirecting the user to an external payment page… and then the user go back to the kirby website where some actions are triggered (including Uniform).

As Uniform is intended for form submissions only, it always expects a CSRF token. You are probably right with the assumption that your third party payment site does not supply this token (not just an invalid one) and causes Uniform to throw the exception. Can you confirm this? Does the payment site perform a POST request back to your site if payment was successful?

Anyway, I see two ways to go for you here:

  1. Don’t use Uniform. If you just want to send an email you can use Kirby’s email helper just as well.
  2. Use Uniform in a way it is not intended to. You can use the EmailAction standalone like this:
    $form = new Form(...);
    $action = new \Uniform\Actions\EmailAction($form, [
       // options go here
    try {
    } catch (\Uniform\Exceptions\PerformerException $e) {
       // handle exception
    Note that you have to handle errors of the action yourself. Also Uniform will not perform validation, guards or anything here.

Hey, thank you for your reply! It all makes sense.

I might go with the first option at this point, and trigger directly the send-email action through PHP Mailer (which I set up already and am using in favour of the built-in php mail() function).


Hi @mzur,

I’m using the latest Uniform v3.4.0 with ajax. My Uniform-form is at almost every page of the site, thus I use a regex pattern to make it work.

Now I need to install another form and it should neither use Uniform nor ajax. But when I submit that form I have problem with Uniform because it kicks in and trying to go through the rules, guards and action for my Uniform-form.

I tried to identify my Uniform-form with a second parameter of the Form constructor like here in the docs:
https://kirby-uniform.readthedocs.io/en/latest/answers/#i-have-multiple-static-forms-on-one-page-when-one-fails-the-error-messages-are-also-displayed-for-the-other-forms-why but with no success.

My Uniform-form works as I want. I just don’t want to go through my uniform-logic in the route when submit another form. How can I limit the form route in my config file to only run when my Uniform-form is submitted?

My route code:

    'pattern' => '(^(?!.*page-lock).+$)',
    'method' => 'POST',
    'action' => function () {
      $shareForm = new \Uniform\Form([
        'name' => [
          'rules' => ['required'],
          'message' => 'Fyll i ditt namn'
        'email' => [
          'rules' => ['required'],
          'message' => 'Fyll i din epost'
        'recipient-email' => [
          'rules' => ['required'],
          'message' => 'Fyll i mottagarens epost'
      ], 'share-form');

      // Perform validation and execute guards:

      if (!$shareForm->success()) {
        // Return validation errors:
        return response::json($shareForm->errors(), 400);

      // If validation and guards passed, execute the action:
        'service' => 'phpmailer',
        'to'      => get('recipient-email'),
        'from'    => 'noreply@example.com',
        'subject' => 'A subject',
        'snippet' => 'email',
        'service-options' => [
          'title'    => 'A title',
          'smtp'     => 'smtp.xxx.com',
          'username' => 'user@example.com',
          'password' => 'xxx',
          'port'     => xxx

      if (!$shareForm->success()) {
        // This should not happen and is our fault:
        return response::json($shareForm->errors(), 500);

My Uniform-form code:

<form id="share-form" class="c-share-form" method="post" action="<?= $page->url() ?>">
    <div id="share-close" class="c-share-close">
      <svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9">
        <g fill="none" fill-rule="evenodd" stroke="#2B2B2B" stroke-linecap="square" transform="rotate(45 6.207 3.793)">
          <path d="M5.5.5L5.5 10.5M.5 5.5L10.5 5.5"/>

    <div id="share-group" class="c-share-group">
      <h3 class="c-share-headline"><?= l('share-page') ?></h3>
      <div class="c-share-field">
        <label class="c-share-label" for="name"><?= l('your-name') ?></label>
        <input class="c-share-input" type="text" id="name" name="name" autocomplete="name" autocapitalize="words">

      <div class="c-share-field">
        <label class="c-share-label" for="email"><?= l('your-email') ?></label>
        <input class="c-share-input" type="email" id="email" name="email" autocomplete="email">

      <div class="c-share-field">
        <label class="c-share-label" for="recipient-email"><?= l('recipient-email') ?></label>
        <input class="c-share-input" type="email" id="recipient-email" name="recipient-email" autocomplete="email">
        <input class="c-share-input" type="hidden" name="page-title" value="<?= $page->title()?>">
        <input class="c-share-input" type="hidden" name="page-url" value="<?= $page->url()?>">

      <?= csrf_field() ?>
      <?= honeypot_field('website', 'c-share-honeypot'); ?>

      <button id="share-button" class="o-button o-button--share" type="submit" name="" value="">
        <span class="c-share-buttontext"><?= l('share') ?></span>

      <div id="share-status" class="c-share-status"></div>


Are there any reasons why you can’t just use two separate routes for the two forms? Like /api/uniform-submit and /api/other-submit?

The Uniform-form handles a form where the visitor can share the page via an email, thus the form needs to be on all pages and the route must listen for all pages to make it work. Thats why I use regex in my pattern.

And the other form is a submit-form to a newsletter in the footer, means it is also on every page.

That’s the reason why I can’t hard code two separate routes. The two forms are on all pages.

You could nevertheless prefix your routes:

'pattern' => 'uniform-submit/(:all)'

then you would have access to the individual pages as well…

All you’d have to do is change your action URL to reflect this.

1 Like

I have this action url today. How should this action look like to reflect the new pattern with uniform-submit?

action="<?= url('uniform-submit/' . $page->uri()) ?>"