Mail API Service

Visit GitHub Repository

Mail service

The Mail service is an Email microservice that sends emails using mustache-based templates. It was built to allow our development teams at 2amigos to avoid having to configure mail over and over on projects involving a microservices infrastructure. It's a combination of two separate applications, one being Symfony's CI application and the other being an API built with Slim3 as it uses a technique called spooling.

The project uses Monolog for logging, Fractal as a serializer, Tactitian as a command bus, gettext for translations, Basic access authentication and Json Web Tokens for authentication (this is optional), and Zend filter for data filtering and validation.

Docker compose and Postman collection files are included for easy development, even though docker is not strictly necessary for development as you could easily use PHP built-in server.

The project tries to follow DDD principles.

Install

Install the latest version using composer.

$ composer create-project --no-interaction --stability=dev 2amigos/mail-service app

If you are using it from a private repository (using a github url here as an example).

$ composer create-project --no-interaction --stability=dev 2amigos/mail-service app --repository-url=https://github.com/2amigos/mail-service

Configuration

The project uses environment files to configure secrets, for that reason, you must create a file named .env at the root directory of the project. An .env.example file has been provided with all required environment values. Modify that file and save it as .env in the root directory.

By default, the API application is configured to work under basic authentication processes. It uses an array of users for that purpose but you could easily change that behavior by configuring the authenticator option of the HttpBasicAuthentication middleware by creating your own or using one provided by the library. Check the PdoAuthenticator.

If authentication is successful, the action will return a Json Web Token to be used for subsequent calls.

Authentication, or the usage of scopes are optional. If you don't wish to work with this kind of setup, simply remove the middleware configurations of HttpBasicAuthentication, JwtAuthentication and ScopeMiddleware middlewares.

The most important part of the application is its views, which need to be in the views/txt and views/html subdirectories. Their names already explain what type of templates should be in each one.

It also has multi-language support by using gettext. An example translation file and template have been provided to help you understand how it works. We have also added a command to work with the excellent localization management platform called POEditor. That command will import for you the translations and transform the files to .php files. To import the required translations to your project from POEditor use this command:

$ ./bin/console import-translations:run --api-token={POEDITOR_TOKEN} --project={PROJECT_ID} --languages=es,de --delay=3

Where {POEDITOR_TOKEN} and {PROJECT_ID} are your token and project id respectively.

The translations will be automatically imported into the ./i18n/ folder.

In order to work with translations, we have also included a helper class to work with Mustache that will parse the content and attempt to get the translated content of text within {{#i18n}}{{/i18n}} tags. See the example view provided on this project.

Usage

For the sake of the example, go to the public folder of the app and start the built-in PHP server like this:

php -S localhost:8080

Now we can access the api at http://127.0.0.1:8080.

Get a token

To get a token, use the following:

$ curl "https://127.0.0.1:8080/token" \
    --request POST \
    --include \
    --insecure \
    --header "Content-Type: application/json" \
    --data '["mail.all"]' \
    --user test:test

HTTP/1.1 201 Created
Content-Type: application/json

{
    "data": {
        "token": "XXXXXXXXXX",
        "expires": 1550271641
    }
}

Sending an email (to the spool)

Using the token, you can now post a request using application/form-data to send an email.


curl -X POST \
  http://127.0.0.1:8080/mail/send \
  -H 'Authorization: Bearer YOUR_TOKEN_HERE' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Postman-Token: 22bf2715-35e4-41ee-a04b-fd8beddcdd62' \
  -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
  -F from=noreply@example.com \
  -F to=user@example.com \
  -F 'subject=Testing micro-services' \
  -F template=hello-world \
  -F 'data[name]=World' \
  -F language=es \
  -F 'attachments[]=@/path/to/image/to/attach/41835188_10217308479844850_6484466208170049536_o.jpg'
  

The above command will create an email message on the spool directory, configured by default at the runtime folder.

Parameters

  • from: Optional. Who the message is from. If not specified, the mail message will be configured with the environment variable named MAIL_NO_REPLY_EMAIL (see .env.example file).
  • to: Required. To whom the message was addressed.
  • subject: Required. This is what the sender set as the topic of the email content.
  • template: Required. The name of the template. For example, if we set this parameter with the value registration, the system will validate whether a mustache template with the name registration.mustache can be found in either the views/txt or views/html folders.
  • language: Optional. If the name is translated, set this parameter with the language code you wish to have the message translated to. For example, if you set it to es, it will try to load the translations from the i18n/es/messages.php file. See src\Infrastructure\Mustache\Helpers\TranslatorHelper for further information on loading translations.

Sending an email (from the spool)

We use Symfony's SwiftMailer bundle to ease the task of sending emails from the spool as it comes with some handy commands. From the project root type $ ./bin/console on the terminal and the following commands will be shown:

Available commands:
  about                      Displays information about the current project
  help                       Displays help for a command
  list                       Lists commands
 assets
  assets:install             Installs bundles web assets under a public directory
 cache
  cache:clear                Clears the cache
  cache:pool:clear           Clears cache pools
  cache:pool:delete          Deletes an item from a cache pool
  cache:pool:prune           Prunes cache pools
  cache:warmup               Warms up an empty cache
 config
  config:dump-reference      Dumps the default configuration for an extension
 debug
  debug:autowiring           Lists classes/interfaces you can use for autowiring
  debug:config               Dumps the current configuration for an extension
  debug:container            Displays current services for an application
  debug:event-dispatcher     Displays configured listeners for an application
  debug:router               Displays current routes for an application
  debug:swiftmailer          Displays current mailers for an application
 enqueue
  enqueue:consume            [enq:c] A client's worker that processes messages. By default it connects to default queue. It select an appropriate message processor based on a message headers
  enqueue:produce            Sends an event to the topic
  enqueue:routes             [debug:enqueue:routes] A command lists all registered routes.
  enqueue:setup-broker       [enq:sb] Setup broker. Configure the broker, creates queues, topics and so on.
  enqueue:transport:consume  A worker that consumes message from a broker. To use this broker you have to explicitly set a queue to consume from and a message processor service
 import-translations
  import-translations:run    Import POEditor translations command
 lint
  lint:yaml                  Lints a file and outputs encountered errors
 router
  router:match               Helps debug routes by simulating a path info match
 swiftmailer
  swiftmailer:email:send     Send simple email message
  swiftmailer:spool:send     Sends emails from the spool

To send emails from the spool, simply configure a cron job on your server to run the following command at whatever time interval you think is best for your application:

$ ./bin/console swiftmailer:spool:send --message-limit=10

Now, we have to say that whilst spooling is a great feature from SwiftMailer, this technique is not suitable for applications that require sending a high volume of emails. In that case, we highly recommend the usage of a good queue library such as php-enqueue with the broker that best suits your knowledge and requirements. We recommend RabbitMQ as it comes with a manager interface where you will be able to see things such as how many emails are being sent, how many have failed, and so on, with the ease of adding/removing as many workers as you need under a possible heavy load. It is also open source.

We have provided a working example using php-enqueue/enqueue-bundle which comes with a set of very handy commands so you don't need to replicate that work, in conjunction with its Filesystem transport. The following sections explain how to work with that queue system provided.

Sending an email (to the filesystem queue)

First what we need to do is to modify the commandBus locator map and use the mail.send.queue.handler instead of mail.send.spool.handler:

 $map = [
         CreateTokenCommand::class => 'token.create.handler',
         SendMessageCommand::class => 'mail.send.queue.handler', // must be set like this
     ];
 ```

And that's it. Using the same previous call, this time the message will be sent to the configured queue on the runtime 
folder. 

### Sending an email (from the filesystem queue)

As we said previously, the [php-enqueue/enqueue-bundle](https://github.com/php-enqueue/enqueue-bundle) comes with a set 
of pretty handy commands. For the full reference of those commands, please go to [its documentation](https://github.com/php-enqueue/enqueue-dev/blob/master/docs/bundle/cli_commands.md). 

The one to consume all the messages that go to the queue is `enqueue:consume`: 

``` bash 
$ ./bin/console enqueue:consume mail --no-interaction -vvv --receive-timeout=60000 

Contributors

Thanks All