diff --git a/doc/guix.texi b/doc/guix.texi
index 51dc42e5a2..13cd86779e 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -18404,6 +18404,14 @@ Available @code{dovecot-configuration} fields are:
 The dovecot package.
 @end deftypevr
 
+@deftypevr {@code{dovecot-configuration} parameter} package-list extensions
+Plugins and extensions to the Dovecot package.  Specify a list of
+dovecot plugins that needs to be available for dovecot and its modules.
+
+Defaults to @samp{()}.
+
+@end deftypevr
+
 @deftypevr {@code{dovecot-configuration} parameter} comma-separated-string-list listen
 A list of IPs or hosts where to listen for connections.  @samp{*}
 listens on all IPv4 interfaces, @samp{::} listens on all IPv6
diff --git a/gnu/services/mail.scm b/gnu/services/mail.scm
index 71fa975b5d..b49fc07916 100644
--- a/gnu/services/mail.scm
+++ b/gnu/services/mail.scm
@@ -468,11 +468,21 @@ as @code{#t}.)")
               (serialize-namespace-configuration field-name val))
             val))
 
+(define (package-list? val)
+  (and (list? val) (and-map package? val)))
+(define (serialize-package-list field-name val)
+  #f)
+
 (define-configuration dovecot-configuration
   (dovecot
    (package dovecot)
    "The dovecot package.")
 
+  (extensions
+   (package-list '())
+   "Plugins and extensions to the Dovecot package. Specify a list of dovecot
+plugins that needs to be available for dovecot and its modules.")
+
   (listen
    (comma-separated-string-list '("*" "::"))
    "A list of IPs or hosts where to listen in for connections.  @samp{*}
@@ -1439,6 +1449,11 @@ greyed out, instead of only later giving \"not selectable\" popup error.
    (package dovecot)
    "The dovecot package.")
 
+  (extensions
+   (package-list '())
+   "Plugins and extensions to the Dovecot package. Specify a list of dovecot
+plugins that needs to be available for dovecot and its modules.")
+
   (string
    (string (configuration-missing-field 'opaque-dovecot-configuration
                                         'string))
@@ -1464,6 +1479,29 @@ greyed out, instead of only later giving \"not selectable\" popup error.
          (home-directory "/var/empty")
          (shell (file-append shadow "/sbin/nologin")))))
 
+(define (%dovecot-moduledir packages)
+  ;; Create a union of the set of modules and dovecot itself.
+  (computed-file
+   "dovecot-moduledir"
+   (with-imported-modules '((guix build utils))
+     #~(begin
+         (use-modules (guix build utils))
+         (mkdir #$output)
+         (for-each
+          (lambda (package)
+            (let ((path (string-append package "/lib/dovecot")))
+              (for-each
+               (lambda (src)
+                 (let* ((tail (substring src (string-length path)))
+                        (dst (string-append #$output tail)))
+                   (mkdir-p (dirname dst))
+                   (if (file-exists? dst)
+                       (format (current-error-port) "warning: ~a exists\n" dst)
+                       (symlink src dst))))
+               (find-files path))))
+          (list #$@packages))
+         #t))))
+
 (define (%dovecot-activation config)
   ;; Activation gexp.
   (let ((config-str
@@ -1474,7 +1512,15 @@ greyed out, instead of only later giving \"not selectable\" popup error.
            (with-output-to-string
              (lambda ()
                (serialize-configuration config
-                                        dovecot-configuration-fields)))))))
+                                        dovecot-configuration-fields))))))
+        (moduledir-directory
+         (cond
+          ((opaque-dovecot-configuration? config)
+           (%dovecot-moduledir (cons* (opaque-dovecot-configuration-dovecot config)
+                                      (opaque-dovecot-configuration-extensions config))))
+          (else
+           (%dovecot-moduledir (cons* (dovecot-configuration-dovecot config)
+                                      (dovecot-configuration-extensions config)))))))
     #~(begin
         (use-modules (guix build utils))
         (define (mkdir-p/perms directory owner perms)
@@ -1521,13 +1567,18 @@ greyed out, instead of only later giving \"not selectable\" popup error.
              (else
               (format (current-error-port)
                       "Failed to create public key at ~a.\n" public-key)))))
-        (let ((user (getpwnam "dovecot")))
+        (let ((user (getpwnam "dovecot"))
+              (moduledir-symlink "/usr/lib/dovecot"))
           (mkdir-p/perms "/var/run/dovecot" user #o755)
           (mkdir-p/perms "/var/lib/dovecot" user #o755)
           (mkdir-p/perms "/etc/dovecot" user #o755)
           (copy-file #$(plain-file "dovecot.conf" config-str)
                      "/etc/dovecot/dovecot.conf")
           (mkdir-p/perms "/etc/dovecot/private" user #o700)
+          (mkdir-p (dirname moduledir-symlink))
+          (when (file-exists? moduledir-symlink)
+            (delete-file moduledir-symlink))
+          (symlink #$moduledir-directory moduledir-symlink)
           (create-self-signed-certificate-if-absent
            #:private-key "/etc/dovecot/private/default.pem"
            #:public-key "/etc/dovecot/default.pem"
