Мой почтовый setup
Не могу не процитировать: "All mail clients suck. This one just sucks less."
Эта фраза стала визитной карточкой программы Mutt. И хотя ей уже больше 20 лет, увы, ее актуальность ничуть не снизилась.
Не буду дискутировать о проблемах различных MUA, расскажу какой выбор сделал я.
Центральным компонентом системы является сервер на котором перманентно работают fetchmail и imapfilter.
Программа fetchmail запущена в нескольких экземплярах, чтобы забирать почту с различных аккаунтов.
С некоторых аккаунтов забирается протоколом IMAP, с других (например GMail) - удобнее использовать POP3.
Вся почта забирается с удалением на сервере.
Забранная почта без какой-либо обработки сбрасывается на входящий SMTP основного хранилища, которое настроено таким образом, чтобы все входящие письма оказывались в IMAP каталоге spool.
Пример конфигурации fetchmail (~/.fetchmailrc):
set daemon 60
skip dovecot via mail.example.com proto imap timeout 300 tracepolls user login1 there is login@example.com here ssl smtphost my.smtphost.com batchlimit 10 fetchall idle
skip google via imap.gmail.com proto pop3 timeout 300 tracepolls user login2 there is login@example.com here ssl smtphost my.smtphost.com batchlimit 10 fetchall
Перезапускается сбор почты таким скриптом (~bin/restart.fetchmail):
#!/bin/sh
mkdir -p ~/var/log ~/var/run
for a in dovecot google; do
logfile=~/var/log/fetchmail.$a.log
pidfile=~/var/run/fetchmail.$a.pid
test -f $pidfile && kill `head -1 $pidfile`
touch $logfile
fetchmail --logfile $logfile --pidfile $pidfile $a
done
Программа imapfilter запущена в единственном экземпляре и при помощи IMAP IDLE постоянно мониторит каталог spool на предмет новой входящей почты.
Обнаружив ее - обрабатывает в соответствии с конфигурационным файлом: раскладывает по другим каталогам, маркирует как важное, прочтенное или просто удаляет.
Ее конфигурация (~/.imapfilter/config.lua) пишется на Lua и выглядит примерно так:
options.timeout = 10
imap = IMAP {
server = 'imap.example.com',
username = 'login',
}
-- filtering
function loop()
-- all incoming
msgs = imap.spool:select_all()
-- maintenance reports
res = msgs:contain_subject('daily run output') *
msgs:contain_from('root@')
res:move_messages(imap.daily)
msgs = msgs - res
res = msgs:contain_subject('weekly run output') *
msgs:contain_from('root@')
res:move_messages(imap.weekly)
msgs = msgs - res
-- maillists
res = msgs:contain_field('X-BeenThere','nginx@nginx.org')
res:move_messages(imap['nginx'])
msgs = msgs - res
res = msgs:contain_field('X-BeenThere','nginx-ru@nginx.org')
res:move_messages(imap['nginx-ru'])
msgs = msgs - res
res = msgs:contain_field('X-BeenThere','freebsd-announce@freebsd.org')
res:move_messages(imap['freebsd-announce'])
msgs = msgs - res
res = msgs:contain_field('X-BeenThere','freebsd-security@freebsd.org')
res:move_messages(imap['freebsd-security'])
msgs = msgs - res
-- all the rest
msgs:move_messages(imap.INBOX)
-- expunge reports
res = imap.daily:is_older(30)
res:delete_messages()
res = imap.weekly:is_older(365)
res:delete_messages()
-- expunge trash
res = imap.trash:is_older(3)
res:delete_messages()
-- waiting...
imap.spool:enter_idle()
end
-- daemonize
become_daemon(3,loop)
Скрипт перезапуска выглядит совсем просто (~/bin/restart.imapfilter):
#!/bin/sh
pkill -u`whoami` imapfilter
imapfilter
В местах чтения почты (workstations) используется программа isync, в задачу которой входит двухсторонняя синхронизация локальных почтовых каталогов с удаленным IMAP.
Ее конфигурация (~/.mbsyncrc) может выглядеть так:
# global configuration section
CopyArrivalDate yes
Create Both
Expunge Both
SyncState *
# local store
MaildirStore local
Flatten .
Inbox ~/Mail/INBOX
Path ~/Mail/
# IMAP accounts
IMAPAccount storage
Host my.example.com
User login
PassCmd "security find-generic-password -w -a login -s my.example.com"
SSLType STARTTLS
SSLVersions TLSv1.2
TimeOut 60
CertificateFile ~/.mbsync/my.example.com.crt
# remote store
IMAPStore storage
Account storage
# INBOX only
Channel inbox
Master :storage:
Slave :local:
# important mailboxes
Channel main
Master :storage:
Slave :local:
Pattern INBOX foo-% trash
# full set of mailboxes
Channel full
Master :storage::
Slave :local:
Pattern *
Pattern !Sent !Spam !spool
Для периодического запуска isync в отдельном терминале используется простой скрипт (~/bin/posty):
#!/bin/sh
clear=30 # clear screen every n-th loop
full=20 # do full sync every n-th loop
main=10 # do main folders sync every n-th loop
sleep=60 # sleep after loop complete
if [ $# -eq 1 ]; then
sleep=$1
fi
left="\e[D"
loop=0
while true; do
if [ `expr $loop % $clear` -eq 0 ]; then
clear
fi
if [ ` expr $loop % $full` -eq 0 ]; then
channel=full
color="0;31"
elif [ ` expr $loop % $main` -eq 0 ]; then
channel=main
color="0;32"
else
channel=inbox
color="0;34"
fi
start=`date +%s`
printf "#%04d \e[${color}m%-7s\e[0m at `date -jr $start +%H:%M:%S` ... " $loop "[$channel]"
mbsync -q $channel
finish=`date +%s`
printf "elapsed %2ds, waiting ${sleep}s: " `expr $finish - $start`
for i in `jot $sleep`; do
sleep 1
if [ $i -gt 1 ]; then
for j in `jot $(expr $(($i-1)) : '.*')`; do
printf $left
done
printf $left
fi
printf "$i "
done
printf "$left, awaked!\n"
(( loop+=1 ))
done
Далее, с локальными каталогами работает непосредственно Mutt в котором для форсированной синхронизации определены следующие макросы:
# sync IMAP
macro index ,sf ': unset wait_key<enter><shell-escape>mbsync full<enter>: set wait_key<enter>'
macro index ,si ': unset wait_key<enter><shell-escape>mbsync inbox<enter>: set wait_key<enter>'
macro index ,sm ': unset wait_key<enter><shell-escape>mbsync main<enter>: set wait_key<enter>'
Плюсы решения
- унифицированная server-side обработка всей входящей корреспонденции, в независимости от типа проверяемого аккаунта
- быстрая работа Mutt в локальных каталогах
- возможность удобного поиска в почтовых архивах
- распределенность системы хранения почты и возможность ее резервного копирования
Слабые стороны
- неудобство работы с HTML письмами, решается доступом к IMAP с помощью GUI клиентов и почтового веб-интерфейса Roundcube