01 Dec 2022
Michael Hnat

using environments for your dev system

The use of environments can prevent you from accidents - See what happened to us

Last week we had a really strange issue. For whatever reason an already sent out mailing was sent again, but with broken links pointing to 127.0.0.1 instead of the origin domain.

This was bad in two ways. First the emailing itself, which was originally sent two weeks ago already and caused a lot of confusion and questions at the customers phone.

And second: Why did it happened? Is there some bug on the core? Is Mailjet having an issue?

After some investigation we found out what happened: We've set up a new developer machine and installed a copy of a db. Within the database, there were some planned mailings that have been sent out live correctly. But after installing the db copy to the local machine, there mailing were still planned and the dev machine sent them out.

We normally change the outgoing mailserver to some mailcatcher to prevent this, but this time we didn't 

The question was: How can we prevent things like this automatically? And this brought us to the use of different environments.

How does this work?

Environments are defined in your config.cfc. If an environment isn't defined, it is assumed, that it's a production setting. The environments can be defined like this (already set in the preside config.cfc (around line 615):

environments = {
  local = "^local\.,\.local(:[0-9]+)?$,^localhost(:[0-9]+)?$,^127.0.0.1(:[0-9]+)?$"
};

Means here: If your domain starts with 'local' oder ends with '.local' or is 'localhost' or '127.0.0.1' - then it's the local environment.

You can define additional in your own config.cfc by just adding elemts to the environments struct (e.g. for stage systems or dev machines.

To define things in a specific environment you need a function named like the environments name in your config.cfc (unfortunately this doesn't work in extension).

Example:

public void function stage() {
  // Define things for your stage environment
}
public void function local() {
  // Define things for your local environment
}

The dev-helpers extension makes heavy use of this. There you can see how it is used.

 

What did we do to prevent our e-mail sending problem?

Two things:

1. Do some settings for your local environment:

We created some override settings we use in step 2:

config.cfc:

public void function local() {
  settings.EmailServerOrderride = {
    settings = {
      password  : '',
      port      : 25,
      server    : '127.0.0.1',
      username  : 'noUser'
    },

    sendArgs = {
      to : ['[email protected]']
    }
  };
}

 

2. Overloading the email send method to make use of the setting:

create an own handlers/email/serviceProvider/smtp.cfc file with the following content:

component extends="preside.system.handlers.email.serviceProvider.Smtp" {
  property name="environment" inject="coldbox:setting:environment"; // or check the Coldbox docs for getting this if doesn't work

  private boolean function send() {
    try {

      // Get EmailServerOverride Settings
      var EmailServerOrderride = getSetting( name="EmailServerOrderride", defaultValue={ settings = {}, sendArgs = {} } );

      // Merge Override Settings
      StructAppend(arguments.settings,EmailServerOrderride.settings,true);
      StructAppend(arguments.sendArgs,EmailServerOrderride.sendArgs,true);

    } catch (any e) {
      // Just in case
    }

    var erg = super.send( argumentCollection=arguments );

    return true;
  }
}

It's pretty easy.

  • Get the EmailServerOverride settings (defined by your environment function) or define some default.
  • overwrite the original settings with structAppend.
  • call the original send method with changed parameters.

What is happening now, when an environment is recognized by the definition (like our local dev machine ending with a .local domain) the override settings are defined in the environment function ( local() ) and the parameters for sending emails are changed to our local mailserver which isn't sending out mails live, but storing them in a catch all mailbox.

Now, if some mailings are planned it's still sent out by preside, but not on the live mailserver.

 

There are some other things to consider like anomyzing the psys.website_user email address to something link 'uuid@local' to make sure nothing is going wrong, but I hope the workaround above is helping you to understand how to work with environments.

 

Extra Tipp:

there are several ways how to check mails without sending them out live via a mailserver. Locally (windows) I'm user papercut-smtp, which is just gathering everything coming in. 

A more network thing is InBucket which can be spinned up as a docker container easily.

A different approach would be changing all e-mails to @local or a maildomain provided by Mailinator.

Or as shown above: Override the to address on every mail sent out to sendArgs = { to : '[email protected]'] }