rnwood / smtp4dev

smtp4dev - the fake smtp email server for development and testing
BSD 3-Clause "New" or "Revised" License
3.03k stars 342 forks source link

[Feature] Relay Rules Engine #879

Closed jafin closed 5 months ago

jafin commented 3 years ago

Related to #574, but encompassing larger surface area than just regex matches, by introducing a rules engine for relay rule matching.

This is a proof of concept, soliciting feedback. I'll push up the code I wrote as a PR for review and link it.

Relay Rules Engine

The Relay rules engine provides a more customizable way to relay messages in Smtp4Dev. You can continue to use the existing approach to relay, or switch on the rules engine.

NOTE: Relay rules is configured in the appsettings.json file. It is currently not exposed in the UI.

Relay rules will allow the following:

Rules are written as Lambda expressions, and support < >, Equals etc. There is also an option for Regex matching via a custom type, Helpers.RegexMatch(field, regexMatch).

Additional custom type helpers can be written to extend this further.

Existing relay options (non breaking)

If the UseRulesEngine is false (see below for explanation), the app will use the existing approach to mange relay rules. By default, to not break anyone, UseRulesEngine should ship as false. It should be opt in to anyone and avoid breaking existing configs.

Getting Started

To configure the app to use the rules engine, edit the appsettings.json properties:

{
    "RelayOptions": {
        "UseRulesEngine": true,
    }
}

NOTE: You are still required to configure the app with valid RelayOptions to identify the server to relay to. Please see the wiki on instructions for each option.

Next create one or more rules (still in the appsettings.json file) Below is an example with one rule.

The rules should have a "WorkflowName" of "RelayMessages" The the array of Rules comes next.

"Rules": [
    {
      "WorkflowName": "RelayMessages",
      "Rules": [
        {
          "RuleName": "Relay From Regex Match",
          "Properties": {
            "RelayMessageProps": {
              "RelayAdditionalAddress": [ "alternativeEmail@mailinator.com", "alternative2Email@mailinator.com" ],
              "RelayToOriginalAddress": true,
              "RelayToAdditionalAddress": true
            }
          },
          "Enabled": true,
          "RuleExpressionType": "LambdaExpression",
          "Expression": "Helpers.RegexMatch(input1.From,\".*gmail.com\")",
        }
      ]
    }
  ]

Breakdown of a rule definition

{
  "RuleName": "Relay if from gmail",  //Any string to identify the intent of the rule
  "Properties": {
    "RelayMessageProps": {  //custom Props type must be 'RelayMessageProps'
      "RelayAdditionalAddress": [  //optional: A list of additional email addresses to relay to if rule match success.
        "alternativeEmail@mailinator.com",
        "alternative2Email@mailinator.com"
      ],
      "RelayToOriginalAddress": true, //boolean - when {true} relay to the original email address if rule success
      "RelayToAdditionalAddress": true //boolean - when {true}, relay to the array of address specified in "RelayAdditionalAddress"
    }
  },
  "Enabled": true, // boolean when {true} rule is active.
  "RuleExpressionType": "LambdaExpression", // Always should be 'LambdaExpression'
  "Expression": "Helpers.RegexMatch(input1.From,\".*gmail.com\")",  // Expression to match for rule success.  Expressions must be escaped
}

Expression examples

Perform regexMatch on From address which contains gmail.com.

"Expression": "Helpers.RegexMatch(input1.From,\".*gmail.com\")"

Perform regexMatch on To field where contains gmail.com

"Expression": "Helpers.RegexMatch(input1.To,\".*gmail.com\")",

Perform lambda match on Subject field where subject equals the string redirect

"Expression": "input1.Subject == \"redirect\"",

Lambda match AND where Subject field equals foo and To field equals bar@baz.com

"Expression": "input1.Subject == \"foo\" AND input1.To == \"bar@baz.com\"",

The engine is based on the Microsoft Rules engine, which indicates it shall work with any compatible Lambda expression.

Error handling

At present, little effort has gone into validation and error handling of invalid configuration or expressions. ☠️ 🐉

Examples

An example config with a set of rules exists in /examples/appsettings.rules.json

Schreckson commented 2 years ago

Hi, is there any chance that this feature will be integrated in one of the next releases?

rnwood commented 5 months ago

An alternative implementation has been merged recently. See AutomaticRelayExpression in appsettings.