How to Configure a Postfix Server when Developing a Custom Mailing System
Azoft Blog How to Configure a Postfix Server when Developing a Custom Mailing System

How to Configure a Postfix Server when Developing a Custom Mailing System

By Anton Demenev on April 10, 2014

How to Configure a Postfix Server when Developing a Custom Mailing System

One of our recent projects was all about “reaching goals”. Users reaching goals make important decisions and step-by-step move toward the “grand summit” – their purpose – and this web app that our team worked on tracks and manages this progress.

The workflow consisted of a systematic series of solving tasks, the results of which were sent in by users as an email attachment in PDF or Word, for example.

These “achievement confirmations” can be done in two ways: via a web app interface, or via email. To implement the latter option, we utilized a Postfix server requiring custom configuration.

Here’s how we made it work.

Task overview

Our goal was to add the following functions to the project backend:

  • creating temporary mail aliases;
  • processing mail on the basis of MySQL table rules.

Challenge

Right after we’ve started beta testing, we found a performance bottleneck. The cause was in the “classic scheme” we used to create users and temporary mailboxes: the procedure of mailbox generation along with further compilation of a hash card for Postfix and restarting the service was so source-greedy that at a certain load point our server stopped serving users and was preoccupied solely by restarting. The user creation procedure was even more source-intense.

Solution

We’ve sold the problem by moving all files to MySQL DBMS. In this case all user data can be changed in real time as the MTA service doesn’t assume that this should be done only by admin.

To make this solution universal, PostfixAdmin package was installed; this article covers v. 2.3.7.

Components

  • Postfix, but you can use any other MTA: Sendmail, Exim, etc.
  • Dovecot – for authorization and mail storing; storage model – maildir.

PHP script

The only thing we have to implement in this app is reading new mails and retrieving data from them; besides we want this code to be executed by cron. So to allow cron execute this code, we will create a Symfony command. Every bundle has the “Command” directory; our command will be placed there. Lets name it CheckMailsCommand: create CheckMailCommand.php file inside the Command directory of the bundle you work in (in our case its APIBundle, so we have the following path to the command file: src/Azoft/APIBundle/Command/CheckMailsCommand.php).

Below is what your command may look like:

namespace Azoft\APIBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

class CheckMailsCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this->setName(‘azoft:check-mails')
             ->setDescription('Check out incoming emails and managing user actions.');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $container = $this->getContainer();
       
        // as you can see we are using Logger to log everything during executing this command
        $logger = new Logger('mails_checker');
        $logger->pushHandler(new StreamHandler($container->parameters['mails_checker_log']), Logger::WARNING);

        // for passing through the files we are using Finder class
        $finder = new Finder();
        $finder->files()->name('*.new')->in($container->parameters['mails_dir']);
        $filesNum = $finder->count();
        if($filesNum == 0) {
            $result = sprintf("Nothing to process");
            $logger->addInfo($result);

            return false;
        }
        foreach($finder as $fileKey => $file) {
            // to get mail content we’re using ZendMail class
            // in our application it looks like this:
            $mailContent = $container->get('zend_mail')->fetch($file);
            // where ‘zend_mail’ is a service which had been registered before

            // here should be placed some business logic
            // you may need to do something less or more useful with mails: for example, remove processed mail or something
        }
        // here you can log some result info about checking mails: something like “n mails have been successfully processed”
    }
}

Execute this command is pretty easy:

<path_to_your_project>/app/console azoft:check-mails

Using ZendMail for processing mails

Before using ZendMail class in your application you have to install it. Add this line into your composer.json:

"zendframework/zend-mail" : "2.0.*"

Then you can use it without any custom wrapper (like your own service) – you can use ZendMail class directly inside the command. But I prefer to create a wrapper for such purposes.

My ZendMail class looks like this:

<?php

namespace Azoft\APIBundle\Zend;

class ZendMail
{
    private $fetchResponse = array(
        'from' => '',
        'to' => '',
        'body' => '',
        'attachments' => array()
    );

    public function fetch($file)
    {
        $content = $file->getContents();
        $email = new \Zend\Mail\Storage\Message(array('raw' => $content));
        $headers = $email->getHeaders();
        if(($headers instanceof \Zend\Mail\Headers) === false) {
            throw new \Exception('Header info cannot be retrieved');
        }

        $this->fetchResponse['to'] = $email->getHeaders()->get('to')->getFieldValue();

        if($email->isMultipart()) {
            foreach (new \RecursiveIteratorIterator($email) as $key => $part) {
                // do something with this very mail part
            }
        }

       return $this->fetchResponse;
    }
}

Installing packages

sudo apt-get install \
dovecot-core \
dovecot-postfix \
dovecot-mysql \
postfix \
postfix-mysql \
mysql-server \
mysql-client \
apache2 \
php5 \
php5-mysql \
libapache2-mod-php5

Note: the apt-get command works only in Debian and Ubuntu; use the command that works in your Linux distribution. 

Creating users

groupadd -g 5000 virtual
useradd -g 5000 -u 5000 -m virtual 

Both transports and users will be working under this user.

Creating database

Create the root password:

mysqladmin -u root password VeryStrongPassword
mysql -uroot -p
mysql> CREATE DATABASE postfix;
#now, creating new user
mysql> CREATE USER 'postfix'@'localhost' IDENTIFIED BY 'password';
mysql> GRANT ALL PRIVILEGES ON postfix . * TO 'postfix'@'localhost';
mysql> FLUSH PRIVILEGES;

Configuring Postfix

To make Postfix work, edit main.cf, master.cf and create hash maps. The configuration below is for virtual transport only.

append to your main.cf

main.cf

virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql_virtual_mailbox_domains.cf
virtual_mailbox_base = /home/mail
virtual_alias_maps = mysql:/etc/postfix/mysql_virtual_alias_maps.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf
virtual_minimum_uid = 100
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
mynetworks = 127.0.0.0/8,  recipient_delimiter = + inet_interfaces = all smtpd_sasl_auth_enable = yes smtpd_sasl_type = dovecot smtpd_sasl_path = inet:127.0.0.1:12345  virtual_transport = dovecot relay_domains = proxy:mysql:/etc/postfix/mysql_relay_domains.cf smtpd_sasl_authenticated_header = yes maximal_queue_lifetime = 4h bounce_queue_lifetime = 4h spf_received_header = yes debug_peer_list = 127.0.0.1

Files required for this configuration:

mysql_relay_domains.cf

password = password
hosts = 127.0.0.1
dbname = postfix
query = SELECT domain FROM domain WHERE domain = '%s' AND backupmx = 1 AND active = 1

mysql_transport.cf

user = postfix
password = password
hosts = 127.0.0.1
dbname = postfix
table = domain
select_field = transport
where_field = domain

mysql_virtual_alias_maps.cf

user = postfix
password = password
hosts = 127.0.0.1
dbname = postfix
query = SELECT goto FROM alias WHERE address='%s' AND active = true

mysql_virtual_mailbox_domains.cf

user = postfix
password = password
hosts = 127.0.0.1
dbname = postfix
query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = 0 AND active = 1

mysql_virtual_mailbox_limits.cf

user = postfix
password = password
hosts = 127.0.0.1
dbname = postfix
query = SELECT quota FROM mailbox WHERE username='%s' AND active = 1

Installing PostfixAdmin

Download the most recent version from the project site. Unpack the archive to the working directory on the web server.

Note: The configuration above was made for v. 2.3. 

Editing config.inc.php:

$CONF['configured'] = true;
$CONF['setup_password'] = 'changeme';
$CONF['postfix_admin_url'] = 'your instance url';
$CONF['database_type'] = 'mysql';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'postfix';
$CONF['database_password'] = 'password';
$CONF['database_name'] = 'postfix';
$CONF['admin_email'] = 'postmaster@change-this-to-your.domain.tld';

$CONF['encrypt'] = 'md5crypt';

$CONF['default_aliases'] = array (
    'abuse' => 'abuse@change-this-to-your.domain.tld',
    'hostmaster' => 'hostmaster@change-this-to-your.domain.tld',
    'postmaster' => 'postmaster@change-this-to-your.domain.tld',
    'webmaster' => 'webmaster@change-this-to-your.domain.tld'
);

$CONF['domain_path'] = 'YES';
$CONF['vacation_domain'] = 'autoreply.change-this-to-your.domain.tld';

$CONF['user_footer_link'] = "http://change-this-to-your.domain.tld/main";

$CONF['show_footer_text'] = 'YES';
$CONF['footer_text'] = 'Return to change-this-to-your.domain.tld';
$CONF['footer_link'] = 'http://change-this-to-your.domain.tld';

Uncomment extra patamerters, this will allow to work with data without using the command line.

$CONF['mailbox_postcreation_script']='sudo -u virtual /usr/local/bin/postfixadmin-mailbox-postcreation.sh';

$CONF['mailbox_postedit_script']='sudo -u virtual /usr/local/bin/postfixadmin-mailbox-postedit.sh';

$CONF['mailbox_postdeletion_script']='sudo -u virtual /usr/local/bin/postfixadmin-mailbox-postdeletion.sh';

$CONF['domain_postcreation_script']='sudo -u virtual /usr/local/bin/postfixadmin-domain-postcreation.sh';

$CONF['domain_postdeletion_script']='sudo -u virtual /usr/local/bin/postfixadmin-domain-postdeletion.sh';

Enter the setup password and fill-in the fields; when the installation is over, delete or rename setup.php and go to the admin pane. There we create a server, users and set all the necessary settings.

VN:F [1.9.22_1171]
Rating: 5.0/5 (2 votes cast)
VN:F [1.9.22_1171]
Rating: +2 (from 4 votes)
How to Configure a Postfix Server when Developing a Custom Mailing System, 5.0 out of 5 based on 2 ratings



Request a Free Quote
 
 
 

Please enter the result and submit the form

Content created by Anton Demenev