Build a Postfix mail server, start Python code triggered by mail reception, and post mail to Slack (local environment)

Article content

Build a mail server with Postfix and run your own program on the server side every time a mail is received. The self-made program uses the received mail as input data and performs some processing. This can be implemented in any language, but in this article we'll use Python to implement posting to Slack. This time, the mail server is built so that it works at least in the local environment, and mails are sent and received only in the local environment. We do not make detailed settings such as security.

environment

Here are the steps for both CentOS 8 and Ubuntu 20.04. Install BIND9, Postfix, and Dovecot from Linux packages. The environment of the server that builds the mail server is as follows.

--Domain name: localdomain --Hostname: localhost --General Linux username and password: usrname, secret

Python version of CentOS


$ python3 -V
Python 3.6.8

Python version of Ubuntu


$ python3 -V
Python 3.8.2

CentOS ifconfig result


ens160: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.79.128  netmask 255.255.255.0  broadcast 192.168.79.255
        inet6 fe80::59ac:7015:10c9:543c  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:93:f6:a7  txqueuelen 1000  (Ethernet)
        RX packets 914  bytes 156651 (152.9 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 284  bytes 25365 (24.7 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

virbr0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 192.168.122.1  netmask 255.255.255.0  broadcast 192.168.122.255
        ether 52:54:00:be:03:9b  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Ubuntu ifconfig result


docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:cd:65:85:ad  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.79.130  netmask 255.255.255.0  broadcast 192.168.79.255
        inet6 fe80::f8e9:90fd:cfc4:280e  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:cc:76:18  txqueuelen 1000  (Ethernet)
        RX packets 159457  bytes 233626546 (233.6 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 10206  bytes 690515 (690.5 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local loop back)
        RX packets 514  bytes 48478 (48.4 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 514  bytes 48478 (48.4 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Build a Postfix mail server

Build a Postfix mail server while looking at the following two books. Since these books contain construction procedures and explanations, this article only shows the procedures required to build a local environment. Both books use CentOS 5.4, but this article rewrites the commands for CentOS 8 and Ubuntu 20.04.


--Title: DNS server construction by BIND9 (revised new edition) --Author: Hiromichi Ito / work Tatsuto Kawahara / work Shin Nozu / work --Publisher: Technical Review Company --Publishing date: 2010-07


--Book title: Introduction to Postfix practice --Author: Masato Shimizu / work --Publisher: Technical Review Company --Publishing date: 2010-10

DNS settings

Book "DNS Server Construction with BIND9" p.84-p.86

Install BIND9 as the root user. CentOS uses chroot according to the book. Ubuntu doesn't use chroot this time.

CentOS


yum install bind bind-chroot

cd /var/named/chroot/etc
mv /etc/named.conf .
ln -s /var/named/chroot/etc/named.conf /etc/

Ubuntu


apt install bind9 bind9utils

Book "DNS Server Construction with BIND9" p.86, p.96

As the root user, edit /etc/resolv.conf.


editor /etc/resolv.conf

CentOS

/etc/resolv.conf



# Generated by NetworkManager
search localdomain
-nameserver 192.168.79.2
+nameserver 127.0.0.1

Ubuntu

/etc/resolv.conf



-nameserver 127.0.0.53
+nameserver 127.0.0.1
options edns0
search localdomain

Book "DNS Server Construction with BIND9" p.87

As the root user, edit named.conf.

CentOS


editor /etc/named.conf

/etc/named.conf



(Omitted)

options {
-        listen-on port 53 { 127.0.0.1; };
+        //listen-on port 53 { 127.0.0.1; };
-        listen-on-v6 port 53 { ::1; };
+        //listen-on-v6 port 53 { ::1; };
        directory       "/var/named";
        dump-file       "/var/named/data/cache_dump.db";
        statistics-file "/var/named/data/named_stats.txt";
        memstatistics-file "/var/named/data/named_mem_stats.txt";
        secroots-file   "/var/named/data/named.secroots";
        recursing-file  "/var/named/data/named.recursing";
-        allow-query     { localhost; };
+        //allow-query     { localhost; };

(Omitted)

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";
+
+zone "localdomain" IN {
+        type master;
+        file "localdomain.zone";
+        allow-update { none; };
+};

Ubuntu


editor /etc/bind/named.conf.local

/etc/bind/named.conf.local



//
// Do any local configuration here
//

// Consider adding the 1918 zones here, if they are not used in your
// organization
//include "/etc/bind/zones.rfc1918";
+zone "localdomain" IN {
+        type master;
+        file "localdomain.zone";
+        allow-update { none; };
+};

Book "DNS Server Construction with BIND9" p.89

As the root user, check the syntax of named.conf.

CentOS


/usr/sbin/named-checkconf /etc/named.conf

Ubuntu


/usr/sbin/named-checkconf /etc/bind/named.conf

Book "DNS Server Construction with BIND9" p.89

As the root user, create a new localdomain.zone.

CentOS


editor /var/named/chroot/var/named/localdomain.zone

/var/named/chroot/var/named/localdomain.zone



$TTL 10800
localdomain. 1D IN SOA ns.localdomain. root.localdomain. (
        2020052601    ; serial
        43200         ; refresh
        5400          ; retry
        3600000       ; expiry
        3600 )        ; minimum

localdomain. 1D IN NS ns.localdomain.

localdomain. 1D IN MX 10 mail01.localdomain.

ns.localdomain. 1D IN A 192.168.79.128
mail01.localdomain. 1D IN A 192.168.79.128

Ubuntu


editor /var/cache/bind/localdomain.zone

/var/cache/bind/localdomain.zone



$TTL 10800
localdomain. 1D IN SOA ns.localdomain. root.localdomain. (
        2020052601    ; serial
        43200         ; refresh
        5400          ; retry
        3600000       ; expiry
        3600 )        ; minimum

localdomain. 1D IN NS ns.localdomain.

localdomain. 1D IN MX 10 mail01.localdomain.

ns.localdomain. 1D IN A 192.168.79.130
mail01.localdomain. 1D IN A 192.168.79.130

Book "DNS Server Construction with BIND9" p.91

As the root user, set the permissions of the zone file.

CentOS


chmod 640 /var/named/chroot/var/named/localdomain.zone
chown root:named /var/named/chroot/var/named/localdomain.zone
ln -s /var/named/chroot/var/named/localdomain.zone /var/named/

Ubuntu


chmod 640 /var/cache/bind/localdomain.zone
chown root:bind /var/cache/bind/localdomain.zone

Book "DNS Server Construction with BIND9" p.93

As the root user, check the syntax of the zone file.

CentOS


/usr/sbin/named-checkzone localdomain /var/named/chroot/var/named/localdomain.zone

Ubuntu


/usr/sbin/named-checkzone localdomain /var/cache/bind/localdomain.zone

Book "DNS Server Construction with BIND9" p.94

Start BIND as the root user.

Common to CentOS and Ubuntu


#When you want to check the current status
systemctl status named.service

#When you want to start after stopping
systemctl stop named.service
systemctl start named.service

#When you want to start automatically when the OS boots
systemctl enable named.service

Book "DNS Server Construction with BIND9" p.100

Test the zone transfer as the root user.

CentOS


dig @192.168.79.128 localdomain AXFR

CentOS results


; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el8 <<>> @192.168.79.128 localdomain AXFR
; (1 server found)
;; global options: +cmd
localdomain.		86400	IN	SOA	ns.localdomain. root.localdomain. 2020052601 43200 5400 3600000 3600
localdomain.		86400	IN	NS	ns.localdomain.
localdomain.		86400	IN	MX	10 mail01.localdomain.
mail01.localdomain.	86400	IN	A	192.168.79.128
ns.localdomain.		86400	IN	A	192.168.79.128
localdomain.		86400	IN	SOA	ns.localdomain. root.localdomain. 2020052601 43200 5400 3600000 3600
;; Query time: 1 msec
;; SERVER: 192.168.79.128#53(192.168.79.128)
;; WHEN:Thu June 04 15:35:22 JST 2020
;; XFR size: 6 records (messages 1, bytes 217)

Ubuntu


dig @192.168.79.130 localdomain AXFR

Ubuntu results


; <<>> DiG 9.16.1-Ubuntu <<>> @192.168.79.130 localdomain AXFR
; (1 server found)
;; global options: +cmd
localdomain.		86400	IN	SOA	ns.localdomain. root.localdomain. 2020052601 43200 5400 3600000 3600
localdomain.		86400	IN	NS	ns.localdomain.
localdomain.		86400	IN	MX	10 mail01.localdomain.
mail01.localdomain.	86400	IN	A	192.168.79.130
ns.localdomain.		86400	IN	A	192.168.79.130
localdomain.		86400	IN	SOA	ns.localdomain. root.localdomain. 2020052601 43200 5400 3600000 3600
;; Query time: 3 msec
;; SERVER: 192.168.79.130#53(192.168.79.130)
;; WHEN:Thu June 04 15:36:11 JST 2020
;; XFR size: 6 records (messages 1, bytes 217)

Postfix settings

Book "Introduction to Postfix Practice" p.89

As the root user, stop if sendmail is running.

Common to CentOS and Ubuntu


ps ax | grep sendmail

Sendmail wasn't working in the environment in this article, so I didn't do anything.

Book "Introduction to Postfix Practice" p.104

Install Postfix as the root user.

CentOS


yum install postfix

Ubuntu


apt install postfix

Book "Introduction to Postfix Practice" p.105 (CentOS only)

As the root user, select Postfix with the alternatives command.

CentOS


alternatives --config mta

In the environment of this article, Postfix was the only option, so I didn't do anything.

Book "Introduction to Postfix Practice" p.121

As the root user, edit main.cf.

Common to CentOS and Ubuntu


cd /etc/postfix
cp main.cf main.cf.org
editor main.cf

CentOS

/etc/postfix/main.cf



(Omitted)

#myhostname = host.domain.tld
#myhostname = virtual.domain.tld
+myhostname = mail01.localdomain

(Omitted)

#mydomain = domain.tld
+mydomain = localdomain

(Omitted)

-#myorigin = $mydomain
+myorigin = $mydomain

(Omitted)

-mydestination = $myhostname, localhost.$mydomain, localhost
-#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
+#mydestination = $myhostname, localhost.$mydomain, localhost
+mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain

(Add the following to the end of the file)

+# Allowed to run :include: method in /etc/aliases
+alias_maps = hash:/etc/aliases
+alias_database = hash:/etc/aliases
+allow_mail_to_commands = alias,forward,include
+allow_mail_to_files    = alias,forward,include

Ubuntu

/etc/postfix/main.cf



# See /usr/share/postfix/main.cf.dist for a commented, more complete version


# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
# fresh installs.
compatibility_level = 2



# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_tls_security_level=may

smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level=may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache


smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
-#myhostname = sample.localdomain
+myhostname = mail01.localdomain
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
-mydestination = mail01.localhost.localdomain, $myhostname, sample, localhost.localdomain, localhost
+mydestination = mail01.localhost.localdomain, $myhostname, mail01, localhost.localdomain, localhost localdomain
relayhost = 
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = loopback-only
default_transport = error
relay_transport = error
inet_protocols = all
+allow_mail_to_commands = alias,forward,include
+allow_mail_to_files    = alias,forward,include

Book "Introduction to Postfix Practice" p.130

Start Postfix as the root user.

Common to CentOS and Ubuntu


#When you want to check the settings
postfix check

#When you want to check the current status
postfix status

#When you want to start after stopping
postfix stop
postfix start

#When you want to reload
postfix reload

Book "Introduction to Postfix Practice" p.134

As a general user, connect to SMTP with telnet and check the operation of Postfix.

Common to CentOS and Ubuntu


telnet localhost 25

EHLO localdomain

MAIL FROM:<usrname@localdomain>

RCPT TO:<usrname@localdomain>

DATA

This is test1
This is test2
This is test3
.

NOOP

QUIT

If you get the following error on the way,


RCPT TO:<usrname@localdomain>
451 4.3.0 <usrname@localdomain>: Temporary lookup failure

As the root user, do the following (book "Introduction to Postfix Practice" p.142), go back to the general user and re-execute from telnet.


postalias /etc/aliases
postfix reload

The screen image when executed is as follows.

CentOS


[usrname@localhost ~]$ telnet localhost 25
Trying ::1...
Connected to localhost.
Escape character is '^]'.
220 mail01.localdomain ESMTP Postfix
EHLO localdomain
250-mail01.localdomain
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250 SMTPUTF8
MAIL FROM:<usrname@localdomain>
250 2.1.0 Ok
RCPT TO:<usrname@localdomain>
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
This is test1
This is test2
This is test3
.
250 2.0.0 Ok: queued as 39B219AF86
NOOP
250 2.0.0 Ok
QUIT
221 2.0.0 Bye
Connection closed by foreign host.

Ubuntu


usrname@localhost:~$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail01.localdomain ESMTP Postfix (Ubuntu)
EHLO localdomain
250-mail01.localdomain
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250-SMTPUTF8
250 CHUNKING
MAIL FROM:<usrname@localdomain>
250 2.1.0 Ok
RCPT TO:<usrname@localdomain>
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
This is test1
This is test2
This is test3
.
250 2.0.0 Ok: queued as 49F8B2F816F8
NOOP
250 2.0.0 Ok
QUIT
221 2.0.0 Bye
Connection closed by foreign host.

Book "Introduction to Postfix Practice" p.141

As the root user, set the alias file. In addition to the procedure of the book, specify the command to be activated when receiving an email here. In this article, we will start "/ home / usrname / email_hook / hook" every time we receive an email. You will create a file named this hook later.

Common to CentOS and Ubuntu


editor /etc/aliases

CentOS


(End of file)

# Person who should get root's mail
#root:          marc
+root:           usrname
+
+# hook
+usrname: usrname, :include:/home/usrname/email_hook/hook

Ubuntu


# See man 5 aliases for format
postmaster:    root

# Person who should get root's mail
root:           usrname
+
+# hook
+usrname: usrname, :include:/home/usrname/email_hook/hook

Book "Introduction to Postfix Practice" p.142

As the root user, update aliases.db and reload Postfix.

Common to CentOS and Ubuntu


postalias /etc/aliases
postfix reload

Dovecot settings

Book "Introduction to Postfix Practice" p.215

Install Dovecot as the root user.

CentOS


yum install dovecot

Ubuntu


apt install dovecot-core dovecot-imapd dovecot-pop3d

Book "Introduction to Postfix Practice" p.222

As the root user, edit dovecot-openssl.cnf for your situation.

CentOS


editor /etc/pki/dovecot/dovecot-openssl.cnf

CentOS is as follows in my case.

/etc/pki/dovecot/dovecot-openssl.cnf



[ req ]
default_bits = 3072
encrypt_key = yes
distinguished_name = req_dn
x509_extensions = cert_type
prompt = no

[ req_dn ]
# country (2 letter code)
#C=FI
+C=JP

# State or Province Name (full name)
#ST=
+ST=TOKYO

# Locality Name (eg. city)
#L=Helsinki
+L=Chofu

# Organization (eg. company)
#O=Dovecot
+O=Kanedaq Office

# Organizational Unit Name (eg. section)
OU=IMAP server

# Common Name (*.example.com is also possible)
-CN=imap.example.com
+CN=mail01.localdomain

# E-mail contact
[email protected]
+emailAddress=postmaster@localdomain

[ cert_type ]
nsCertType = server

Ubuntu


editor /usr/share/dovecot/dovecot-openssl.cnf

Ubuntu didn't change the content this time.

/usr/share/dovecot/dovecot-openssl.cnf



#
# SSLeay configuration file for Dovecot.
#

RANDFILE                = /dev/urandom

[ req ]
default_bits            = 2048
default_keyfile         = privkey.pem
distinguished_name      = req_distinguished_name
prompt                  = no
policy                  = policy_anything
req_extensions          = v3_req
x509_extensions         = v3_req

[ req_distinguished_name ]
organizationName = Dovecot mail server
organizationalUnitName = @commonName@
commonName = @commonName@
emailAddress = @emailAddress@

[ v3_req ]
basicConstraints        = CA:FALSE

Book "Introduction to Postfix Practice" p.223

Recreate the certificate as root user.

CentOS


sh /usr/share/doc/dovecot/mkcert.sh

Ubuntu has the following command to run, but I didn't run it this time.


sh /usr/share/dovecot/mkcert.sh

Book "Introduction to Postfix Practice" p.227

As the root user, edit dovecot.conf.

Common to CentOS and Ubuntu


editor /etc/dovecot/dovecot.conf

CentOS

/etc/dovecot/dovecot.conf



(Omitted)

# Protocols we want to be serving.
#protocols = imap pop3 lmtp
+protocols = pop3

(Add the following to the end of the file)

+log_path = /var/log/dovecot.log
+disable_plaintext_auth = no    #Allow plaintext passwords for the time being (insecure)

Ubuntu

/etc/dovecot/dovecot.conf



(Omitted)

# Enable installed protocols
-!include_try /usr/share/dovecot/protocols.d/*.protocol
+!include_try /usr/share/dovecot/protocols.d/pop3d.protocol

(Add the following to the end of the file)

+log_path = /var/log/dovecot.log
+disable_plaintext_auth = no    #Allow plaintext passwords for the time being (insecure)

Book "Introduction to Postfix Practice" p.228

As the root user, edit the mailbox location.

Common to CentOS and Ubuntu


editor /etc/dovecot/conf.d/10-mail.conf

CentOS

/etc/dovecot/conf.d/10-mail.conf



(Omitted)

#mail_location = 
+mail_location = mbox:~/mail:INBOX=/var/mail/%u

(Omitted)

Ubuntu was already set up as intended, as shown below, so I didn't change it.

/etc/dovecot/conf.d/10-mail.conf



(Omitted)

mail_location = mbox:~/mail:INBOX=/var/mail/%u

(Omitted)

Book "Introduction to Postfix Practice" p.229

Start Dovecot as the root user.

Common to CentOS and Ubuntu


#When you want to check the current status
systemctl status dovecot.service

#When you want to start after stopping
systemctl stop dovecot.service
systemctl start dovecot.service

#When you want to start automatically when the OS boots
systemctl enable dovecot.service

Book "Introduction to Postfix Practice" p.230

As the root user, check your Dovecot launch settings.

Common to CentOS and Ubuntu


systemctl list-unit-files -t service | grep dovecot

Book "Introduction to Postfix Practice" p.231

As the root user, check for open ports.

Common to CentOS and Ubuntu


netstat -ln | grep tcp

CentOS results


tcp        0      0 0.0.0.0:5355            0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:110             0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN     
tcp        0      0 192.168.79.128:53       0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:53            0.0.0.0:*               LISTEN     
tcp        0      0 192.168.122.1:53        0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:953           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:995             0.0.0.0:*               LISTEN     
tcp6       0      0 :::5355                 :::*                    LISTEN     
tcp6       0      0 :::110                  :::*                    LISTEN     
tcp6       0      0 :::111                  :::*                    LISTEN     
tcp6       0      0 :::53                   :::*                    LISTEN     
tcp6       0      0 :::22                   :::*                    LISTEN     
tcp6       0      0 ::1:631                 :::*                    LISTEN     
tcp6       0      0 ::1:25                  :::*                    LISTEN     
tcp6       0      0 ::1:953                 :::*                    LISTEN     
tcp6       0      0 :::995                  :::*                    LISTEN     

Ubuntu results


tcp        0      0 172.17.0.1:53           0.0.0.0:*               LISTEN     
tcp        0      0 192.168.79.130:53       0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:53            0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:953           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:995             0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:110             0.0.0.0:*               LISTEN     
tcp6       0      0 fe80::f8e9:90fd:cfc4:53 :::*                    LISTEN     
tcp6       0      0 ::1:53                  :::*                    LISTEN     
tcp6       0      0 ::1:631                 :::*                    LISTEN     
tcp6       0      0 ::1:25                  :::*                    LISTEN     
tcp6       0      0 ::1:953                 :::*                    LISTEN     
tcp6       0      0 :::995                  :::*                    LISTEN     
tcp6       0      0 :::110                  :::*                    LISTEN     

Book "Introduction to Postfix Practice" p.232

As a general user, check the operation of the POP server with telnet. Before that, CentOS required the following as the root user:

CentOS


chmod 0600 /var/mail/*
chmod 0600 /var/spool/mail/*

Now start telnet.

Common to CentOS and Ubuntu


telnet localhost 110

USER usrname

PASS secret

LIST

RETR 1

QUIT

The screen image when executed is as follows.

CentOS


[usrname@localhost ~]$ telnet localhost 110
Trying ::1...
Connected to localhost.
Escape character is '^]'.
+OK Dovecot ready.
USER usrname
+OK
PASS secret
+OK Logged in.
LIST
+OK 1 messages:
1 463
.
RETR 1
+OK 463 octets
Return-Path: <usrname@localdomain>
X-Original-To: usrname@localdomain
Delivered-To: usrname@localdomain
Received: from localdomain (localhost [IPv6:::1])
	by mail01.localdomain (Postfix) with ESMTP id 39B219AF86
	for <usrname@localdomain>; Thu,  4 Jun 2020 17:52:38 +0900 (JST)
Message-Id: <[email protected]>
Date: Thu,  4 Jun 2020 17:52:38 +0900 (JST)
From: usrname@localdomain

This is test1
This is test2
This is test3
.
QUIT
+OK Logging out.
Connection closed by foreign host.

Ubuntu


usrname@localhost:~$ telnet localhost 110
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
+OK Dovecot (Ubuntu) ready.
USER usrname
+OK
PASS secret
+OK Logged in.
LIST
+OK 1 messages:
1 468
.
RETR 1
+OK 468 octets
Return-Path: <usrname@localdomain>
X-Original-To: usrname@localdomain
Delivered-To: usrname@localdomain
Received: from localdomain (localhost [127.0.0.1])
	by mail01.localdomain (Postfix) with ESMTP id 49F8B2F816F8
	for <usrname@localdomain>; Thu,  4 Jun 2020 17:08:33 +0900 (JST)
Message-Id: <[email protected]>
Date: Thu,  4 Jun 2020 17:08:33 +0900 (JST)
From: usrname@localdomain

This is test1
This is test2
This is test3
.
QUIT
+OK Logging out.
Connection closed by foreign host.

This completes the construction of the Postfix mail server.

Implementation of code that is activated when an email is received

Subsequent work will be done by general users. There is no difference in working between CentOS and Ubuntu.

I will create a subdirectory (email_hook) under my home directory (/ home / usrname) and put all my files here.


cd
mkdir email_hook
cd email_hook

Subsequent work is done under / home / usrname / email_hook.

Create a new command that is started when an email is received

Create a new file with the name hook (whatever the name is) and write the command there.


editor hook

The content of the command passes the mail received by Postfix through a pipeline to Python code (which we will implement later) called hook_slack.py.


"|LC_CTYPE='C.UTF-8' /usr/bin/python3 /home/usrname/email_hook/hook_slack.py || true"

Create new Python code that is launched when an email is received

Create a new file with the file name hook_slack.py (any name you like).


editor hook_slack.py

The contents are as stated below. Forgive me that the code isn't very well-behaved. Make sure you set the appropriate URL for Slack Incoming Webhooks.

/home/usrname/email_hook/hook_slack.py



import logging
import os
import sys
import time
import datetime
import json
import email.parser
import urllib.request
import urllib.parse
from pathlib import Path


def make_logfile_path(file):
    directory = Path("/home/usrname/email_hook/log")

    #Create if no directory
    directory.mkdir(parents=True, exist_ok=True)

    #Returns the full path log file name
    return directory / os.path.basename(os.path.splitext(file)[0] + datetime.datetime.today().strftime("_%Y%m%d_%H%M%S.log"))


def get_logger(name, filepath):
    LOG_LEVEL_FILE = logging.DEBUG
    LOG_LEVEL_CONSOLE = logging.INFO

    _detail_formatting = "\n%(asctime)s %(levelname)-8s [%(module)s#%(funcName)s %(lineno)d]\n%(message)s"

    logging.basicConfig(
        level=LOG_LEVEL_FILE,
        format=_detail_formatting,
        filename=filepath
    )

    #Create a handler console that sends logs to the console
    console = logging.StreamHandler()
    console.setLevel(LOG_LEVEL_CONSOLE)
    console_formatter = logging.Formatter(_detail_formatting)
    console.setFormatter(console_formatter)

    #Get the logger and add a console handler
    logger = logging.getLogger(name)
    logger.addHandler(console)
    return console, logger


#Logger
console, logger = get_logger(__name__, make_logfile_path(__file__))


def main():
    start = time.time()
    logger.info(f"hook started : {time.strftime('%d %b %X', time.localtime(start))}")

    #Receive mail received by Postfix from standard input
    mime_str = sys.stdin.read()
    logger.debug(f"mime_str={mime_str}")
    message = email.parser.Parser().parsestr(mime_str)
    logger.debug(f"message={message}")

    ##URL for Slack Incoming Webhooks
    url = "secret"

    payload = {}
    logger.debug(f'message.get("Subject")={message.get("Subject")}')
    logger.debug(f"message.get_payload()={message.get_payload()}")
    payload["text"] = message.get("Subject") + "\n" + message.get_payload(0).get_payload()
    logger.debug(f"payload={payload}")

    data = json.dumps(payload).encode("utf-8")
    logger.debug(f"data={data}")

    request = urllib.request.Request(url, data)
    urllib.request.urlopen(request)

    stop = time.time()
    delta = stop - start
    logger.info(f"hook started : {time.strftime('%d %b %X', time.localtime(start))}")
    logger.info(f"hook finished : {time.strftime('%d %b %X', time.localtime(stop))}")
    logger.info("hook duration : {:0.3} seconds".format(delta))

if __name__ == "__main__":
    try:
        main()
    except Exception as ee:
        logger.exception(ee)

We have prepared the data for Slack post test of hook_slack.py.py.


editor hook_test_stdin.txt

/home/usrname/email_hook/hook_test_stdin.txt



From usrname@localdomain  Wed Jun  3 21:41:32 2020
Return-Path: <usrname@localdomain>
X-Original-To: usrname@localdomain
Delivered-To: usrname@localdomain
Received: from localhost.localdomain (localhost [127.0.0.1])
	by mail01.localdomain (Postfix) with ESMTPS id 9630E40D18C9
	for <usrname@localdomain>; Wed,  3 Jun 2020 21:41:32 +0900 (JST)
Content-Type: multipart/mixed; boundary="===============8887878637416477407=="
MIME-Version: 1.0
Subject:Slack post test subject
From: usrname@localdomain
To: usrname@localdomain
Date: Wed, 03 Jun 2020 12:41:32 -0000
Message-Id: <[email protected]>

--===============8887878637416477407==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

Slack post test body
--===============8887878637416477407==--

Let's run hook_slack.py and test if it is posted to Slack.


cat hook_test_stdin.txt | LC_CTYPE='C.UTF-8' /usr/bin/python3 ./hook_slack.py || true

A log file was output under the log subdirectory and posted to Slack as follows:

slack1.png

New Python code to send test email

Create a new file with the file name send_testmail.py (any name you like).


editor send_testmail.py

The contents are as stated below.

/home/usrname/email_hook/send_testmail.py



import smtplib
from email.mime.text import MIMEText
from email.utils import formatdate
from email.mime.multipart import MIMEMultipart


def create_message(from_addr, to_addr, subject, body):
    #header
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Date'] = formatdate()

    #Text
    msg.attach(MIMEText(body))

    return msg


def send_mail(from_addr, to_addr, body_msg):
    smtpobj = smtplib.SMTP("localhost", 25)
    smtpobj.ehlo()
    smtpobj.starttls()
    smtpobj.ehlo()
    smtpobj.sendmail(from_addr, to_addr, body_msg.as_string())
    smtpobj.close()


MAIL_ADDRESS = "usrname@localdomain"

from_addr = MAIL_ADDRESS
to_addr = MAIL_ADDRESS
subject = "test mail"
body = "We'll send you a test email."

msg = create_message(from_addr, to_addr, subject, body)
send_mail(from_addr, to_addr, msg)

Run this program to send an email and test if the hook launches.


python3 ./send_testmail.py

It was posted on Slack as follows.

slack2.png

List of things I want to do in the future

--Push mail using IMAP IDLE --Set up a mail server on your VPS and set up security etc. --It seems that many of MetaTrader's indicators and EA have an email sending function, so I would like to have the EA send an email to the local server and do some processing with the hook program.

that's all.

Recommended Posts

Build a Postfix mail server, start Python code triggered by mail reception, and post mail to Slack (local environment)
Python environment construction and TensorFlow
Build a Postfix mail server, start Python code triggered by mail reception, and post mail to Slack (local environment)
Environment construction of python and opencv
python sql statement extracted by time
Easy modeling with Blender and Python
Try to build python and anaconda environment on Mac (by pyenv, conda)
Build a Python environment and transfer data to the server
[Python] How to create a local web server environment with SimpleHTTPServer and CGIHTTPServer
How to build Python and Jupyter execution environment with VS Code
I want to build a Python environment
Build a CentOS Linux 8 environment with Docker and start Apache HTTP Server
Try to build python and anaconda environment on Mac (by pyenv, conda)
Send a message from Slack to a Python server
Create a Django project and application in a Python virtual environment and start the server
Build a python execution environment with VS Code
How to build a beautiful Python environment on a new Mac and install Jupter Notebook
Build a python virtual environment with virtualenv and virtualenvwrapper
How to build a Django (python) environment on docker
Build a python virtual environment with virtualenv and virtualenvwrapper
How to build a Python environment on amazon linux 2
How to build a Python virtual execution environment using Visual Studio Code and pipenv on a Windows machine (also Jupyter notebook)
How to start a simple WEB server that can execute cgi of php and python
[Sakura Rental Server] (For beginners) How to build an environment for Python, pyenv, and Flask. | For csh
How to build a new python virtual environment on Ubuntu
How to build a python2.7 series development environment with Vagrant
Build a Python environment with WSL + Pyenv + Jupyter + VS Code
Build a python environment on CentOS 7.7 for your home server
Post from Python to Slack
Post to vim → Python → Slack
Post to slack with Python 3
Build a Python environment offline
Post to Slack in Python
How to build a LAMP environment using Vagrant and VirtulBox Note
Build and test a CI environment for multiple versions of Python
Build a 64-bit Python 2.7 environment with TDM-GCC and MinGW-w64 on Windows 7
Build a local development environment for Lambda + Python using Serverless Framework
Build a Python environment on your Mac with Anaconda and PyCharm
Try to create a python environment with Visual Studio Code & WSL
How to build a Python environment using Virtualenv on Ubuntu 18.04 LTS
Create a simple Python development environment with VS Code and Docker
How to debug a Python program by remotely connecting to a Docker container in WSL2 environment with VS Code
Build a development environment using Jupyter and Flask with Python in Docker (supports both VS Code / code-server)
Build a python3 environment on CentOS7
I tried to communicate with a remote server by Socket communication with Python.
I was addicted to creating a Python venv environment with VS Code
Steps to create a Python virtual environment with VS Code on Windows
Migration from Python2 to Python3 (Python2 is rebuilt as a virtual environment and coexists)
How to install python package in local environment as a general user
Quickly build a python environment for deep learning and data science (Windows)
I tried to build a Mac Python development environment with pythonz + direnv
Build Linux on a Windows environment. Steps to install Laradock and migrate
Build a lightweight server in Python and listen for Scratch 2 HTTP extensions