Migration of the Maildir standard from Dovecot 2.3 to Dovecot 2.4
Hello everyone,
I have a Dovecot 2.3 installation on a Debian 12 Linux system that is being upgraded to Dovecot 2.4. The server hosts many domains with numerous email accounts. Users had created folder structures containing many received and sent emails, following the Maildir standard as "dot-prefixed folders."
When switching to Dovecot 2.4, all these emails and folders are no longer visible. This is what it looks like on the old system:
root@mx1 /var/vmail/example.com/systemmails # ll -a insgesamt 256 drwx------ 14 vmail vmail 4096 3. Nov 09:15 . drwx------ 15 vmail vmail 4096 6. Apr 2021 .. drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Archive drwx------ 2 vmail vmail 86016 27. Okt 12:19 cur -rw------- 1 vmail vmail 0 15. Sep 2018 dovecot-acl-list -rw------- 1 vmail vmail 1080 29. Okt 06:50 dovecot.index -rw------- 1 vmail vmail 23400 30. Okt 01:36 dovecot.index.cache -rw------- 1 vmail vmail 10600 30. Okt 01:41 dovecot.index.log -rw------- 1 vmail vmail 6627 26. Aug 09:56 dovecot.index.thread -rw------- 1 vmail vmail 24 20. Apr 2024 dovecot-keywords -rw------- 1 vmail vmail 1256 3. Nov 09:05 dovecot.list.index -rw------- 1 vmail vmail 5156 3. Nov 09:15 dovecot.list.index.log -rw------- 1 vmail vmail 672 15. Jul 16:45 dovecot.mailbox.log -rw------- 1 vmail vmail 1781 30. Okt 01:36 dovecot-uidlist -rw------- 1 vmail vmail 8 15. Jul 16:45 dovecot-uidvalidity -r--r--r-- 1 vmail 1002 0 15. Sep 2018 dovecot-uidvalidity.5b9d20a3 -r--r--r-- 1 vmail vmail 0 15. Sep 2018 dovecot-uidvalidity.5b9d20a5 drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Drafts drwx------ 5 vmail vmail 4096 3. Nov 09:15 .INBOX.Fail2ban drwx------ 5 vmail vmail 4096 3. Nov 09:15 .INBOX.vzdump drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Junk drwx------ 2 vmail vmail 36864 30. Okt 01:36 new drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Quarantine drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Sent drwx------ 3 vmail vmail 4096 3. Dez 06:25 sieve -rw------- 1 vmail vmail 70 15. Jul 16:45 subscriptions drwx------ 2 vmail vmail 4096 2. Nov 16:41 tmp drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Trash
This should be preserved so that users don't notice the system upgrade. Dovecot 2.4 works quite differently in this regard. What solution is there for migrating both the namespace and the existing emails to Dovecot 2.4?
Thank you for your suggestions
ByteMe
My configuration on new system:
2.4.1-4 (7d8c0e5759): /etc/dovecot/dovecot.conf
Pigeonhole version 2.4.1-4 (0a86619f)
OS: Linux 6.12.57+deb13-amd64 x86_64 Debian 13.2 ext4
dovecot_config_version = 2.4.1 acl_driver = vfile auth_allow_weak_schemes = yes auth_debug = yes auth_debug_passwords = yes auth_username_format = %{user|lower} auth_verbose = yes doveadm_password = # hidden, use -P to show it doveadm_port = 12345 dovecot_storage_version = 2.4.1 fts_autoindex = yes fts_autoindex_max_recent_msgs = 999 fts_search_add_missing = yes info_log_path = /var/log/dovecot/dovecot-info.log listen = * log_path = /var/log/dovecot/dovecot.log log_timestamp = "%Y-%m-%d %H:%M:%S " login_log_format_elements = user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k mail_debug = yes mail_driver = maildir mail_home = /var/vmail/%{user | domain}/%{user | username } mail_inbox_path = . mail_max_userip_connections = 500 mail_path = /var/vmail/%{user | domain}/%{user | username }/ mail_plugins = notify quota push_notification quota_clone acl fts fts_solr mail_privileged_group = mail protocols { imap = yes sieve = yes lmtp = yes pop3 = yes } quota_exceeded_message = Benutzer %{user} hat das Speichervolumen ueberschritten. / User %{user} has exhausted allowed storage space. sieve_global_extensions { vnd.dovecot.pipe = yes } sieve_max_redirects = 10 sieve_max_script_size = 1M sieve_plugins { sieve_imapsieve = yes sieve_extprograms = yes } sql_driver = mysql ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305 verbose_proctitle = yes mysql /var/run/mysqld/mysqld.sock { dbname = postfix password = # hidden, use -P to show it user = postfix } passdb sql { query = SELECT username AS user, CONCAT('{CRYPT}', password) AS password FROM mailbox WHERE username = '%{user}' AND active = true; } userdb sql { iterate_query = SELECT username AS user FROM mailbox WHERE active = true; query = SELECT 5000 AS uid, 5000 AS gid, CONCAT('/var/vmail/', SUBSTRING_INDEX(username,'@',-1), '/', SUBSTRING_INDEX(username,'@',1)) AS home, CONCAT('maildir:/var/vmail/', SUBSTRING_INDEX(username,'@',-1), '/', SUBSTRING_INDEX(username,'@',1)) AS mail, CONCAT(quota,'M') AS quota_storage_size FROM mailbox WHERE username = '%{user}' AND active = true; } namespace inbox { inbox = yes prefix = .INBOX. separator = / type = private mailbox Drafts { special_use = "\\Drafts" } mailbox Archive { } mailbox Junk { special_use = "\\Junk" } mailbox Sent { special_use = "\\Sent" } mailbox Trash { special_use = "\\Trash" quota_storage_extra = 100M quota_storage_percentage = 110 } mailbox "Sent Messages" { special_use = "\\Sent" } } namespace shared { list = yes prefix = Shared. separator = . subscriptions = yes type = shared } namespace public { list = yes prefix = Public. separator = . subscriptions = yes type = public mailbox Announcements { auto = subscribe } } service imap-login { inet_listener imap { } inet_listener imaps { } } service pop3-login { inet_listener pop3 { } inet_listener pop3s { } } service submission-login { inet_listener submission { } } service imap { executable = imap process_limit = 1024 process_min_avail = 2 vsz_limit = 1G } service pop3 { } service submission { } service auth { user = root unix_listener /var/spool/postfix/private/auth_dovecot { group = postfix mode = 0660 user = postfix } unix_listener auth-master { mode = 0600 user = vmail } unix_listener auth-userdb { mode = 0600 user = vmail } } service auth-worker { } service dict { unix_listener dict { group = vmail mode = 0660 user = vmail } } ssl_server { cert_file = /etc/letsencrypt/live/mx1.germany.com/fullchain.pem key_file = /etc/letsencrypt/live/mx1.germany.com/privkey.pem } service doveadm { inet_listener doveadm_listener { port = 12345 } } protocol imap { imap_idle_notify_interval = 29 mins mail_max_userip_connections = 50 mail_plugins = quota quota_clone imap_quota imap_acl acl fts fts_solr } protocol lmtp { auth_socket_path = /var/run/dovecot/auth-master mail_plugins { sieve = yes notify = yes push_notification = yes } postmaster_address = postmaster@germany.com } service managesieve-login { process_min_avail = 2 vsz_limit = 128 M inet_listener sieve { port = 4190 } inet_listener sieve_deprecated { port = 2000 } } service managesieve { process_limit = 256 } protocol sieve { managesieve_logout_format = bytes=%i/%o managesieve_notify_capability = mailto managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext } dict_server { dict mysql { driver = sql sql_driver = mysql dict_map priv/quota/messages { sql_table = quota username_field = username dict_map_value_field messages { } } dict_map priv/quota/storage { sql_table = quota username_field = username dict_map_value_field bytes { } } } } acl_sharing_map { dict file { path = /var/vmail/shared-mailboxes.db } } quota "User quota" { driver = count storage_size = 1G } quota_clone { dict proxy { name = mysql } } sieve_script personal { active_path = /var/vmail/sieve/%{user | domain }/%{user | username }/active-script.sieve driver = file path = /var/vmail/sieve/%{user | domain }/%{user | username }/scripts type = personal } sieve_script global { driver = file path = /var/vmail/sieve/global.sieve type = before } sieve_script spam-global { path = /var/vmail/sieve/global/spam-global.sieve type = before } imapsieve_from Spam { sieve_script ham { cause = copy path = /var/vmail/sieve/global/learn-ham.sieve type = before } }
On the old system Bookworm an Dovecot 2.3 it looks like:
2.3.19.1 (9b53102964): /etc/dovecot/dovecot.conf
Pigeonhole version 0.5.19 (4eae2f79)
OS: Linux 6.1.0-40-amd64 x86_64 Debian 12.12
dict { sqlquota = mysql:/etc/dovecot/dovecot-dict-sql.conf } doveadm_password = # hidden, use -P to show it doveadm_port = 12345 info_log_path = /var/log/dovecot/dovecot-info.log listen = * log_path = /var/log/dovecot/dovecot.log log_timestamp = "%Y-%m-%d %H:%M:%S " login_log_format_elements = user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k mail_home = /var/vmail/%d/%n mail_location = maildir:~/ mail_max_userip_connections = 500 mail_plugins = quota acl fts fts_solr mail_privileged_group = mail managesieve_notify_capability = mailto managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext namespace { list = yes location = maildir:%%h/:INDEXPVT=~/Shared/%%u prefix = Shared/%%u/ separator = / subscriptions = no type = shared } namespace inbox { inbox = yes location = mailbox Archief { special_use = \Archive } mailbox Archiv { special_use = \Archive } mailbox Archive { auto = subscribe special_use = \Archive } mailbox Archives { special_use = \Archive } mailbox Arquivo { special_use = \Archive } mailbox Arquivos { special_use = \Archive } mailbox Brouillons { special_use = \Drafts } mailbox Concepten { special_use = \Drafts } mailbox Corbeille { special_use = \Trash } mailbox "Deleted Items" { special_use = \Trash } mailbox "Deleted Messages" { special_use = \Trash } mailbox Drafts { auto = subscribe special_use = \Drafts } mailbox Entwürfe { special_use = \Drafts } mailbox Enviados { special_use = \Sent } mailbox Envoyés { special_use = \Sent } mailbox "Gelöschte Objekte" { special_use = \Trash } mailbox Gesendet { special_use = \Sent } mailbox "Gesendete Objekte" { special_use = \Sent } mailbox "Itens Enviados" { special_use = \Sent } mailbox "Itens Excluídos" { special_use = \Trash } mailbox "Itens Excluidos" { special_use = \Trash } mailbox Junk { auto = subscribe special_use = \Junk } mailbox "Junk E-mail" { special_use = \Junk } mailbox Lixeira { special_use = \Trash } mailbox "Lixo Eletrônico" { special_use = \Junk } mailbox "Messages envoyés" { special_use = \Sent } mailbox "Ongewenste e-mail" { special_use = \Junk } mailbox Papierkorb { special_use = \Trash } mailbox Prullenbak { special_use = \Trash } mailbox Quarantine { auto = subscribe comment = Mail containing viruses } mailbox Rascunhos { special_use = \Drafts } mailbox Sent { auto = subscribe special_use = \Sent } mailbox "Sent Items" { special_use = \Sent } mailbox "Sent Messages" { special_use = \Sent } mailbox Spam { special_use = \Junk } mailbox Trash { auto = subscribe special_use = \Trash } mailbox "Verwijderde items" { special_use = \Trash } mailbox Verzonden { special_use = \Sent } mailbox "Verzonden items" { special_use = \Sent } prefix = separator = / } passdb { driver = pam } passdb { args = /etc/dovecot/dovecot-mysql.conf driver = sql } plugin { acl = vfile acl_anyone = allow acl_shared_dict = file:/var/vmail/shared-mailboxes.db mail_replica = tcp:192.168.2.6 quota = dict:Userquota::proxy::sqlquota quota_rule2 = Trash:storage=+100%% recipient_delimiter = + sieve = /var/vmail/%d/%n/sieve/active-script.sieve sieve_before = /var/vmail/sieve/global.sieve sieve_dir = /var/vmail/%d/%n/sieve/scripts/ sieve_max_script_size = 1M sieve_quota_max_scripts = 0 sieve_quota_max_storage = 0 } protocols = imap sieve lmtp pop3 replication_dsync_parameters = -d -n INBOX -l 30 -U replication_full_sync_interval = 2 mins service aggregator { fifo_listener replication-notify-fifo { user = vmail } unix_listener replication-notify { user = vmail } } service auth { unix_listener /var/spool/postfix/private/auth_dovecot { group = postfix mode = 0660 user = postfix } unix_listener auth-master { mode = 0600 user = vmail } unix_listener auth-userdb { mode = 0600 user = vmail } user = root } service dict { unix_listener dict { group = vmail mode = 0660 user = vmail } } service doveadm { inet_listener { port = 12345 } } service imap { process_limit = 1024 vsz_limit = 1 G } service lmtp { unix_listener /var/spool/postfix/private/dovecot-lmtp { group = postfix mode = 0600 user = postfix } user = vmail } service managesieve-login { inet_listener sieve { port = 4190 } process_min_avail = 2 service_count = 1 vsz_limit = 128 M } service managesieve { process_limit = 256 } service replicator { process_min_avail = 1 unix_listener replicator-doveadm { mode = 0600 user = vmail } } ssl_cert = </etc/letsencrypt/live/mx1.germany.com/fullchain.pem ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305 ssl_key = # hidden, use -P to show it userdb { driver = passwd } userdb { args = /etc/dovecot/dovecot-mysql.conf driver = sql } verbose_proctitle = yes protocol lmtp { auth_socket_path = /var/run/dovecot/auth-master mail_plugins = quota sieve acl fts fts_solr postmaster_address = postmaster@germany.com } protocol imap { mail_plugins = quota imap_quota imap_acl acl fts fts_solr } protocol sieve { managesieve_logout_format = bytes=%i/%o } remote 127.0.0.1 { disable_plaintext_auth = no }
Hi ByteMe,
Try changing the separator from / to .
Best regards, Piotr Szafarczyk
On 04/12/2025 11:50, byteme--- via dovecot wrote:
My configuration on new system:
2.4.1-4 (7d8c0e5759): /etc/dovecot/dovecot.conf
Pigeonhole version 2.4.1-4 (0a86619f)
OS: Linux 6.12.57+deb13-amd64 x86_64 Debian 13.2 ext4
dovecot_config_version = 2.4.1 acl_driver = vfile auth_allow_weak_schemes = yes auth_debug = yes auth_debug_passwords = yes auth_username_format = %{user|lower} auth_verbose = yes doveadm_password = # hidden, use -P to show it doveadm_port = 12345 dovecot_storage_version = 2.4.1 fts_autoindex = yes fts_autoindex_max_recent_msgs = 999 fts_search_add_missing = yes info_log_path = /var/log/dovecot/dovecot-info.log listen = * log_path = /var/log/dovecot/dovecot.log log_timestamp = "%Y-%m-%d %H:%M:%S " login_log_format_elements = user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k mail_debug = yes mail_driver = maildir mail_home = /var/vmail/%{user | domain}/%{user | username } mail_inbox_path = . mail_max_userip_connections = 500 mail_path = /var/vmail/%{user | domain}/%{user | username }/ mail_plugins = notify quota push_notification quota_clone acl fts fts_solr mail_privileged_group = mail protocols { imap = yes sieve = yes lmtp = yes pop3 = yes } quota_exceeded_message = Benutzer %{user} hat das Speichervolumen ueberschritten. / User %{user} has exhausted allowed storage space. sieve_global_extensions { vnd.dovecot.pipe = yes } sieve_max_redirects = 10 sieve_max_script_size = 1M sieve_plugins { sieve_imapsieve = yes sieve_extprograms = yes } sql_driver = mysql ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305 verbose_proctitle = yes mysql /var/run/mysqld/mysqld.sock { dbname = postfix password = # hidden, use -P to show it user = postfix } passdb sql { query = SELECT username AS user, CONCAT('{CRYPT}', password) AS password FROM mailbox WHERE username = '%{user}' AND active = true; } userdb sql { iterate_query = SELECT username AS user FROM mailbox WHERE active = true; query = SELECT 5000 AS uid, 5000 AS gid, CONCAT('/var/vmail/', SUBSTRING_INDEX(username,'@',-1), '/', SUBSTRING_INDEX(username,'@',1)) AS home, CONCAT('maildir:/var/vmail/', SUBSTRING_INDEX(username,'@',-1), '/', SUBSTRING_INDEX(username,'@',1)) AS mail, CONCAT(quota,'M') AS quota_storage_size FROM mailbox WHERE username = '%{user}' AND active = true; } namespace inbox { inbox = yes prefix = .INBOX. separator = / type = private mailbox Drafts { special_use = "\\Drafts" } mailbox Archive { } mailbox Junk { special_use = "\\Junk" } mailbox Sent { special_use = "\\Sent" } mailbox Trash { special_use = "\\Trash" quota_storage_extra = 100M quota_storage_percentage = 110 } mailbox "Sent Messages" { special_use = "\\Sent" } } namespace shared { list = yes prefix = Shared. separator = . subscriptions = yes type = shared } namespace public { list = yes prefix = Public. separator = . subscriptions = yes type = public mailbox Announcements { auto = subscribe } } service imap-login { inet_listener imap { } inet_listener imaps { } } service pop3-login { inet_listener pop3 { } inet_listener pop3s { } } service submission-login { inet_listener submission { } } service imap { executable = imap process_limit = 1024 process_min_avail = 2 vsz_limit = 1G } service pop3 { } service submission { } service auth { user = root unix_listener /var/spool/postfix/private/auth_dovecot { group = postfix mode = 0660 user = postfix } unix_listener auth-master { mode = 0600 user = vmail } unix_listener auth-userdb { mode = 0600 user = vmail } } service auth-worker { } service dict { unix_listener dict { group = vmail mode = 0660 user = vmail } } ssl_server { cert_file = /etc/letsencrypt/live/mx1.germany.com/fullchain.pem key_file = /etc/letsencrypt/live/mx1.germany.com/privkey.pem } service doveadm { inet_listener doveadm_listener { port = 12345 } } protocol imap { imap_idle_notify_interval = 29 mins mail_max_userip_connections = 50 mail_plugins = quota quota_clone imap_quota imap_acl acl fts fts_solr } protocol lmtp { auth_socket_path = /var/run/dovecot/auth-master mail_plugins { sieve = yes notify = yes push_notification = yes } postmaster_address = postmaster@germany.com } service managesieve-login { process_min_avail = 2 vsz_limit = 128 M inet_listener sieve { port = 4190 } inet_listener sieve_deprecated { port = 2000 } } service managesieve { process_limit = 256 } protocol sieve { managesieve_logout_format = bytes=%i/%o managesieve_notify_capability = mailto managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext } dict_server { dict mysql { driver = sql sql_driver = mysql dict_map priv/quota/messages { sql_table = quota username_field = username dict_map_value_field messages { } } dict_map priv/quota/storage { sql_table = quota username_field = username dict_map_value_field bytes { } } } } acl_sharing_map { dict file { path = /var/vmail/shared-mailboxes.db } } quota "User quota" { driver = count storage_size = 1G } quota_clone { dict proxy { name = mysql } } sieve_script personal { active_path = /var/vmail/sieve/%{user | domain }/%{user | username }/active-script.sieve driver = file path = /var/vmail/sieve/%{user | domain }/%{user | username }/scripts type = personal } sieve_script global { driver = file path = /var/vmail/sieve/global.sieve type = before } sieve_script spam-global { path = /var/vmail/sieve/global/spam-global.sieve type = before } imapsieve_from Spam { sieve_script ham { cause = copy path = /var/vmail/sieve/global/learn-ham.sieve type = before } }
On the old system Bookworm an Dovecot 2.3 it looks like:
2.3.19.1 (9b53102964): /etc/dovecot/dovecot.conf
Pigeonhole version 0.5.19 (4eae2f79)
OS: Linux 6.1.0-40-amd64 x86_64 Debian 12.12
dict { sqlquota = mysql:/etc/dovecot/dovecot-dict-sql.conf } doveadm_password = # hidden, use -P to show it doveadm_port = 12345 info_log_path = /var/log/dovecot/dovecot-info.log listen = * log_path = /var/log/dovecot/dovecot.log log_timestamp = "%Y-%m-%d %H:%M:%S " login_log_format_elements = user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k mail_home = /var/vmail/%d/%n mail_location = maildir:~/ mail_max_userip_connections = 500 mail_plugins = quota acl fts fts_solr mail_privileged_group = mail managesieve_notify_capability = mailto managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext namespace { list = yes location = maildir:%%h/:INDEXPVT=~/Shared/%%u prefix = Shared/%%u/ separator = / subscriptions = no type = shared } namespace inbox { inbox = yes location = mailbox Archief { special_use = \Archive } mailbox Archiv { special_use = \Archive } mailbox Archive { auto = subscribe special_use = \Archive } mailbox Archives { special_use = \Archive } mailbox Arquivo { special_use = \Archive } mailbox Arquivos { special_use = \Archive } mailbox Brouillons { special_use = \Drafts } mailbox Concepten { special_use = \Drafts } mailbox Corbeille { special_use = \Trash } mailbox "Deleted Items" { special_use = \Trash } mailbox "Deleted Messages" { special_use = \Trash } mailbox Drafts { auto = subscribe special_use = \Drafts } mailbox Entwürfe { special_use = \Drafts } mailbox Enviados { special_use = \Sent } mailbox Envoyés { special_use = \Sent } mailbox "Gelöschte Objekte" { special_use = \Trash } mailbox Gesendet { special_use = \Sent } mailbox "Gesendete Objekte" { special_use = \Sent } mailbox "Itens Enviados" { special_use = \Sent } mailbox "Itens Excluídos" { special_use = \Trash } mailbox "Itens Excluidos" { special_use = \Trash } mailbox Junk { auto = subscribe special_use = \Junk } mailbox "Junk E-mail" { special_use = \Junk } mailbox Lixeira { special_use = \Trash } mailbox "Lixo Eletrônico" { special_use = \Junk } mailbox "Messages envoyés" { special_use = \Sent } mailbox "Ongewenste e-mail" { special_use = \Junk } mailbox Papierkorb { special_use = \Trash } mailbox Prullenbak { special_use = \Trash } mailbox Quarantine { auto = subscribe comment = Mail containing viruses } mailbox Rascunhos { special_use = \Drafts } mailbox Sent { auto = subscribe special_use = \Sent } mailbox "Sent Items" { special_use = \Sent } mailbox "Sent Messages" { special_use = \Sent } mailbox Spam { special_use = \Junk } mailbox Trash { auto = subscribe special_use = \Trash } mailbox "Verwijderde items" { special_use = \Trash } mailbox Verzonden { special_use = \Sent } mailbox "Verzonden items" { special_use = \Sent } prefix = separator = / } passdb { driver = pam } passdb { args = /etc/dovecot/dovecot-mysql.conf driver = sql } plugin { acl = vfile acl_anyone = allow acl_shared_dict = file:/var/vmail/shared-mailboxes.db mail_replica = tcp:192.168.2.6 quota = dict:Userquota::proxy::sqlquota quota_rule2 = Trash:storage=+100%% recipient_delimiter = + sieve = /var/vmail/%d/%n/sieve/active-script.sieve sieve_before = /var/vmail/sieve/global.sieve sieve_dir = /var/vmail/%d/%n/sieve/scripts/ sieve_max_script_size = 1M sieve_quota_max_scripts = 0 sieve_quota_max_storage = 0 } protocols = imap sieve lmtp pop3 replication_dsync_parameters = -d -n INBOX -l 30 -U replication_full_sync_interval = 2 mins service aggregator { fifo_listener replication-notify-fifo { user = vmail } unix_listener replication-notify { user = vmail } } service auth { unix_listener /var/spool/postfix/private/auth_dovecot { group = postfix mode = 0660 user = postfix } unix_listener auth-master { mode = 0600 user = vmail } unix_listener auth-userdb { mode = 0600 user = vmail } user = root } service dict { unix_listener dict { group = vmail mode = 0660 user = vmail } } service doveadm { inet_listener { port = 12345 } } service imap { process_limit = 1024 vsz_limit = 1 G } service lmtp { unix_listener /var/spool/postfix/private/dovecot-lmtp { group = postfix mode = 0600 user = postfix } user = vmail } service managesieve-login { inet_listener sieve { port = 4190 } process_min_avail = 2 service_count = 1 vsz_limit = 128 M } service managesieve { process_limit = 256 } service replicator { process_min_avail = 1 unix_listener replicator-doveadm { mode = 0600 user = vmail } } ssl_cert = </etc/letsencrypt/live/mx1.germany.com/fullchain.pem ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305 ssl_key = # hidden, use -P to show it userdb { driver = passwd } userdb { args = /etc/dovecot/dovecot-mysql.conf driver = sql } verbose_proctitle = yes protocol lmtp { auth_socket_path = /var/run/dovecot/auth-master mail_plugins = quota sieve acl fts fts_solr postmaster_address = postmaster@germany.com } protocol imap { mail_plugins = quota imap_quota imap_acl acl fts fts_solr } protocol sieve { managesieve_logout_format = bytes=%i/%o } remote 127.0.0.1 { disable_plaintext_auth = no }
dovecot mailing list -- dovecot@dovecot.org To unsubscribe send an email to dovecot-leave@dovecot.org
That doesn't solve the problem. 2025-12-04 14:57:37 imap(test@example.com)<1517631><nsIetiBFyrp/AAAB>: Error: namespace configuration error: list=yes requires prefix=.INBOX. not to start with separator 2025-12-04 14:58:01 imap(systemmails@example.com)<1517854><aQuXtyBFHMZ/AAAB>: Error: namespace configuration error: list=yes requires prefix=.INBOX. not to start with separator
Don't set mail_inbox_path (remove/comment out if its set by debian in conf.d). Your config does not need it. Namespace separator is not your issue.
Aki
On 04/12/2025 16:01 EET byteme--- via dovecot <[1]dovecot@dovecot.org>
wrote:
That doesn't solve the problem.
2025-12-04 14:57:37
imap([2]test@example.com)<1517631><nsIetiBFyrp/AAAB>: Error: namespace
configuration error: list=yes requires prefix=.INBOX. not to start with
separator
2025-12-04 14:58:01
imap([3]systemmails@example.com)<1517854><aQuXtyBFHMZ/AAAB>: Error:
namespace configuration error: list=yes requires prefix=.INBOX. not to
start with separator
_______________________________________________
dovecot mailing list -- [4]dovecot@dovecot.org
To unsubscribe send an email to [5]dovecot-leave@dovecot.org
References
Visible links
- mailto:dovecot@dovecot.org
- mailto:test@example.com
- mailto:systemmails@example.com
- mailto:dovecot@dovecot.org
- mailto:dovecot-leave@dovecot.org
Looked ok otherwise.
Aki
On 04/12/2025 17:03 EET byteme--- via dovecot <[1]dovecot@dovecot.org>
wrote:
Okay,
what would be the right definition for namespace in my case?
ByteMe
_______________________________________________
dovecot mailing list -- [2]dovecot@dovecot.org
To unsubscribe send an email to [3]dovecot-leave@dovecot.org
References
Visible links
- mailto:dovecot@dovecot.org
- mailto:dovecot@dovecot.org
- mailto:dovecot-leave@dovecot.org
"byteme---" == byteme--- via dovecot <dovecot@dovecot.org> writes:
Aki Tuomi wrote:
Looked ok otherwise. Aki That's good news, but it doesn't solve the problem of the previously invisible folders.
What is your current 2.3 configuration? It would be good to be able to compare them.
John Stoffel wrote:
What is your current 2.3 configuration? It would be good to be able to compare them. That's the old config:
2.3.19.1 (9b53102964): /etc/dovecot/dovecot.conf
Pigeonhole version 0.5.19 (4eae2f79)
OS: Linux 6.1.0-40-amd64 x86_64 Debian 12.12
dict { sqlquota = mysql:/etc/dovecot/dovecot-dict-sql.conf } doveadm_password = # hidden, use -P to show it doveadm_port = 12345 info_log_path = /var/log/dovecot/dovecot-info.log listen = * log_path = /var/log/dovecot/dovecot.log log_timestamp = "%Y-%m-%d %H:%M:%S " login_log_format_elements = user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k mail_home = /var/vmail/%d/%n mail_location = maildir:~/ mail_max_userip_connections = 500 mail_plugins = quota acl fts fts_solr mail_privileged_group = mail managesieve_notify_capability = mailto managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext namespace { list = yes location = maildir:%%h/:INDEXPVT=~/Shared/%%u prefix = Shared/%%u/ separator = / subscriptions = no type = shared } namespace inbox { inbox = yes location = mailbox Archiv { special_use = \Archive } prefix = separator = / } passdb { driver = pam } passdb { args = /etc/dovecot/dovecot-mysql.conf driver = sql } plugin { acl = vfile acl_anyone = allow acl_shared_dict = file:/var/vmail/shared-mailboxes.db mail_replica = tcp:192.168.2.6 quota = dict:Userquota::proxy::sqlquota quota_rule2 = Trash:storage=+100%% recipient_delimiter = + sieve = /var/vmail/%d/%n/sieve/active-script.sieve sieve_before = /var/vmail/sieve/global.sieve sieve_dir = /var/vmail/%d/%n/sieve/scripts/ sieve_max_script_size = 1M sieve_quota_max_scripts = 0 sieve_quota_max_storage = 0 } protocols = imap sieve lmtp pop3 replication_dsync_parameters = -d -n INBOX -l 30 -U replication_full_sync_interval = 2 mins service aggregator { fifo_listener replication-notify-fifo { user = vmail } unix_listener replication-notify { user = vmail } } service auth { unix_listener /var/spool/postfix/private/auth_dovecot { group = postfix mode = 0660 user = postfix } unix_listener auth-master { mode = 0600 user = vmail } unix_listener auth-userdb { mode = 0600 user = vmail } user = root } service dict { unix_listener dict { group = vmail mode = 0660 user = vmail } } service doveadm { inet_listener { port = 12345 } } service imap { process_limit = 1024 vsz_limit = 1 G } service lmtp { unix_listener /var/spool/postfix/private/dovecot-lmtp { group = postfix mode = 0600 user = postfix } user = vmail } service managesieve-login { inet_listener sieve { port = 4190 } process_min_avail = 2 service_count = 1 vsz_limit = 128 M } service managesieve { process_limit = 256 } service replicator { process_min_avail = 1 unix_listener replicator-doveadm { mode = 0600 user = vmail } } ssl_cert = </etc/letsencrypt/live/mx1.example.com/fullchain.pem ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305 ssl_key = # hidden, use -P to show it userdb { driver = passwd } userdb { args = /etc/dovecot/dovecot-mysql.conf driver = sql } verbose_proctitle = yes protocol lmtp { auth_socket_path = /var/run/dovecot/auth-master mail_plugins = quota sieve acl fts fts_solr postmaster_address = postmaster@example.com } protocol imap { mail_plugins = quota imap_quota imap_acl acl fts fts_solr } protocol sieve { managesieve_logout_format = bytes=%i/%o } remote 127.0.0.1 { disable_plaintext_auth = no } Which shows this:
doveadm mailbox list -u systemmails@example.com
Quarantine INBOX/vzdump INBOX/Fail2ban Drafts Archive Trash Sent Junk Shared INBOX
On mx2 I have exactly the same folder on Linux Debian, but the folder are not shown at Dovecot.
At first, please don't use private mails here. It will use the wrong folders here and will not be visible on the right context! John Stoffel wrote:
I have in my setup the following in my 2.3 config:
mail_location = maildir:/var/mail/%d/%n/Maildir
while from your recent email I see you have:
mail_home = /var/vmail/%d/%n mail_location = maildir:~/
Which implies to me that you don't push them down as far as I do into the directory tree. But that all looks good to me.
We talk here about a migration from Dovecot 2.3 to 2.4 and the newer version doesn't know mail_location = maildir:/var/mail/%d/%n/Maildir anymore. It throws errors. "mail_location" is unkown meanwhile. Dovecot 2.3 is running well, only Dovecot 2.4 doesn't show the origin folders and files.
On 06/12/2025 11:21 EET byteme--- via dovecot <dovecot@dovecot.org> wrote:
At first, please don't use private mails here. It will use the wrong folders here and will not be visible on the right context! John Stoffel wrote:
I have in my setup the following in my 2.3 config:
mail_location = maildir:/var/mail/%d/%n/Maildir
while from your recent email I see you have:
mail_home = /var/vmail/%d/%n mail_location = maildir:~/
Which implies to me that you don't push them down as far as I do into the directory tree. But that all looks good to me.
We talk here about a migration from Dovecot 2.3 to 2.4 and the newer version doesn't know mail_location = maildir:/var/mail/%d/%n/Maildir anymore. It throws errors. "mail_location" is unkown meanwhile. Dovecot 2.3 is running well, only Dovecot 2.4 doesn't show the origin folders and files.
mail_location = maildir:/var/mail/%d/%n/Maildir
converts into
mail_home = /var/mail/%{user|domain}/%{user|username} mail_driver = maildir mail_path = ~/Maildir
Aki
mail_location = maildir:/var/mail/%d/%n/Maildir
converts into
mail_home = /var/mail/%{user|domain}/%{user|username} mail_driver = maildir mail_path = ~/Maildir
Aki
What would this convert to in 2.4x considering the indexes?
v2.3x: mail_home = /var/spool/email/%d/%n/_dovecot mail_location = maildir:/var/spool/email/%d/%n/_folders:INDEX=/var/spool/email/%d/%n/_dovecot:CONTROL=/var/spool/email/%d/%n/_dovecot plugin { sieve = file:~/../_sieve;active=~/../_sieve/.active_sieve }
On 06/12/2025 16:33 EET dovecot--- via dovecot <dovecot@dovecot.org> wrote:
mail_location = maildir:/var/mail/%d/%n/Maildir
converts into
mail_home = /var/mail/%{user|domain}/%{user|username} mail_driver = maildir mail_path = ~/Maildir
Aki
What would this convert to in 2.4x considering the indexes?
v2.3x: mail_home = /var/spool/email/%d/%n/_dovecot mail_location = maildir:/var/spool/email/%d/%n/_folders:INDEX=/var/spool/email/%d/%n/_dovecot:CONTROL=/var/spool/email/%d/%n/_dovecot plugin { sieve = file:~/../_sieve;active=~/../_sieve/.active_sieve }
mail_driver = maildir mail_home = /var/spool/email/%{user|domain}/%{user|username}/_dovecot mail_path = /var/spool/email/%{user|domain}/%{user|username}/_folders mail_index_path = /var/spool/email/%{user|domain}/%{user|username}/_dovecot mail_control_path = /var/spool/email/%{user|domain}/%{user|username}/_dovecot
but this is bit weird config...
sieve_script personal { driver = file path = ~/../_sieve active_path = ~/../.active.sieve }
for the sieve bit.
Aki
"byteme---" == byteme--- via dovecot <dovecot@dovecot.org> writes:
Hello everyone,
I have a Dovecot 2.3 installation on a Debian 12 Linux system that is being upgraded to Dovecot 2.4. The server hosts many domains with numerous email accounts. Users had created folder structures containing many received and sent emails, following the Maildir standard as "dot-prefixed folders."
Folowing up on this in backwards timeline because I've got the same type of setup, though much smaller, so I'm interested in what the solution will be.
My folder structure looks like this too:
/var/mail/<domain>/<user>/Maildir/.<folders>/
as well. You can probably guess my values here. LOL!
When switching to Dovecot 2.4, all these emails and folders are no longer visible. This is what it looks like on the old system:
root@mx1 /var/vmail/example.com/systemmails # ll -a insgesamt 256 drwx------ 14 vmail vmail 4096 3. Nov 09:15 . drwx------ 15 vmail vmail 4096 6. Apr 2021 .. drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Archive drwx------ 2 vmail vmail 86016 27. Okt 12:19 cur -rw------- 1 vmail vmail 0 15. Sep 2018 dovecot-acl-list -rw------- 1 vmail vmail 1080 29. Okt 06:50 dovecot.index -rw------- 1 vmail vmail 23400 30. Okt 01:36 dovecot.index.cache -rw------- 1 vmail vmail 10600 30. Okt 01:41 dovecot.index.log -rw------- 1 vmail vmail 6627 26. Aug 09:56 dovecot.index.thread -rw------- 1 vmail vmail 24 20. Apr 2024 dovecot-keywords -rw------- 1 vmail vmail 1256 3. Nov 09:05 dovecot.list.index -rw------- 1 vmail vmail 5156 3. Nov 09:15 dovecot.list.index.log -rw------- 1 vmail vmail 672 15. Jul 16:45 dovecot.mailbox.log -rw------- 1 vmail vmail 1781 30. Okt 01:36 dovecot-uidlist -rw------- 1 vmail vmail 8 15. Jul 16:45 dovecot-uidvalidity -r--r--r-- 1 vmail 1002 0 15. Sep 2018 dovecot-uidvalidity.5b9d20a3 -r--r--r-- 1 vmail vmail 0 15. Sep 2018 dovecot-uidvalidity.5b9d20a5 drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Drafts drwx------ 5 vmail vmail 4096 3. Nov 09:15 .INBOX.Fail2ban drwx------ 5 vmail vmail 4096 3. Nov 09:15 .INBOX.vzdump drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Junk drwx------ 2 vmail vmail 36864 30. Okt 01:36 new drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Quarantine drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Sent drwx------ 3 vmail vmail 4096 3. Dez 06:25 sieve -rw------- 1 vmail vmail 70 15. Jul 16:45 subscriptions drwx------ 2 vmail vmail 4096 2. Nov 16:41 tmp drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Trash
I have in my setup the following in my 2.3 config:
mail_location = maildir:/var/mail/%d/%n/Maildir
while from your recent email I see you have:
mail_home = /var/vmail/%d/%n
mail_location = maildir:~/
Which implies to me that you don't push them down as far as I do into the directory tree. But that all looks good to me.
This should be preserved so that users don't notice the system upgrade. Dovecot 2.4 works quite differently in this regard. What solution is there for migrating both the namespace and the existing emails to Dovecot 2.4?
I've got a super simple namespace setup:
namespace inbox { inbox = yes location = mailbox Drafts { special_use = \Drafts } mailbox Junk { autoexpunge = 10 days special_use = \Junk } mailbox Sent { special_use = \Sent } mailbox "Sent Messages" { special_use = \Sent } mailbox Trash { autoexpunge = 30 days special_use = \Trash } prefix = }
And I wonder if your 'seperator' entry in your inbox namespace when translated to the 2.4 format is the problem?
Could it be your prefix definition in your new 2.4 config where you have
prefix = .INBOX.
But I haven't had the time or energy to try and migrate my setup to v2.4 as well either. Good luck!
John
Question by a person:
But are the IMAP folders subscribed to or available for subscription? That's something to consider.
root@mx2 /var/vmail/example.com/systemmails # ll -ta insgesamt 144 -rw------- 1 vmail vmail 2988 6. Dez 10:39 dovecot.list.index.log drwx------ 5 vmail vmail 4096 6. Dez 10:39 Sent drwx------ 5 vmail vmail 4096 6. Dez 10:39 .Sent drwx------ 16 vmail vmail 4096 6. Dez 10:39 . -rw------- 1 vmail vmail 1104 6. Dez 10:39 dovecot.mailbox.log -rw------- 1 vmail vmail 89 6. Dez 10:39 subscriptions -rw------- 1 vmail vmail 8 6. Dez 10:39 dovecot-uidvalidity -rw------- 1 vmail vmail 12552 6. Dez 10:38 dovecot.index.log drwx------ 5 vmail vmail 4096 6. Dez 10:38 Trash drwx------ 2 vmail vmail 4096 6. Dez 10:38 cur -rw------- 1 vmail vmail 14648 6. Dez 10:38 dovecot.index.cache -rw------- 1 vmail vmail 1616 6. Dez 10:38 dovecot.list.index -rw------- 1 vmail vmail 8212 6. Dez 10:38 dovecot.list.index.log.2 drwx------ 5 vmail vmail 4096 6. Dez 10:37 .INBOX.Fail2ban drwx------ 5 vmail vmail 4096 6. Dez 10:35 .INBOX.vzdump -rw------- 1 vmail vmail 283 5. Dez 16:01 dovecot.index.thread drwx------ 2 vmail vmail 4096 5. Dez 16:01 new drwx------ 5 vmail vmail 4096 5. Dez 16:01 .Drafts drwx------ 5 vmail vmail 4096 5. Dez 16:01 .Junk drwx------ 5 vmail vmail 4096 5. Dez 16:01 .Trash -rw------- 1 vmail vmail 1833 5. Dez 15:51 dovecot-uidlist drwx------ 3 vmail vmail 4096 3. Dez 06:25 sieve drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Quarantine drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Archive drwx------ 2 vmail vmail 4096 2. Nov 16:41 tmp -rw------- 1 vmail vmail 1080 29. Okt 06:50 dovecot.index -rw------- 1 vmail vmail 24 20. Apr 2024 dovecot-keywords drwx------ 15 vmail vmail 4096 6. Apr 2021 .. -r--r--r-- 1 vmail 1002 0 15. Sep 2018 dovecot-uidvalidity.5b9d20a3 -r--r--r-- 1 vmail vmail 0 15. Sep 2018 dovecot-uidvalidity.5b9d20a7 -rw------- 1 vmail vmail 0 15. Sep 2018 dovecot-acl-list
I explicitly subscribed to the hidden folders via webmail. The "subscriptions" section then looks like this: .Archive .Drafts .INBOX.Fail2ban .INBOX.vzdump .Junk .Quarantine .Sent .Trash Trash Sent
In the webmail interface or in my case in KMail, the folder structure looks like this: Systemmails Dovecot24 |-.Archive |-.Dafts |-.INBOX.Fail2ban |-.INBOX.vzdump |-.Junk |-.Quarantine |-.Sent |-.Trash |-Posteingang |-Trash |-Sent
For the old server with Dovecot 2.3, it looks like this: Systemmails Dovecot23 |-Archive |-Dafts |-Posteingang |-Fail2ban |-vzdump |-Junk |-Quarantine |-Sent |-Trash
That's the issue. Users get completely confused when they
- firstly, can no longer see their previous emails and folders
- secondly, are expected to subscribe to everything they've received so far
- thirdly, are expected to rewrite Sieve scripts or other client filters.
And what does that look like? Where previously subfolders were located directly under "Inbox," there's now an ".INBOX.Subfolder." All previously sent emails are now in ".Sent," including any subfolders, and newly sent emails are stored in a newly created "Sent" folder.
Perhaps this clarifies what I need to solve. We're talking about just this one mail server with 20-30 domains and several thousand email accounts, not to mention the individual folder structure conventions.
Do you have any ideas on how to implement this?
ByteMe
The problem with dot-notation folders in Dovecot 2.3, which were no longer accepted in Dovecot 2.4, is SOLVED!
Solution:
systemctl stop dovecot
/usr/local/bin/migrate-dovecot-folders.sh
systemctl start dovecot
doveadm force-resync -u '*' '*'
Now there is another challenge: many individual user folders are not automatically displayed. This is automatically synchronized using the script "auto-subscribe-dovecot-folders.sh".
Afterward, all mailboxes will function as expected.
Here are the two scripts: #!/usr/bin/bash
migrate-dovecot-folders.sh
Usage:
./migrate-dovecot-folders.sh (dry-run)
./migrate-dovecot-folders.sh --apply (perform changes)
./migrate-dovecot-folders.sh --user user@domain (only that user)
set -euo pipefail
BASE="/var/vmail" DRY_RUN=1 ONLY_USER="" PRESERVE_CASE=1 # 1 = keep original case after .INBOX., 0 = TitleCase
if [[ "${1:-}" == "--apply" ]]; then DRY_RUN=0; shift; fi while [[ $# -gt 0 ]]; do case "$1" in --apply) DRY_RUN=0; shift ;; --user) ONLY_USER="$2"; shift 2 ;; --lower) PRESERVE_CASE=0; shift ;; *) echo "Unknown arg: $1"; exit 1 ;; esac done
log() { echo "$(date +'%F %T') $*" }
transform a source folder (basename) to target name
transform_name() { local src="$1"
if starts with .INBOX. => strip that prefix
if [[ "$src" =~ ^\.INBOX\.(.+) ]]; then local after="${BASH_REMATCH[1]}" if [[ $PRESERVE_CASE -eq 1 ]]; then echo "$after" else # Title case: replace separators and uppercase first letters echo "$after" | sed -E 's/[^A-Za-z0-9]+/ /g' | awk '{ for(i=1;i<=NF;i++){ $i = toupper(substr($i,1,1)) tolower(substr($i,2)) } ; print $0 }' | sed 's/ /_/g' fi return fi
otherwise strip leading dot
if [[ "$src" =~ ^\.(.+) ]]; then echo "${BASH_REMATCH[1]}" return fi
otherwise return unchanged
echo "$src" }
move/merge a mailbox subdir
move_folder() { local userdir="$1" # full path to user dir, e.g. /var/vmail/domain/user local srcname="$2" # basename e.g. .INBOX.Fail2ban local tgtname="$3" # basename e.g. Fail2ban
local src="$userdir/$srcname" local tgt="$userdir/$tgtname"
sanity checks
[[ -d "$src" ]] || { log "SKIP: src not dir: $src"; return 0; }
if [[ "$src" == "$tgt" ]]; then log "SKIP: source == target for $src" return 0 fi
if [[ $DRY_RUN -eq 1 ]]; then log "DRYRUN: would rename '$src' -> '$tgt'" return 0 fi
if target exists, merge contents
if [[ -d "$tgt" ]]; then log "Merging '$src' -> '$tgt'"
# move cur/new/tmp files (avoid clobbering same names)
for part in cur new tmp; do
if [[ -d "$src/$part" ]]; then
mkdir -p "$tgt/$part"
# move files, avoid overwrite: use mv -n if available; otherwise loop
if mv -n "$src/$part/"* "$tgt/$part/" 2>/dev/null; then
true
else
# fallback: move one-by-one with unique suffix
for f in "$src/$part/"*; do
[[ -e "$f" ]] || continue
base="$(basename "$f")"
if [[ -e "$tgt/$part/$base" ]]; then
# append PID timestamp
mv "$f" "$tgt/$part/${base}.$(date +%s).$$"
else
mv "$f" "$tgt/$part/"
fi
done
fi
fi
done
# move dovecot.* files (index/cache/uidlist etc.) - if target has file, we keep target file
for f in dovecot.* maildirsize subscriptions mailbox*; do
if [[ -e "$src/$f" && ! -e "$tgt/$f" ]]; then
mv "$src/$f" "$tgt/"
else
# if both exist, prefer keeping target and remove src's file
if [[ -e "$src/$f" ]]; then
rm -f "$src/$f"
fi
fi
done
# remove now-empty src directories if empty
find "$src" -mindepth 1 -maxdepth 1 | read -r || rmdir --ignore-fail-on-non-empty "$src" || true
chown -R vmail:vmail "$tgt"
log "Merged done: $src -> $tgt"
return 0
fi
Otherwise simple rename
log "Renaming '$src' -> '$tgt'" mv "$src" "$tgt" chown -R vmail:vmail "$tgt" log "Renamed done: $src -> $tgt" }
iterate all user dirs
find_users() { if [[ -n "$ONLY_USER" ]]; then # Only process single user: split domain/user # user dir may be under /var/vmail/<domain>/<localpart or fulluser?> # try to find the exact directory find "$BASE" -mindepth 2 -maxdepth 3 -type d -path "*/$ONLY_USER" 2>/dev/null return fi
list user dirs: /var/vmail/<domain>/<user>
find "$BASE" -mindepth 2 -maxdepth 2 -type d -printf '%h/%f\n' 2>/dev/null }
Main
log "Starting migration script (DRY_RUN=$DRY_RUN) base=$BASE only_user=$ONLY_USER"
while IFS= read -r userdir; do [[ -n "$userdir" ]] || continue
ensure it's a user directory (has cur/new/tmp or maildirfolder)
if [[ ! -d "$userdir" ]]; then continue; fi
scan for dot-folders in that user dir
while IFS= read -r src; do srcbase="$(basename "$src")" # skip standard maildir parts case "$srcbase" in cur|new|tmp|Maildir|dovecot.*|subscriptions|maildirsize) continue ;; esac # select only directories starting with dot OR name equal to "INBOX" variants if [[ "$srcbase" =~ ^\. ]] || [[ "$srcbase" =~ ^INBOX ]]; then tgtbase="$(transform_name "$srcbase")" # if transform yields empty -> skip [[ -n "$tgtbase" ]] || continue # avoid converting e.g. ".INBOX" -> "" (keep INBOX) if [[ "$tgtbase" == "" ]]; then tgtbase="INBOX"; fi move_folder "$userdir" "$srcbase" "$tgtbase" fi done < <(find "$userdir" -mindepth 1 -maxdepth 1 -type d -printf '%p\n' 2>/dev/null) done < <(find_users)
log "Done. If not --apply then this was a dry-run. Verify and then run with --apply."
after running with --apply, run (per-user) reindex/resync, e.g.:
doveadm force-resync -u user@domain '*'
or for many users:
for u in user1@d; do doveadm force-resync -u '*' '*'; done
And: #!/bin/bash
auto-subscribe-dovecot-folders.sh
Automatically subscribes to all existing Maildir folders for all users.
BASE="/var/vmail" SYSTEM_FOLDERS=("Drafts" "Sent" "Junk" "Trash" "Archive" "Quarantine" "INBOX" "new" "cur" "tmp" "sieve")
echo "Starting auto-subscribe of non-system folders..."
Loop through all domains
for DOMAIN in "$BASE"/*; do [ -d "$DOMAIN" ] || continue DOMAIN_NAME=$(basename "$DOMAIN")
Loop through all users
for USERDIR in "$DOMAIN"/*; do [ -d "$USERDIR" ] || continue USERNAME=$(basename "$USERDIR") USER_EMAIL="$USERNAME@$DOMAIN_NAME"
echo "Processing user $USER_EMAIL..."
# Loop through all folders in the user directory
for MAILBOX in "$USERDIR"/*; do
[ -d "$MAILBOX" ] || continue
FOLDER=$(basename "$MAILBOX")
# Check if it's a system folder
SKIP=0
for SYS in "${SYSTEM_FOLDERS[@]}"; do
if [[ "$FOLDER" == "$SYS" ]]; then
SKIP=1
break
fi
done
[ $SKIP -eq 1 ] && continue
# Subscribe to mailbox
echo "Subscribing $FOLDER for $USER_EMAIL..."
doveadm mailbox subscribe -u "$USER_EMAIL" "$FOLDER"
done
done done
echo "Done."
This is working as desired now. I hope this helps everyone who is facing a similar problem.
ByteMe
maildir++ style .foo.bar folders are totally still accepted by Dovecot 2.4, since we have tests that rely on this behaviour. So I am still puzzled why you need to do any of this.
Aki
On 10/12/2025 12:12 EET byteme--- via dovecot <dovecot@dovecot.org> wrote:
The problem with dot-notation folders in Dovecot 2.3, which were no longer accepted in Dovecot 2.4, is SOLVED!
Solution:
systemctl stop dovecot
/usr/local/bin/migrate-dovecot-folders.sh
systemctl start dovecot
doveadm force-resync -u '*' '*'
Now there is another challenge: many individual user folders are not automatically displayed. This is automatically synchronized using the script "auto-subscribe-dovecot-folders.sh".
Afterward, all mailboxes will function as expected.
Here are the two scripts: #!/usr/bin/bash
migrate-dovecot-folders.sh
Usage:
./migrate-dovecot-folders.sh (dry-run)
./migrate-dovecot-folders.sh --apply (perform changes)
./migrate-dovecot-folders.sh --user user@domain (only that user)
set -euo pipefail
BASE="/var/vmail" DRY_RUN=1 ONLY_USER="" PRESERVE_CASE=1 # 1 = keep original case after .INBOX., 0 = TitleCase
if [[ "${1:-}" == "--apply" ]]; then DRY_RUN=0; shift; fi while [[ $# -gt 0 ]]; do case "$1" in --apply) DRY_RUN=0; shift ;; --user) ONLY_USER="$2"; shift 2 ;; --lower) PRESERVE_CASE=0; shift ;; *) echo "Unknown arg: $1"; exit 1 ;; esac done
log() { echo "$(date +'%F %T') $*" }
transform a source folder (basename) to target name
transform_name() { local src="$1"
if starts with .INBOX. => strip that prefix
if [[ "$src" =~ ^\.INBOX\.(.+) ]]; then local after="${BASH_REMATCH[1]}" if [[ $PRESERVE_CASE -eq 1 ]]; then echo "$after" else # Title case: replace separators and uppercase first letters echo "$after" | sed -E 's/[^A-Za-z0-9]+/ /g' | awk '{ for(i=1;i<=NF;i++){ $i = toupper(substr($i,1,1)) tolower(substr($i,2)) } ; print $0 }' | sed 's/ /_/g' fi return fi
otherwise strip leading dot
if [[ "$src" =~ ^\.(.+) ]]; then echo "${BASH_REMATCH[1]}" return fi
otherwise return unchanged
echo "$src" }
move/merge a mailbox subdir
move_folder() { local userdir="$1" # full path to user dir, e.g. /var/vmail/domain/user local srcname="$2" # basename e.g. .INBOX.Fail2ban local tgtname="$3" # basename e.g. Fail2ban
local src="$userdir/$srcname" local tgt="$userdir/$tgtname"
sanity checks
[[ -d "$src" ]] || { log "SKIP: src not dir: $src"; return 0; }
if [[ "$src" == "$tgt" ]]; then log "SKIP: source == target for $src" return 0 fi
if [[ $DRY_RUN -eq 1 ]]; then log "DRYRUN: would rename '$src' -> '$tgt'" return 0 fi
if target exists, merge contents
if [[ -d "$tgt" ]]; then log "Merging '$src' -> '$tgt'"
# move cur/new/tmp files (avoid clobbering same names) for part in cur new tmp; do if [[ -d "$src/$part" ]]; then mkdir -p "$tgt/$part" # move files, avoid overwrite: use mv -n if available; otherwise loop if mv -n "$src/$part/"* "$tgt/$part/" 2>/dev/null; then true else # fallback: move one-by-one with unique suffix for f in "$src/$part/"*; do [[ -e "$f" ]] || continue base="$(basename "$f")" if [[ -e "$tgt/$part/$base" ]]; then # append PID timestamp mv "$f" "$tgt/$part/${base}.$(date +%s).$$" else mv "$f" "$tgt/$part/" fi done fi fi done # move dovecot.* files (index/cache/uidlist etc.) - if target has file, we keep target file for f in dovecot.* maildirsize subscriptions mailbox*; do if [[ -e "$src/$f" && ! -e "$tgt/$f" ]]; then mv "$src/$f" "$tgt/" else # if both exist, prefer keeping target and remove src's file if [[ -e "$src/$f" ]]; then rm -f "$src/$f" fi fi done # remove now-empty src directories if empty find "$src" -mindepth 1 -maxdepth 1 | read -r || rmdir --ignore-fail-on-non-empty "$src" || true chown -R vmail:vmail "$tgt" log "Merged done: $src -> $tgt" return 0fi
Otherwise simple rename
log "Renaming '$src' -> '$tgt'" mv "$src" "$tgt" chown -R vmail:vmail "$tgt" log "Renamed done: $src -> $tgt" }
iterate all user dirs
find_users() { if [[ -n "$ONLY_USER" ]]; then # Only process single user: split domain/user # user dir may be under /var/vmail/<domain>/<localpart or fulluser?> # try to find the exact directory find "$BASE" -mindepth 2 -maxdepth 3 -type d -path "*/$ONLY_USER" 2>/dev/null return fi
list user dirs: /var/vmail/<domain>/<user>
find "$BASE" -mindepth 2 -maxdepth 2 -type d -printf '%h/%f\n' 2>/dev/null }
Main
log "Starting migration script (DRY_RUN=$DRY_RUN) base=$BASE only_user=$ONLY_USER"
while IFS= read -r userdir; do [[ -n "$userdir" ]] || continue
ensure it's a user directory (has cur/new/tmp or maildirfolder)
if [[ ! -d "$userdir" ]]; then continue; fi
scan for dot-folders in that user dir
while IFS= read -r src; do srcbase="$(basename "$src")" # skip standard maildir parts case "$srcbase" in cur|new|tmp|Maildir|dovecot.*|subscriptions|maildirsize) continue ;; esac # select only directories starting with dot OR name equal to "INBOX" variants if [[ "$srcbase" =~ ^\. ]] || [[ "$srcbase" =~ ^INBOX ]]; then tgtbase="$(transform_name "$srcbase")" # if transform yields empty -> skip [[ -n "$tgtbase" ]] || continue # avoid converting e.g. ".INBOX" -> "" (keep INBOX) if [[ "$tgtbase" == "" ]]; then tgtbase="INBOX"; fi move_folder "$userdir" "$srcbase" "$tgtbase" fi done < <(find "$userdir" -mindepth 1 -maxdepth 1 -type d -printf '%p\n' 2>/dev/null) done < <(find_users)
log "Done. If not --apply then this was a dry-run. Verify and then run with --apply."
after running with --apply, run (per-user) reindex/resync, e.g.:
doveadm force-resync -u user@domain '*'
or for many users:
for u in user1@d; do doveadm force-resync -u '*' '*'; done
And: #!/bin/bash
auto-subscribe-dovecot-folders.sh
Automatically subscribes to all existing Maildir folders for all users.
BASE="/var/vmail" SYSTEM_FOLDERS=("Drafts" "Sent" "Junk" "Trash" "Archive" "Quarantine" "INBOX" "new" "cur" "tmp" "sieve")
echo "Starting auto-subscribe of non-system folders..."
Loop through all domains
for DOMAIN in "$BASE"/*; do [ -d "$DOMAIN" ] || continue DOMAIN_NAME=$(basename "$DOMAIN")
Loop through all users
for USERDIR in "$DOMAIN"/*; do [ -d "$USERDIR" ] || continue USERNAME=$(basename "$USERDIR") USER_EMAIL="$USERNAME@$DOMAIN_NAME"
echo "Processing user $USER_EMAIL..." # Loop through all folders in the user directory for MAILBOX in "$USERDIR"/*; do [ -d "$MAILBOX" ] || continue FOLDER=$(basename "$MAILBOX") # Check if it's a system folder SKIP=0 for SYS in "${SYSTEM_FOLDERS[@]}"; do if [[ "$FOLDER" == "$SYS" ]]; then SKIP=1 break fi done [ $SKIP -eq 1 ] && continue # Subscribe to mailbox echo "Subscribing $FOLDER for $USER_EMAIL..." doveadm mailbox subscribe -u "$USER_EMAIL" "$FOLDER" donedone done
echo "Done."
This is working as desired now. I hope this helps everyone who is facing a similar problem.
ByteMe
dovecot mailing list -- dovecot@dovecot.org To unsubscribe send an email to dovecot-leave@dovecot.org
You haven't provided a plausible solution to this claim yet. So, for me, it's just an unproven assertion! Okay?
Furthermore, you haven't provided a single working solution for our situation. If anything, we've only received brief statements, no explanation of why or how!
The fact is, it didn't work here! Therefore, "maildir++ style .foo.bar folders are totally still accepted by Dovecot 2.4" is an unproven and boastful claim. I've now found a solution.
Your complete upgrade from 2.3 to 2.4, which was supposed to run better than major release 3.0, is a disgrace in terms of implementation and, above all, documentation. Given these facts, I would suggest you be a bit more modest and at least say thank you for the external support.
These kinds of half-baked claims really make me angry!
ByteMe
With simple minimal config
dovecot_config_version = 2.4.2 dovecot_storage_version = 2.4.0 mail_driver = maildir mail_home = /home/vmail/%{user} mail_path = ~/mail passdb static { password = # hidden, use -P to show it }
doveadm mailbox create -u testuser foo.bar.baz
~# find /home/vmail/testuser/ /home/vmail/testuser/mail/tmp /home/vmail/testuser/mail/cur /home/vmail/testuser/mail/new /home/vmail/testuser/mail/.foo.bar.baz /home/vmail/testuser/mail/.foo.bar.baz/tmp /home/vmail/testuser/mail/.foo.bar.baz/new /home/vmail/testuser/mail/.foo.bar.baz/dovecot.index.log /home/vmail/testuser/mail/.foo.bar.baz/maildirfolder /home/vmail/testuser/mail/.foo.bar.baz/cur /home/vmail/testuser/mail/.foo.bar.baz/dovecot-uidlist
Does this suffice as evidence?
Aki
On 10/12/2025 12:34 EET byteme--- via dovecot <dovecot@dovecot.org> wrote:
You haven't provided a plausible solution to this claim yet. So, for me, it's just an unproven assertion! Okay?
Furthermore, you haven't provided a single working solution for our situation. If anything, we've only received brief statements, no explanation of why or how!
The fact is, it didn't work here! Therefore, "maildir++ style .foo.bar folders are totally still accepted by Dovecot 2.4" is an unproven and boastful claim. I've now found a solution.
Your complete upgrade from 2.3 to 2.4, which was supposed to run better than major release 3.0, is a disgrace in terms of implementation and, above all, documentation. Given these facts, I would suggest you be a bit more modest and at least say thank you for the external support.
These kinds of half-baked claims really make me angry!
ByteMe
dovecot mailing list -- dovecot@dovecot.org To unsubscribe send an email to dovecot-leave@dovecot.org
No, that's definitely not enough! You have a completely different structure:
"/home/vmail/testuser/mail/.foo.bar.baz"
We had /var/vmail/example.com/wh/.INBOX.Fritzbox/
- The mail directories are located in a completely different place.
- We have ".INBOX." before the folders everywhere.
- This isn't about creating new users, but about migrating several hundred gigabytes of emails!
Please read through the migration script; it shows everything we had to do. As I can see on LinkedIn, you're a developer. I'm certainly not going to test that again now; it's definitely too late. And frankly, the explanations are missing again. That would be too much guesswork.
I've finished it and don't want to discuss your Patchwork 2.4 any further. Let everyone else form their own opinions about this kind of developer work.
For some reason your original email showed up only now. Can you post your doveconf from the *old* server as well as doveconf from *new* server, so it would make it lot easier to know why it's not working.
Aki
On 04/12/2025 12:38 EET byteme--- via dovecot <dovecot@dovecot.org> wrote:
Hello everyone,
I have a Dovecot 2.3 installation on a Debian 12 Linux system that is being upgraded to Dovecot 2.4. The server hosts many domains with numerous email accounts. Users had created folder structures containing many received and sent emails, following the Maildir standard as "dot-prefixed folders."
When switching to Dovecot 2.4, all these emails and folders are no longer visible. This is what it looks like on the old system:
root@mx1 /var/vmail/example.com/systemmails # ll -a insgesamt 256 drwx------ 14 vmail vmail 4096 3. Nov 09:15 . drwx------ 15 vmail vmail 4096 6. Apr 2021 .. drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Archive drwx------ 2 vmail vmail 86016 27. Okt 12:19 cur -rw------- 1 vmail vmail 0 15. Sep 2018 dovecot-acl-list -rw------- 1 vmail vmail 1080 29. Okt 06:50 dovecot.index -rw------- 1 vmail vmail 23400 30. Okt 01:36 dovecot.index.cache -rw------- 1 vmail vmail 10600 30. Okt 01:41 dovecot.index.log -rw------- 1 vmail vmail 6627 26. Aug 09:56 dovecot.index.thread -rw------- 1 vmail vmail 24 20. Apr 2024 dovecot-keywords -rw------- 1 vmail vmail 1256 3. Nov 09:05 dovecot.list.index -rw------- 1 vmail vmail 5156 3. Nov 09:15 dovecot.list.index.log -rw------- 1 vmail vmail 672 15. Jul 16:45 dovecot.mailbox.log -rw------- 1 vmail vmail 1781 30. Okt 01:36 dovecot-uidlist -rw------- 1 vmail vmail 8 15. Jul 16:45 dovecot-uidvalidity -r--r--r-- 1 vmail 1002 0 15. Sep 2018 dovecot-uidvalidity.5b9d20a3 -r--r--r-- 1 vmail vmail 0 15. Sep 2018 dovecot-uidvalidity.5b9d20a5 drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Drafts drwx------ 5 vmail vmail 4096 3. Nov 09:15 .INBOX.Fail2ban drwx------ 5 vmail vmail 4096 3. Nov 09:15 .INBOX.vzdump drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Junk drwx------ 2 vmail vmail 36864 30. Okt 01:36 new drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Quarantine drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Sent drwx------ 3 vmail vmail 4096 3. Dez 06:25 sieve -rw------- 1 vmail vmail 70 15. Jul 16:45 subscriptions drwx------ 2 vmail vmail 4096 2. Nov 16:41 tmp drwx------ 5 vmail vmail 4096 3. Nov 09:15 .Trash
This should be preserved so that users don't notice the system upgrade. Dovecot 2.4 works quite differently in this regard. What solution is there for migrating both the namespace and the existing emails to Dovecot 2.4?
Thank you for your suggestions
ByteMe
dovecot mailing list -- dovecot@dovecot.org To unsubscribe send an email to dovecot-leave@dovecot.org
participants (5)
-
Aki Tuomi
-
byteme@linuxmaker.net
-
dovecot@ptld.com
-
John Stoffel
-
Piotr Szafarczyk