ابزارهای Self-Hostedمهندسانسطح متوسط

ایمیل تحویل داده می‌شود ولی Inbox خالی است؛ تله‌ی mail_location

ایمیل در لاگ Postfix «تحویل داده» می‌شود اما کاربر هنگام اتصال IMAP صندوق خالی می‌بیند. علت رایج، اختلاف مسیر تحویل میان Postfix و mail_location در Dovecot است.

تحریریه کلودیکپ ۷ دقیقه مطالعه
ایمیل تحویل داده می‌شود ولی Inbox خالی است؛ تله‌ی mail_location

مسئله: ایمیل «تحویل داده شد»، ولی صندوق خالی است

سناریو را ببینید. کاربر می‌گوید ایمیلی به من نمی‌رسد، یا ایمیل‌هایی که ارسال شده‌اند در Inbox نیستند. سراغ /var/log/mail.log می‌روید و می‌بینید همه‌چیز از نظر Postfix سالم است:

postfix/lmtp[29341]: 4Xk2...: to=<ali@example.com>, relay=example.com[/private/dovecot-lmtp], delay=0.12, status=sent (250 2.0.0 <ali@example.com> Saved)

یا اگر LDA در مسیر باشد:

postfix/local[29344]: 4Xk2...: to=<ali@example.com>, relay=local, delay=0.21, status=sent (delivered to maildir)

ولی به محض اینکه کاربر با Thunderbird یا Roundcube وصل می‌شود، INBOX خالی است. در /var/log/dovecot.log معمولاً یکی از این خطوط را می‌بینید:

imap(ali@example.com)<...>: Error: chdir(/home/ali/Maildir) failed: No such file or directory

یا این:

Error: open(/var/vmail/example.com/ali/Maildir/dovecot-uidlist) failed: Permission denied

حتی ممکن است هیچ خطایی هم نبینید؛ Dovecot یک صندوق خالی می‌سازد و همه‌چیز از بیرون سالم به نظر می‌رسد. نکته‌ی کلیدی این است: Postfix پیام را در یک مسیر می‌نشاند و Dovecot مسیر دیگری را نگاه می‌کند. کاربر نه خطا می‌بیند، نه ایمیل.

علت ریشه‌ای: دو جفت کلید که با هم هماهنگ نیستند

در یک سرور ایمیل، تحویل پیام و ارائه‌ی IMAP دو لایه‌ی جداست. Postfix مسئول گرفتن پیام است و Dovecot مسئول نمایش آن. اگر این دو درباره‌ی «محل صندوق» توافق نداشته باشند، پیام در یک جا می‌نشیند و کاربر جای دیگری را نگاه می‌کند. سه منبع تفرق را بررسی کنید:

  • mbox در برابر Maildir: Postfix با home_mailbox = Maildir/ در main.cf به ~/Maildir/ می‌نویسد، ولی مقدار پیش‌فرض Dovecot روی بعضی توزیع‌ها mbox:~/mail:INBOX=/var/mail/%u است. دو مسیر کاملاً متفاوت.
  • مبنای virtual_mailbox: در حالت کاربران مجازی، Postfix با virtual_mailbox_base به یک مسیر می‌نویسد و Dovecot با mail_location مسیر را می‌خواند. اگر یکی %d/%n و دیگری %u باشد، مسیرها از هم جدا می‌شوند.
  • mailbox_command: اگر mailbox_command تنظیم شده باشد (مثلاً dovecot-lda یا procmail)، تحویل به آن سپرده می‌شود و home_mailbox عملاً بی‌اثر است. خیلی‌ها هر دو را با هم دارند و از همین‌جا سردرگم می‌شوند.

و یک تله‌ی ظریف که خیلی وقت‌ها ندیده می‌ماند: lmtp_save_to_detail_mailbox = yes در Dovecot. اگر این گزینه فعال باشد، پیام‌های user+tag@example.com به یک زیرپوشه می‌روند، نه به INBOX. کاربر فکر می‌کند ایمیل نیامده، در حالی که پیام داخل INBOX/tag نشسته است.

در محیط‌هایی که SELinux یا AppArmor فعال است، یک لایه‌ی سوم هم اضافه می‌شود: حتی اگر مسیر درست باشد، پروفایل امنیتی ممکن است اجازه‌ی نوشتن روی /var/vmail را ندهد و علائم دقیقاً به شکل «صندوق خالی» دیده شود. در آن حالت audit.log یا dmesg را هم چک کنید.

راه‌حل گام‌به‌گام

گام ۱: کشف لایه‌ی تحویل

اول ببینید Postfix واقعاً چه مسیری را انتخاب می‌کند:

postconf -n | grep -E "home_mailbox|virtual_transport|mailbox_command|virtual_mailbox_base|virtual_mailbox_maps"

سه حالت رایج را می‌بینید. اگر virtual_transport = lmtp:unix:private/dovecot-lmtp است، کل تحویل به Dovecot سپرده شده و Postfix خودش به دیسک دست نمی‌زند. اگر virtual_transport = virtual است، Postfix خودش می‌نویسد. اگر mailbox_command تنظیم شده، یک LDA جدا وسط ماجراست و باید در همان مسیر مورد توافق هر دو طرف بنویسد.

گام ۲: Dovecot چه مسیری را انتظار دارد؟

doveconf -n | grep -E "mail_location|mail_home|mail_uid|mail_gid"
doveadm user ali@example.com

دستور doveadm user مستقیماً home و mail_location را نشان می‌دهد. یک نمونه خروجی:

userdb:
  ali@example.com
  home: /var/vmail/example.com/ali
  mail: maildir:/var/vmail/example.com/ali/Maildir
  uid: 5000
  gid: 5000

این خروجی، مرجع شما است. هر مسیری که Dovecot اینجا اعلام می‌کند باید با مسیری که Postfix پیام را در آن می‌نشاند، یکسان باشد.

گام ۳: هم‌ترازسازی در حالت virtual users (توصیه‌شده)

سالم‌ترین الگو در محیط عملیاتی این است که تحویل را به LMTP بسپارید تا Dovecot خودش مسیر را تعیین کند و امکان اختلاف از اساس حذف شود.

در /etc/postfix/main.cf:

virtual_mailbox_domains = example.com
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_transport = lmtp:unix:private/dovecot-lmtp

در /etc/postfix/vmailbox فقط وجود کاربر را تعریف کنید؛ خودِ مسیر را Dovecot می‌سازد:

ali@example.com    example.com/ali/

سپس:

postmap /etc/postfix/vmailbox
systemctl reload postfix

در سمت Dovecot، فایل /etc/dovecot/conf.d/10-mail.conf:

mail_location = maildir:/var/vmail/%d/%n/Maildir
mail_uid = vmail
mail_gid = vmail

اگر userdb از SQL می‌آید، ستون home و mail را دقیقاً با همان الگو هم‌تراز کنید:

SELECT '/var/vmail/' || maildir AS home,
       'maildir:/var/vmail/' || maildir || '/Maildir' AS mail,
       5000 AS uid, 5000 AS gid
FROM mailbox
WHERE username = '%u'

گام ۴: هم‌ترازی در حالت system users

اگر کاربران واقعی سیستم هستید و می‌خواهید Maildir استفاده کنید، هر دو طرف را روی یک مسیر ثابت قفل کنید. در main.cf:

home_mailbox = Maildir/

در 10-mail.conf:

mail_location = maildir:~/Maildir

برعکس، اگر مایل به استفاده از mbox هستید (که در محیط‌های تولیدی توصیه نمی‌شود)، home_mailbox را حذف کنید و مقصد پیش‌فرض /var/mail/%u را صراحتاً در Dovecot اعلام کنید. هرگز هر دو حالت را همزمان فعال نگذارید.

گام ۵: مالکیت و مجوزها

حتی اگر مسیرها یکی باشند، مالکیت اشتباه همان علامت را می‌دهد. در حالت virtual:

chown -R vmail:vmail /var/vmail
chmod -R 0700 /var/vmail/example.com

در حالت system users، مطمئن شوید /home/ali حداقل 0711 است و ~/Maildir متعلق به خود کاربر با 0700:

chmod 0711 /home/ali
chown -R ali:ali /home/ali/Maildir

گام ۶: انتقال پیام‌های قدیمی از مسیر قبلی

قبل از هر چیز یک بکاپ بگیرید، سپس محتوای مسیر قدیمی را با rsync منتقل کنید (نه mv؛ چون ممکن است همزمان نوشتن در جریان باشد):

systemctl stop postfix dovecot
rsync -aHAX --info=progress2 /var/mail/ali/ /home/ali/Maildir/
chown -R ali:ali /home/ali/Maildir
systemctl start dovecot postfix

اگر مسیر قبلی mbox بود، از ابزار تبدیل استفاده کنید:

doveadm import -u ali@example.com mbox:/var/mail/ali INBOX

بررسی اینکه مشکل واقعاً حل شده است

سه سطح تست انجام دهید. سطح اول، احراز هویت و مسیر:

doveadm auth test ali@example.com
doveadm user ali@example.com

سطح دوم، خودِ صندوق:

doveadm mailbox list -u ali@example.com
doveadm search -u ali@example.com mailbox INBOX ALL

اگر تعداد پیام‌ها در خروجی doveadm search با انتظار شما هم‌خوانی داشت، سطح سوم: یک ایمیل آزمایشی بفرستید و لاگ‌ها را رصد کنید:

swaks --to ali@example.com --from test@example.com --server 127.0.0.1
tail -f /var/log/mail.log /var/log/dovecot.log

در لاگ Postfix انتظار دارید ببینید:

status=sent (250 2.0.0 <ali@example.com> Saved)

و بلافاصله پس از آن در Dovecot:

lmtp(ali@example.com): ...: msgid=<...>: saved mail to INBOX

اگر خط «saved mail to INBOX» را دیدید، کار تمام است. حالا با یک کلاینت IMAP واقعی وارد شوید و مطمئن شوید پیام دیده می‌شود. اگر کاربر از دو کلاینت استفاده می‌کند (مثلاً webmail و موبایل) و یکی خالی است، احتمالاً یکی از کلاینت‌ها با +detail یا با پیشوند متفاوت وصل می‌شود. یک بار جست‌وجو را روی همه‌ی پوشه‌ها بزنید تا ببینید پیام دقیقاً کجا نشسته است:

doveadm search -u ali@example.com mailbox '*' ALL

پیشگیری و بهترین روش‌ها

  • یک منبع حقیقت انتخاب کنید. در محیط با کاربران مجازی، LMTP تنها منبع حقیقت است. Postfix نباید هیچ‌گاه مستقیماً به دیسک بنویسد. این کار اختلاف مسیر را از اساس حذف می‌کند.
  • mail_location را در IaC تست کنید. یک تسک ادواری با Ansible یا Puppet که doveadm user را برای چند کاربر نمونه اجرا و خروجی را با مسیر تحویل مورد انتظار مقایسه کند، این تله را همان روز کشف می‌کند، نه بعد از یک هفته سکوت.
  • تست smoke پس از نصب و مهاجرت. یک اسکریپت با swaks که ایمیلی بفرستد و بلافاصله با doveadm search وجودش را تأیید کند، به شما سیگنال قطعی می‌دهد؛ نه «به نظر می‌رسد کار می‌کند».
  • از تعارض پیکربندی پرهیز کنید. اگر mailbox_command تنظیم شده، home_mailbox عملاً بی‌اثر است و فقط ذهن را گمراه می‌کند. یکی را حذف کنید.
  • پیش از سوییچ DNS، مسیرها را در staging همگام کنید. تغییر پیش‌فرض mail_location در نسخه‌های جدید Dovecot یک سورپرایز بی‌صداست.
  • مالکیت و مجوزها را جزو پیکربندی حساب کنید. بخش زیادی از خطاهای «صندوق خالی» در واقع Permission denied روی ~/Maildir هستند که فقط به شکل خالی‌بودن خود را نشان می‌دهند؛ audit.log و SELinux را هم فراموش نکنید.

خلاصه‌ی حرف: وقتی Postfix می‌گوید «delivered»، کار تمام نشده است. تا وقتی Dovecot همان مسیر را نبیند، کاربر هیچ‌چیز نمی‌بیند. با LMTP تحویل بدهید، mail_location را ثابت و صریح نگه دارید و تحویل end-to-end را در استیجینگ تست کنید — نه با حدس.

اشتراک‌گذاری:

نویسنده

تحریریه کلودیکپ

تیم محتوای کلودیکپ

تیم فنی و محتوای کلودیکپ؛ مهندسانی که هر روز با DevOps، Kubernetes و زیرساخت ابری کار می‌کنند و تجربه‌هایشان را اینجا می‌نویسند.

سوالات متداول

سوالات متداول این مقاله

چون تحویل و نمایش دو لایه‌ی جداست. Postfix پیام را در مسیری می‌نویسد و Dovecot مسیر دیگری را برای IMAP می‌خواند. با doveadm user بفهمید Dovecot چه home و mail_location‌ای انتظار دارد و آن را با مسیر واقعی تحویل هم‌تراز کنید.

mbox همه‌ی پیام‌ها را در یک فایل نگه می‌دارد و در محیط‌های پرترافیک باعث قفل‌شدن و ریسک خرابی می‌شود. Maildir هر پیام را در یک فایل جدا می‌گذارد، سازگاری بهتری با IMAP و ابزارهای موازی دارد و برای سرورهای امروزی انتخاب پیش‌فرض است.

با doveadm user ali@example.com که مستقیماً home و mail_location را چاپ می‌کند. برای بررسی فولدرها هم از doveadm mailbox list و doveadm search استفاده کنید تا ببینید پیام‌ها دقیقاً در کدام مسیر نشسته‌اند.

ادامه مطالعه

مقالات مرتبط

رفع خطای no SASL authentication mechanisms در Postfix
ابزارهای Self-Hostedمتوسط

رفع خطای no SASL authentication mechanisms در Postfix

وقتی smtpd پورت ۵۸۷ با خطای «no SASL authentication mechanisms» بالا نمی‌آید، ریشه معمولاً سوکت Dovecot است که داخل chroot پست‌فیکس ساخته نشده. این مقاله علت و راه‌حل کامل را قدم‌به‌قدم بررسی می‌کند.

تحریریه کلودیکپ ۶ دقیقه مطالعه
رفع dkim=none در Postfix با راه‌اندازی OpenDKIM
ابزارهای Self-Hostedمتوسط

رفع dkim=none در Postfix با راه‌اندازی OpenDKIM

اگر ایمیل‌های سرور Postfix شما در Gmail به اسپم می‌روند و هدر پیام dkim=none نشان می‌دهد، یعنی هیچ امضای DKIM روی پیام‌ها درج نمی‌شود. در این مقاله OpenDKIM را نصب، پیکربندی و به Postfix وصل می‌کنیم و رکورد DNS را منتشر می‌کنیم.

تحریریه کلودیکپ ۶ دقیقه مطالعه

در پیاده‌سازی به کمک نیاز دارید؟

تیم کلودیکپ همین کار را هر روز برای تیم‌های دیگر انجام می‌دهد. اگر جایی گیر کرده‌اید، با ما صحبت کنید.