[PATCH] services: mail: add opensmtpd records to enhance opensmtpd-configuration.

OpenSubmitted by Joshua Branson.
Details
2 participants
  • Joshua Branson
  • Liliana Marie Prikler
Owner
unassigned
Severity
normal
J
J
Joshua Branson wrote on 17 Jun 23:46 +0200
(address . guix-patches@gnu.org)(name . Joshua Branson)(address . jbranso@dismail.de)
20220617214618.12377-1-jbranso@dismail.de
Openmstpd-configuration may only be configured by a config-file. This
patch, enables one to configure opensmtpd by using some guile record
types (defined via define-record-type*).

This patch is mostly complete, but I could use some guidance on what
else needs to be done for it to be accepted in to guix properly.
I do have some documentation written for the opensmtpd-service,
but it is probably not complete and is currently written in the
org-mode format.

* gnu/services/mail.scm (opensmtpd-table-configuration): New record.
* gnu/services/mail.scm (opensmtpd-ca-configuration): New record.
* gnu/services/mail.scm (opensmtpd-pki-configuration): New record.
* gnu/services/mail.scm (opensmtpd-action-local-delivery-configuration): New record.
* gnu/services/mail.scm (opensmtpd-maildir-configuration): New record.
* gnu/services/mail.scm (opensmtpd-mda-configuration): New record.
* gnu/services/mail.scm (opensmtpd-action-relay-configuration): New record.
* gnu/services/mail.scm (opensmtpd-option-configuration): New record.
* gnu/services/mail.scm (opensmtpd-filter-phase-configuration): New record.
* gnu/services/mail.scm (opensmtpd-filter-configuration): New record.
* gnu/services/mail.scm (opensmtpd-listen-on-configuration): New record.
* gnu/services/mail.scm (opensmtpd-listen-on-socket-configuration): New record.
* gnu/services/mail.scm (opensmtpd-match-configuration): New record.
* gnu/services/mail.scm (opensmtpd-smtp-configuration): New record.
* gnu/services/mail.scm (opensmtpd-srs-configuration): New record.
* gnu/services/mail.scm (opensmtpd-queue-configuration): New record.
* gnu/services/mail.scm (opensmtpd-configuration): New record.
---
gnu/services/mail.scm | 2016 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 2013 insertions(+), 3 deletions(-)

Toggle diff (2042 lines)
diff --git a/gnu/services/mail.scm b/gnu/services/mail.scm
index d99743ac31..bdc0ee3bf7 100644
--- a/gnu/services/mail.scm
+++ b/gnu/services/mail.scm
@@ -57,8 +57,143 @@ (define-module (gnu services mail)
             mailbox-configuration
             namespace-configuration
 
+            opensmtpd-table-configuration
+            opensmtpd-table-configuration?
+            opensmtpd-table-configuration-name
+            opensmtpd-table-configuration-file-db
+            opensmtpd-table-configuration-data
+
+            opensmtpd-ca-configuration
+            opensmtpd-ca-configuration?
+            opensmtpd-ca-configuration-name
+            opensmtpd-ca-configuration-file
+
+            opensmtpd-pki-configuration
+            opensmtpd-pki-configuration?
+            opensmtpd-pki-configuration-domain
+            opensmtpd-pki-configuration-cert
+            opensmtpd-pki-configuration-key
+            opensmtpd-pki-configuration-dhe
+
+            opensmtpd-action-local-delivery-configuration
+            opensmtpd-action-local-delivery-configuration?
+            opensmtpd-action-local-delivery-configuration-method
+            opensmtpd-action-local-delivery-configuration-alias
+            opensmtpd-action-local-delivery-configuration-ttl
+            opensmtpd-action-local-delivery-configuration-user
+            opensmtpd-action-local-delivery-configuration-userbase
+            opensmtpd-action-local-delivery-configuration-virtual
+            opensmtpd-action-local-delivery-configuration-wrapper
+
+            opensmtpd-maildir-configuration
+            opensmtpd-maildir-configuration?
+            opensmtpd-maildir-configuration-pathname
+            opensmtpd-maildir-configuration-junk
+
+            opensmtpd-mda-configuration
+            opensmtpd-mda-configuration-name
+            opensmtpd-mda-configuration-command
+
+            opensmtpd-action-relay-configuration
+            opensmtpd-action-relay-configuration?
+            opensmtpd-action-relay-configuration-backup
+            opensmtpd-action-relay-configuration-backup-mx
+            opensmtpd-action-relay-configuration-helo
+            opensmtpd-action-relay-configuration-domain
+            opensmtpd-action-relay-configuration-host
+            opensmtpd-action-relay-configuration-pki
+            opensmtpd-action-relay-configuration-srs
+            opensmtpd-action-relay-configuration-tls
+            opensmtpd-action-relay-configuration-auth
+            opensmtpd-action-relay-configuration-mail-from
+            opensmtpd-action-relay-configuration-src
+
+            opensmtpd-option-configuration
+            opensmtpd-option-configuration?
+            opensmtpd-option-configuration-option
+            opensmtpd-option-configuration-not
+            opensmtpd-option-configuration-regex
+            opensmtpd-option-configuration-data
+
+            opensmtpd-filter-phase-configuration
+            opensmtpd-filter-phase-configuration?
+            opensmtpd-filter-phase-configuration-name
+            opensmtpd-filter-phase-configuration-phase-name
+            opensmtpd-filter-phase-configuration-options
+            opensmtpd-filter-phase-configuration-decision
+            opensmtpd-filter-phase-configuration-message
+            opensmtpd-filter-phase-configuration-value
+
+            opensmtpd-filter-configuration
+            opensmtpd-filter-configuration?
+            opensmtpd-filter-configuration-name
+            opensmtpd-filter-configuration-proc
+
+            opensmtpd-listen-on-configuration
+            opensmtpd-listen-on-configuration?
+            opensmtpd-listen-on-configuration-interface
+            opensmtpd-listen-on-configuration-family
+            opensmtpd-listen-on-configuration-auth
+            opensmtpd-listen-on-configuration-auth-optional
+            opensmtpd-listen-on-configuration-filters
+            opensmtpd-listen-on-configuration-hostname
+            opensmtpd-listen-on-configuration-hostnames
+            opensmtpd-listen-on-configuration-mask-src
+            opensmtpd-listen-on-configuration-disable-dsn
+            opensmtpd-listen-on-configuration-pki
+            opensmtpd-listen-on-configuration-port
+            opensmtpd-listen-on-configuration-proxy-v2
+            opensmtpd-listen-on-configuration-received-auth
+            opensmtpd-listen-on-configuration-senders
+            opensmtpd-listen-on-configuration-secure-connection
+            opensmtpd-listen-on-configuration-tag
+
+            opensmtpd-listen-on-socket-configuration
+            opensmtpd-listen-on-socket-configuration?
+            opensmtpd-listen-on-socket-configuration-filters
+            opensmtpd-listen-on-socket-configuration-mask-src
+            opensmtpd-listen-on-socket-configuration-tag
+
+            opensmtpd-match-configuration
+            opensmtpd-match-configuration?
+            opensmtpd-match-configuration-action
+            opensmtpd-match-configuration-options
+
+            opensmtpd-smtp-configuration
+            opensmtpd-smtp-configuration?
+            opensmtpd-smtp-configuration-ciphers
+            opensmtpd-smtp-configuration-limit-max-mails
+            opensmtpd-smtp-configuration-limit-max-rcpt
+            opensmtpd-smtp-configuration-max-message-size
+            opensmtpd-smtp-configuration-sub-addr-delim character
+
+            opensmtpd-srs-configuration
+            opensmtpd-srs-configuration?
+            opensmtpd-srs-configuration-key
+            opensmtpd-srs-configuration-backup-key
+            opensmtpd-srs-configuration-ttl-delay
+
+            opensmtpd-queue-configuration
+            opensmtpd-queue-configuration?
+            opensmtpd-queue-configuration-compression
+            opensmtpd-queue-configuration-encryption
+            opensmtpd-queue-configuration-ttl-delay
+
             opensmtpd-configuration
             opensmtpd-configuration?
+            opensmtpd-package
+            opensmtpd-config-file
+            opensmtpd-configuration-bounce
+            opensmtpd-configuration-listen-ons
+            opensmtpd-configuration-listen-on-socket
+            opensmtpd-configuration-includes
+            opensmtpd-configuration-matches
+            opensmtpd-configuration-mda-wrappers
+            opensmtpd-configuration-mta-max-deferred
+            opensmtpd-configuration-srs
+            opensmtpd-configuration-smtp
+            opensmtpd-configuration-queue
+
             opensmtpd-service-type
             %default-opensmtpd-config-file
 
@@ -1651,13 +1786,1888 @@ (define (generate-dovecot-documentation)
 ;;; OpenSMTPD.
 ;;;
 
+;; some fieldnames have a default value of #f, which is ok.  They cannot have a value of #t.
+;; for example opensmtpd-table-configuration-data can be #f, BUT NOT true.
+;; my/sanitize procedure tests values to see if they are of the right kind.
+;; procedure false? is needed to allow fields like 'values' to be blank, (empty), or #f BUT also
+;; have a value like a list of strings.
+(define (false? var)
+  (eq? #f var))
+
+;; this procedure takes in a var and a list of procedures.  It loops through list of procedures passing in var to each.
+;; if one procedure returns #t, the function returns true.  Otherwise #f.
+;; TODO for fun rewrite this using map
+;; If I rewrote it in map, then it may help with sanitizing.
+;; eg: I could then potentially easily sanitize vars with lambda procedures.
+(define (is-value-right-type? var list-of-procedures record fieldname)
+  (if (null? list-of-procedures)
+        #f
+        (cond [(procedure? (car list-of-procedures))
+               (if ((car list-of-procedures) var)
+                   #t
+                   (is-value-right-type? var (cdr list-of-procedures) record fieldname))]
+              [(and (sanitize-configuration? (car list-of-procedures))
+                    (sanitize-configuration-error-if-proc-fails (car list-of-procedures))
+                    (if ((sanitize-configuration-proc (car list-of-procedures)) var)
+                        #t
+                        (begin
+                          (apply string-append
+                                 (sanitize-configuration-error-message (car list-of-procedures)))
+                          (throw 'bad! var))))]
+              [else (if ((sanitize-configuration-proc (car list-of-procedures)) var)
+                        #t
+                        (is-value-right-type? var (cdr list-of-procedures) record fieldname))])))
+
+;; converts strings like this:
+;; "apple, ham, cherry" -> "apple, ham, or cherry"
+;; "pineapple" -> "pinneapple".
+;; "cheese, grapefruit, or jam" -> "cheese, grapefruit, or jam"
+(define (add-comma-or string)
+  (define last-comma-location (string-rindex string #\,))
+  (if last-comma-location
+      (if (string-contains string ", or" last-comma-location)
+          string
+          (string-replace string ", or" last-comma-location
+                          (+ 1 last-comma-location)))
+      string))
+
+;; I could test for read-ability of a file, but then I would have to
+;; test the program as root everytime instead of as a normal user...
+(define (file-exists? file)
+(if (string? file)
+    (access? file F_OK)
+    #f))
+
+(define (list-of-procedures->string procedures)
+  (define string
+    (let loop ([procedures procedures])
+      (if (null? procedures)
+          ""
+          (begin
+            (string-append
+             (cond [(eq? false? (car procedures))
+                    "#f , "]
+                   [(eq? boolean? (car procedures))
+                    "boolean, "]
+                   [(eq? string? (car procedures))
+                    "string, "]
+                   [(eq? integer? (car procedures))
+                    "integer, "]
+                   [(eq? list-of-strings? (car procedures))
+                    "list of strings, "]
+                   [(eq? assoc-list? (car procedures))
+                    "an association list, "]
+                   [(eq? opensmtpd-pki-configuration? (car procedures))
+                    "an <opensmtpd-pki-configuration> record, "]
+                   [(eq? opensmtpd-table-configuration? (car procedures))
+                    "an <opensmtpd-table-configuration> record, "]
+                   [(eq? list-of-unique-opensmtpd-match-configuration? (car procedures))
+                    "a list of unique <opensmtpd-match-configuration> records, "]
+                   [(eq? table-whose-data-are-assoc-list? (car procedures))
+                    (string-append
+                    "an <opensmtpd-table-configuration> record whose fieldname 'values' are an assoc-list \n"
+                    "(eg: (opensmtpd-table-configuration (name \"table\") (data '(\"joshua\" . \"$encrypted$password\")))), ")]
+                   [(eq? file-exists? (car procedures))
+                    "file, "]
+                   [else "has an incorrect value, "])
+             (loop (cdr procedures)))))))
+  (add-comma-or (string-append (string-drop-right string 2) ".\n")))
+
+;; TODO can I M-x raise-sexp (string=? string var) in this procedure? and get rid of checking
+;; if the var is a string?  The previous string-in-list? had that check.
+;; (string-in-list? '("hello" 5 "cat")) currently works.  If I M-x raise-sexp (string=? string var)
+;; then it will no longer work.
+(define (string-in-list? string list)
+  (primitive-eval (cons 'or (map (lambda (var) (and (string? var) (string=? string var))) list))))
+
+(define (my/sanitize var record fieldname list-of-procedures)
+  (if (is-value-right-type? var list-of-procedures record fieldname)
+      var
+      (begin
+        (display (string-append "<" record "> fieldname: '" fieldname "' is of type "
+                                (list-of-procedures->string list-of-procedures) "\n"))
+        (throw 'bad! var))))
+
+;; Some example opensmtpd-table-configurations:
+;;
+;;  (opensmtpd-table-configuration (name "root accounts") (data '(("joshua" . "root@dismail.de") ("joshua" . "postmaster@dismail.de"))))
+;;  (opensmtpd-table-configuration (name "root accounts") (data (list "mysite.me" "your-site.com")))
+;;  TODO should <opensmtpd-table-configuration> support have a fieldname 'file'?
+;;  Or should I change name to name-or-file ?
+(define-record-type* <opensmtpd-table-configuration>
+  opensmtpd-table-configuration make-opensmtpd-table-configuration
+  opensmtpd-table-configuration?
+  this-record
+  (name opensmtpd-table-configuration-name ;; string
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-table-configuration" "name" (list string?)))))
+  (file-db opensmtpd-table-configuration-file-db
+           (default #f)
+           (sanitize (lambda (var)
+                       (my/sanitize var "opensmtpd-table-configuration" "file-db"
+                                    (list boolean?)))))
+  ;; FIXME support an aliasing table as described here:
+  ;; https://man.openbsd.org/table.5
+  ;; One may have to use the record file for this.  I don't think tables support a table like this:
+  ;; table "name" { joshua = joshua@gnucode.me,joshua@gnu-hurd.com,joshua@propernaming.org, root = root@gnucode.me }
+  ;; If values is an absolute filename, then it will use said filename to house the table info.
+  ;; filename must be an absolute filename.
+  (data opensmtpd-table-configuration-data
+          (default #f)
+          (sanitize (lambda (var)
+                      (my/sanitize var "opensmtpd-table-configuration" "values"
+                                   (list file-exists? list-of-strings? assoc-list?)))))
+  ;; is a list of values or key values
+  ;; eg: (list "mysite.me" "your-site.com")
+  ;; eg: (list ("joshua" . "joshua@gnu.org") ("james" . "james@gnu.org"))
+  ;; I am currently making these values be as assocation list of strings only.
+  ;; FIXME should I allow a var like this?
+  ;; (list (cons "gnucode.me" 234.949.392.23))
+  ;; can be of type: (quote list-of-strings) or (quote assoc-list)
+  ;; (opensmtpd-table-configuration-type record) returns the values' type.  The user SHOULD NEVER set the type.
+  ;; TODO jpoiret: on irc reccomends that I just use an outside function to determine fieldname 'values', type.
+  ;; it would be "simpler" and possibly easier for the next person working on this code to understand what is happening.
+  (type opensmtpd-table-configuration-type
+        (default #f)
+        (thunked)
+        (sanitize (lambda (var)
+                    (cond [(opensmtpd-table-configuration-data this-record)
+                           (if (list-of-strings? (opensmtpd-table-configuration-data this-record))
+                               (quote list-of-strings)
+                               (quote assoc-list))]
+                          [(file-exists? (opensmtpd-table-configuration-data this-record))
+                           (if (opensmtpd-table-configuration-file-db this-record)
+                               (quote db)
+                               (quote file))]
+                          [else
+                           (display "opensmtpd-table-configuration-type is broke\n")
+                           (throw 'bad! var)])))))
+
+(define-record-type* <opensmtpd-ca-configuration>
+  opensmtpd-ca-configuration make-opensmtpd-ca-configuration
+  opensmtpd-ca-configuration?
+  (name opensmtpd-ca-configuration-name
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-ca-configuration" "name" (list string?)))))
+  (file opensmtpd-ca-configuration-file
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-ca-configuration" "file" (list file-exists?))))))
+
+(define-record-type* <opensmtpd-pki-configuration>
+  opensmtpd-pki-configuration make-opensmtpd-pki-configuration
+  opensmtpd-pki-configuration?
+  (domain opensmtpd-pki-configuration-domain
+          (default #f)
+          (sanitize (lambda (var)
+                      (my/sanitize var "opensmtpd-pki-configuration" "domain" (list string?)))))
+  ;; TODO/FIXME this should probably be a list of files.  The opensmtpd documentation says
+  ;; that you could have a list of files:
+  ;;
+  ;; pki pkiname cert certfile
+  ;; Associate certificate file certfile with host pkiname, and use that file to prove
+  ;; the identity of the mail server to clients.  pkiname is the server's name, de‐
+  ;; rived from the default hostname or set using either
+  ;; /gnu/store/2d13sdz76ldq8zgwv4wif0zx7hkr3mh2-opensmtpd-6.8.0p2/etc/mailname or us‐
+  ;; ing the hostname directive.  If a fallback certificate or SNI is wanted, the ‘*’
+  ;; wildcard may be used as pkiname.
+
+  ;; A certificate chain may be created by appending one or many certificates, includ‐
+  ;; ing a Certificate Authority certificate, to certfile.  The creation of certifi‐
+  ;; cates is documented in starttls(8).
+  (cert opensmtpd-pki-configuration-cert
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-pki-configuration" "cert" (list file-exists?)))))
+  (key opensmtpd-pki-configuration-key
+       (default #f)
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-pki-configuration" "key" (list file-exists?)))))
+  ; todo sanitize this. valid parameters are "none", "legacy", or "auto".
+  (dhe opensmtpd-pki-configuration-dhe
+       (default #f)
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-dhe" "dhe" (list false? string?))))))
+
+(define-record-type* <opensmtpd-lmtp-configuration>
+  opensmtpd-lmtp-configuration make-opensmtpd-lmtp-configuration
+  opensmtpd-lmtp-configuration?
+  (destination opensmtpd-lmtp-configuration-destination
+               (default #f)
+               (sanitize (lambda (var)
+                           (my/sanitize var "opensmtpd-lmtp-configuration" "destination"
+                                        (list string?)))))
+  (rcpt-to opensmtpd-lmtp-configuration-rcpt-to
+           (default #f)
+           (sanitize (lambda (var)
+                       (my/sanitize var "opensmtpd-lmtp-configuration" "rcpt-to"
+                                    (list false? string?))))))
+
+(define-record-type* <opensmtpd-mda-configuration>
+  opensmtpd-mda-configuration make-opensmtpd-mda-configuration
+  opensmtpd-mda-configuration?
+  (name opensmtpd-mda-configuration-name
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-mda-configuration" "name"
+                                 (list string?)))))
+  ;; TODO should I allow this command to be a gexp?
+  (command opensmtpd-mda-configuration-command
+           (default #f)
+           (sanitize (lambda (var)
+                       (my/sanitize var "opensmtpd-mda-configuration" "command"
+                                    (list string?))))))
+
+(define-record-type* <opensmtpd-maildir-configuration>
+  opensmtpd-maildir-configuration make-opensmtpd-maildir-configuration
+  opensmtpd-maildir-configuration?
+  (pathname opensmtpd-maildir-configuration-pathname
+            (default #f)
+            (sanitize (lambda (var)
+                        (my/sanitize var "opensmtpd-maildir-configuration" "pathname"
+                                     (list false? string?)))))
+  (junk opensmtpd-maildir-configuration-junk
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-maildir-configuration" "junk"
+                                 (list boolean?))))))
+
+(define-record-type* <opensmtpd-action-local-delivery-configuration>
+  opensmtpd-action-local-delivery-configuration make-opensmtpd-action-local-delivery-configuration
+  opensmtpd-action-local-delivery-configuration?
+  (name opensmtpd-action-local-delivery-configuration-name
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-action-local-delivery-configuration" "name"
+                                 (list string?)))))
+  (method opensmtpd-action-local-delivery-configuration-method
+          (default "mbox")
+          (sanitize (lambda (var)
+                      (cond
+                       [(or (opensmtpd-lmtp-configuration? var)
+                            (opensmtpd-maildir-configuration? var)
+                            (opensmtpd-mda-configuration? var)
+                            (string=? var "mbox")
+                            (string=? var "expand-only")
+                            (string=? var "forward-only"))
+                        var]
+                       [else
+                        (begin
+                          (display (string-append "<opensmtpd-action-local-delivery-configuration> fieldname 'method' must be of type \n"
+                                                  "\"mbox\", \"expand-only\", \"forward-only\" \n"
+                                                  "<opensmtpd-lmtp-configuration>, <opensmtpd-maildir-configuration>, \n"
+                                                  "or <opensmtpd-mda-configuration>.\n"))
+                          (throw 'bad! var))]))))
+  (alias opensmtpd-action-local-delivery-configuration-alias
+         (default #f)
+         (sanitize (lambda (var)
+                     (my/sanitize var "opensmtpd-action-local-delivery-configuration" "alias"
+                                  (list false? opensmtpd-table-configuration?)))))
+  (ttl opensmtpd-action-local-delivery-configuration-ttl
+       (default #f)
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-action-local-delivery-configuration" "ttl"
+                                (list false? string?)))))
+  (user opensmtpd-action-local-delivery-configuration-user
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-action-local-delivery-configuration" "user"
+                                 (list false? string?)))))
+  (userbase opensmtpd-action-local-delivery-configuration-userbase
+            (default #f)
+            (sanitize (lambda (var)
+                        (my/sanitize var "opensmtpd-action-local-delivery-configuration" "userbase"
+                                     (list false? opensmtpd-table-configuration?)))))
+  (virtual opensmtpd-action-local-delivery-configuration-virtual
+           (default #f)
+           (sanitize (lambda (var)
+                       (my/sanitize var "opensmtpd-action-local-delivery-configuration" "virtual"
+                                    (list false? opensmtpd-table-configuration?)))))
+  (wrapper opensmtpd-action-local-delivery-configuration-wrapper
+           (default #f)
+           (sanitize (lambda (var)
+                       (my/sanitize var "opensmtpd-action-local-delivery-configuration" "wrapper"
+                                    (list false? string?))))))
+
+;; FIXME/TODO this is a valid opensmtpd-relay record
+;; (opensmtpd-action-relay-configuration
+;;  (pki (opensmtpd-pki-configuration
+;;        (domain "gnucode.me")
+;;        (cert "opensmtpd.scm")
+;;        (key "opensmtpd.scm"))))
+;; BUT how does it relay the email?  What host does it use?
+;; I think opensmtpd-relay-configuration needs "method" field.
+;; the method field might need to be another record...BUT basically the relay has to have a 'backup', 'backup-mx',
+;; or 'domain', or 'host' defined.
+(define-record-type* <opensmtpd-action-relay-configuration>
+  opensmtpd-action-relay-configuration make-opensmtpd-action-relay-configuration
+  opensmtpd-action-relay-configuration?
+  (name opensmtpd-action-relay-configuration-name
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-action-relay-configuration" "name"
+                                 (list string?))))
+        (default #f))
+  (backup opensmtpd-action-relay-configuration-backup ;; boolean
+          (default #f)
+          (sanitize (lambda (var)
+                      (my/sanitize var "opensmtpd-action-relay-configuration" "backup"
+                                   (list boolean?)))))
+  (backup-mx opensmtpd-action-relay-configuration-backup-mx ;; string mx name
+             (default #f)
+             (sanitize (lambda (var)
+                         (my/sanitize var "opensmtpd-action-relay-configuration" "backup-mx"
+                                      (list false? string?)))))
+  (helo opensmtpd-action-relay-configuration-helo
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-action-relay-configuration" "helo"
+                                 (list false? string? opensmtpd-table-configuration?))))
+        (default #f))
+  (helo-src opensmtpd-action-relay-configuration-helo-src
+        (sanitize (lambda (var)
+                      (my/sanitize var "opensmtpd-action-relay-configuration" "helo-src"
+                                   (list false? string? opensmtpd-table-configuration?))))
+        (default #f))
+  (domain opensmtpd-action-relay-configuration-domain
+          (sanitize (lambda (var)
+                      (my/sanitize var "opensmtpd-action-relay-configuration" "domain"
+                                   (list false? opensmtpd-table-configuration?))))
+          (default #f))
+  (host opensmtpd-action-relay-configuration-host
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-action-relay-configuration" "host"
+                                 (list false? string?))))
+        (default #f))
+  (pki opensmtpd-action-relay-configuration-pki
+       (default #f)
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-action-relay-configuration" "pki"
+                                (list false? opensmtpd-pki-configuration?)))))
+  (srs opensmtpd-action-relay-configuration-srs
+       (default #f)
+       (lambda (var)
+         (my/sanitize var "opensmtpd-action-relay-configuration" "srs"
+                      (list boolean?))))
+  (tls opensmtpd-action-relay-configuration-tls
+       (default #f)
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-action-relay-configuration" "tls"
+                                (list false? string?)))))
+  (auth opensmtpd-action-relay-configuration-auth
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-action-relay-configuration" "auth"
+                                 (list false? opensmtpd-table-configuration?))))
+        (default #f))
+  (mail-from opensmtpd-action-relay-configuration-mail-from
+             (default #f))
+  ;; string "127.0.0.1" or "<interface>" or "<table of IP addresses>"
+  ;; TODO should I do some sanitizing to make sure that the string? here is actually an IP address or a valid interface?
+  (src opensmtpd-action-relay-configuration-src
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-action-relay-configuration" "src"
+                                (list false? string? opensmtpd-table-configuration?))))
+       (default #f)))
+
+;; this record is used by <opensmtpd-filter-phase-configuration> &
+;; <opensmtpd-match-configuration>
+(define-record-type* <opensmtpd-option-configuration>
+  opensmtpd-option-configuration make-opensmtpd-option-configuration
+  opensmtpd-option-configuration?
+  (option opensmtpd-option-configuration-option
+          (default #f)
+          (sanitize (lambda (var)
+                      (if (and (string? var)
+                               (or (string-in-list? var (list "fcrdns" "rdns"
+                                                          "src" "helo"
+                                                          "auth" "mail-from"
+                                                          "rcpt-to"
+                                                          "for"
+                                                          "for any" "for local"
+                                                          "for domain" "for rcpt-to"
+                                                          "from any" "from auth"
+                                                          "from local" "from mail-from"
+                                                          "from rdns" "from socket"
+                                                          "from src" "auth"
+                                                          "helo" "mail-from"
+                                                          "rcpt-to" "tag" "tls"
+                                                          ))))
+                          var
+                          (begin
+                            (display (string-append "<opensmtpd-option-configuration> fieldname: 'option' is of type \n"
+                                                    "string.  The string can be either 'fcrdns', \n"
+                                                    " 'rdns', 'src', 'helo', 'auth', 'mail-from', or 'rcpt-to', \n"
+                                                    "'for', 'for any', 'for local', 'for domain', 'for rcpt-to', \n"
+                                                    "'from any', 'from auth', 'from local', 'from mail-from', 'from rdns', 'from socket', \n"
+                                                    "'from src', 'auth helo', 'mail-from', 'rcpt-to', 'tag', or 'tls' \n"
+                                                    ))
+                            (throw 'bad! var))))))
+  (not opensmtpd-option-configuration-not
+       (default #f)
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-option-configuration" "not"
+                                (list boolean?)))))
+  (regex opensmtpd-option-configuration-regex
+         (default #f)
+         (sanitize (lambda (var)
+                     (my/sanitize var "opensmtpd-option-configuration" "regex"
+                                  (list boolean?)))))
+  (data opensmtpd-option-configuration-data
+         (default #f)
+         (sanitize (lambda (var)
+                     (my/sanitize var "opensmtpd-option-configuration" "data"
+                                  (list false? string? opensmtpd-table-configuration?))))))
+
+(define-record-type* <opensmtpd-filter-phase-configuration>
+  opensmtpd-filter-phase-configuration make-opensmtpd-filter-phase-configuration
+  opensmtpd-filter-phase-configuration?
+  (name opensmtpd-filter-phase-configuration-name ;; string chain-name
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-filter-phase-configuration" "name"
+                                 (list string?)))))
+  (phase opensmtpd-filter-phase-configuration-phase ;; string
+              (default #f)
+              (sanitize (lambda (var)
+                          ;;(my/sanitize var "opensmtpd-filter-phase-configuration" "phase"
+                          ;;             (list (sanitize-configuration
+                          ;;                    (proc (lambda (value)
+                          ;;                            (and (string? var)
+                          ;;                                 (string-in-list? var (list "connect"
+                          ;;                                                            "helo"
+                          ;;                                                            "mail-from"
+                          ;;                                                            "rcpt-to"
+                          ;;                                                            "data"
+                          ;;                                                            "commit")))))
+                          ;;                    (error-message (list
+                          ;;                                    "<opensmtpd-filter-phase-configuration> fieldname: 'phase' is of type \n"
+                          ;;                                    "string.  The string can be either 'connect',"
+                          ;;                                    " 'helo', 'mail-from', 'rcpt-to', 'data', or 'commit.'\n ")))))
+                          (if (and (string? var)
+                                   (string-in-list? var (list "connect"
+                                                          "helo"
+                                                          "mail-from"
+                                                          "rcpt-to"
+                                                          "data"
+                                                          "commit")))
+                              var
+                              (begin
+                                (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'phase' is of type \n"
+                                                        "string.  The string can be either 'connect',"
+                                                        " 'helo', 'mail-from', 'rcpt-to', 'data', or 'commit.'\n "
+                                                        ))
+                                (throw 'bad! var)))
+                          )))
+
+  (options opensmtpd-filter-phase-configuration-options
+              (default #f)
+              (sanitize (lambda (var)
+                          ;; returns #t if list is a unique list of <opensmtpd-option-configuration>
+                          (define (list-of-opensmtpd-option-configuration? list)
+                            (and (list-of-type? list opensmtpd-option-configuration?)
+                                 (not (contains-duplicate? list))))
+
+                          (define (list-has-duplicates-or-non-opensmtpd-option-configuration list)
+                            (not (list-of-opensmtpd-option-configuration? list)))
+
+                          ;; input <opensmtpd-option-configuration>
+                          ;; return #t if <opensmtpd-option-configuration> fieldname 'option'
+                          ;; that needs a corresponding table has one.  Otherwise #f
+                          (define (opensmtpd-option-configuration-has-table? record)
+                            (define decision (opensmtpd-option-configuration-option record))
+                            (and (string? decision)
+                                 ;; if option needs a table, check for a table
+                                 (if (string-in-list? decision (list "src"
+                                                                     "helo"
+                                                                     "mail-from"
+                                                                     "rcpt-to"))
+                                     (opensmtpd-table-configuration? (opensmtpd-option-configuration-data record))
+                                     #t)))
+
+                          (define (list-of-opensmtpd-option-configuration-has-table? list)
+                            (list-of-type? list opensmtpd-option-configuration-has-table?))
+
+                          (define (some-opensmtpd-option-configuration-in-list-lack-table? list)
+                            (not (list-of-opensmtpd-option-configuration-has-table? list)))
+
+                          ;;each element in list is of type <opensmtpd-option-configuration>
+                          (cond [(list-has-duplicates-or-non-opensmtpd-option-configuration var)
+                                 (begin
+                                   (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'options' is a list of unique \n"
+                                                           "<opensmtpd-option-configuration> records.\n"))
+                                   (throw 'bad! var))]
+                                ;; if fieldname 'option' is of string 'src', 'helo', 'mail-from', 'rcpt-to', then there should be a table
+                                [(some-opensmtpd-option-configuration-in-list-lack-table? var)
+                                 (begin
+                                   (display (string-append "<opensmtpd-option-configuration>'s fieldname 'option' values of \n"
+                                                           "'src', 'helo', 'mail-from', or 'rcpt-to' need a corresponding 'table' \n"
+                                                           " of type <opensmtpd-table-configuration>. eg: \n"
+                                                           "(opensmtpd-option-configuration \n"
+                                                           "   (option \"src\")\n"
+                                                           "   (table (opensmtpd-table-configuration \n"
+                                                           "              (name \"src-table\")\n"
+                                                           "              (data (list \"hello\" \"cat\")))))\n"))
+                                   ;; TODO it would be nice if the var this error message throws in the bad
+                                   ;; <opensmtpd-option-configuration>, instead of the list of records.
+                                   (throw 'bad! var))]
+                                [else var]))))
+  (decision opensmtpd-filter-phase-configuration-decision
+            (default #f)
+            (sanitize (lambda (var)
+                        (if (and (string? var)
+                                 (string-in-list? var (list "bypass" "disconnect"
+                                                            "reject" "rewrite" "junk")))
+                            var
+                            (begin
+                              (display (string-append "<opensmtpd-filter-decision> fieldname: 'decision' is of type \n"
+                                                      "string.  The string can be either 'bypass',"
+                                                      " 'disconnect', 'reject', 'rewrite', or 'junk'.\n"))
+                              (throw 'bad! var))))))
+  (message opensmtpd-filter-phase-configuration-message
+           (default #f)
+           (sanitize (lambda (var)
+                       (my/sanitize var "opensmtpd-filter-phase-configuration" "message"
+                                    (list false? string?)))))
+  (value opensmtpd-filter-phase-configuration-value
+           (default #f)
+           (sanitize (lambda (var)
+                       (my/sanitize var "opensmtpd-filter-phase-configuration" "value"
+                                    (list false? number?))))))
+
+(define-record-type* <opensmtpd-filter-configuration>
+  opensmtpd-filter-configuration make-opensmtpd-filter-configuration
+  opensmtpd-filter-configuration?
+  (name opensmtpd-filter-configuration-name
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-filter" "name"
+                                 (list string?)))))
+  (exec opensmtpd-filter-exec
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-filter" "exec"
+                                 (list boolean?)))))
+  (proc opensmtpd-filter-configuration-proc ; a string like "rspamd" or the command to start it like "/path/to/rspamd --option=arg --2nd-option=arg2"
+             (default #f)
+             (sanitize (lambda (var)
+                         (my/sanitize var "opensmtpd-filter" "proc"
+                                      (list string?))))))
+
+;; There is another type of filter that opensmtpd supports, which is a filter chain.
+;; A filter chain is a list of <opensmtpd-filter-phase-configuration> and <opensmtpd-filter-configuration>.
+;; This lets you apply several filters under one filter name.  I could have defined
+;; a record type for it, but the record would only have had two fields: name and list-of-filters.
+;; Why write that as a record?  That's too simple.
+;; returns #t if list is a unique list of <opensmtpd-filter-configuration> or <opensmtpd-filter-phase-configuration>
+;; returns # otherwise
+(define (opensmtpd-filter-chain? %filters)
+  (and (list-of-unique-filter-or-filter-phase? %filters)
+       (< 1 (length %filters))))
+
+(define-record-type* <opensmtpd-listen-on-configuration>
+  opensmtpd-listen-on-configuration make-opensmtpd-listen-on-configuration
+  opensmtpd-listen-on-configuration?
+  ;; interface may be an IP address, interface group, or domain name
+  (interface opensmtpd-listen-on-configuration-interface
+             (default "lo"))
+  (family opensmtpd-listen-on-configuration-family
+          (default #f)
+          (sanitize (lambda (var)
+                      (cond
+                       [(eq? #f var) ;; var == #f
+                        var]
+                       [(and (string? var)
+                             (string-in-list? var (list "inet4" "inet6")))
+                        var]
+                       [else
+                        (begin
+                          (display "<opensmtpd-listen-on-configuration> fieldname 'family' must be string \"inet4\" or \"inet6\".\n")
+                          (throw 'bad! var))]))))
+  (auth opensmtpd-listen-on-configuration-auth
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-listen-on-configuration" "auth"
+                                 (list boolean? table-whose-data-are-assoc-list?)))))
+  (auth-optional opensmtpd-listen-on-configuration-auth-optional
+                 (default #f)
+                 (sanitize (lambda (var)
+                             (my/sanitize var "opensmtpd-listen-on-configuration" "auth-optional"
+                                          (list boolean?
+                                                table-whose-data-are-assoc-list?)))))
+  ;; TODO add a ca entry?
+  ;; string FIXME/TODO sanitize this to support a gexp.  That way way the
+  ;; includes directive can include my hacky scheme code that I use for opensmtpd-dkimsign.
+  (filters opensmtpd-listen-on-configuration-filters
+           (default #f)
+           (sanitize (lambda (var)
+                       (sanitize-filters var))))
+  (hostname opensmtpd-listen-on-configuration-hostname
+            (default #f)
+            (sanitize (lambda (var)
+                        (my/sanitize var "opensmtpd-listen-on-configuration" "hostname"
+                                     (list false? string?)))))
+  (hostnames opensmtpd-listen-on-configuration-hostnames
+             (default #f)
+             (sanitize (lambda (var)
+                         (my/sanitize var "opensmtpd-listen-on-configuration" "hostnames"
+                                      (list false? table-whose-data-are-assoc-list?)))))
+  (mask-src opensmtpd-listen-on-configuration-mask-src
+            (default #f)
+            (sanitize (lambda (var)
+                        (my/sanitize var "opensmtpd-listen-on-configuration" "mask-src"
+                                     (list boolean?)))))
+  (disable-dsn opensmtpd-listen-on-configuration-disable-dsn
+          (default #f))
+  (pki opensmtpd-listen-on-configuration-pki
+       (default #f)
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-listen-on-configuration" "pki"
+                                (list false? opensmtpd-pki-configuration?)))))
+  (port opensmtpd-listen-on-configuration-port
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-listen-on-configuration" "port"
+                                 (list false? integer?)))))
+  (proxy-v2 opensmtpd-listen-on-configuration-proxy-k2
+            (default #f))
+  (received-auth opensmtpd-listen-on-configuration-received-auth
+                 (default #f))
+  ;; TODO add in a senders option!
+  ;; string or <opensmtpd-senders> record
+  ;; (senders opensmtpd-listen-on-configuration-senders
+  ;;          (sanitize (lambda (var)
+  ;;                      (my/sanitize var "opensmtpd-listen-on-configuration" "port" (list false? integer?))))
+  ;;          (default #f))
+  (secure-connection opensmtpd-listen-on-configuration-secure-connection
+                     (default #f)
+                     (sanitize (lambda (var)
+                                 (cond [(boolean? var)
+                                        var]
+                                       [(and (string? var)
+                                             (string-in-list? var
+                                                              (list "smtps" "tls"
+                                                                    "tls-require"
+                                                                    "tls-require-verify")))
+                                        var]
+                                       [else
+                                        (begin
+                                          (display (string-append "<opensmtd-listen-on> fieldname 'secure-connection' can be \n"
+                                                                  "one of the following strings: \n'smtps', 'tls', 'tls-require', \n"
+                                                                  "or 'tls-require-verify'.\n"))
+                                          (throw 'bad! var))]))))
+  (tag opensmtpd-listen-on-configuration-tag
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-listen-on-configuration" "tag"
+                                (list false? string?))))
+       (default #f)))
+
+(define-record-type* <opensmtpd-listen-on-socket-configuration-configuration>
+  opensmtpd-listen-on-socket-configuration-configuration make-opensmtpd-listen-on-socket-configuration-configuration
+  opensmtpd-listen-on-socket-configuration-configuration?
+  ;; false or <opensmtpd-filter-configuration> or list of <opensmtpd-filter-configuration>
+  (filters opensmtpd-listen-on-socket-configuration-configuration-filters
+           (sanitize (lambda (var)
+                       (sanitize-filters var)))
+          (default #f))
+  (mask-src opensmtpd-listen-on-socket-configuration-configuration-mask-src
+            (default #f))
+  (tag opensmtpd-listen-on-socket-configuration-configuration-tag
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-listen-on-configuration" "tag"
+                                (list false? string?))))
+       (default #f)))
+
+
+(define-record-type* <opensmtpd-match-configuration>
+  opensmtpd-match-configuration make-opensmtpd-match-configuration
+  opensmtpd-match-configuration?
+  ;;TODO? Perhaps I should add in a reject fieldname.  If reject
+  ;;is #t, then the match record will be a reject match record.
+  ;; (opensmtpd-match (reject #t)) vs. (opensmtpd-match (action 'reject))
+  ;; To do this, I will also have to  'reject' mutually exclusive. AND an match with 'reject' can have no action defined.
+  (action opensmtpd-match-configuration-action
+          (default #f)
+          (sanitize (lambda (var)
+                      (if (or (opensmtpd-action-relay-configuration? var)
+                              (opensmtpd-action-local-delivery-configuration? var)
+                              (eq? (quote reject) var))
+                          var
+                          (begin
+                            (display
+                             (string-append "<opensmtpd-match-configuration> fieldname 'action' is of type <opensmtpd-action-relay-configuration>, \n"
+                                            "<opensmtpd-action-local-delivery-configuration>, or (quote reject).\n"
+                                            "If its var is (quote reject), then the match rejects the incoming message\n"
+                                            "during the SMTP dialogue.\n"))
+                            (throw 'bad! var))))))
+  (options opensmtpd-match-configuration-options
+           (default #f)
+           (sanitize (lambda (var)
+                       (cond ((not var)
+                              #f)
+                             ((not (list-of-unique-opensmtpd-option-configuration? var))
+                              (throw-error var '("<opensmtpd-match-configuration> fieldname 'options' is a list of unique \n"
+                                                 "<opensmtpd-option-configuration> records. \n")))
+                           (else (sanitize-list-of-options-for-match-configuration var)))))))
+
+(define-record-type* <opensmtpd-smtp-configuration>
+  opensmtpd-smtp-configuration make-opensmtpd-smtp-configuration
+  opensmtpd-smtp-configuration?
+  (ciphers opensmtpd-smtp-configuration-ciphers
+           (default #f)
+           (sanitize (lambda (var)
+                       (my/sanitize var "opensmtpd-smtp-configuration" "ciphers"
+                                    (list false? string?)))))
+  (limit-max-mails opensmtpd-smtp-configuration-limit-max-mails
+                   (default #f)
+                   (sanitize (lambda (var)
+                               (my/sanitize var "opensmtpd-smtp-configuration" "limit-max-mails"
+                                            (list false? integer?)))))
+  (limit-max-rcpt opensmtpd-smtp-configuration-limit-max-rcpt
+                  (default #f)
+                  (sanitize (lambda (var)
+                              (my/sanitize var "opensmtpd-smtp-configuration" "limit-max-rcpt"
+                                           (list false? integer?)))))
+  (max-message-size opensmtpd-smtp-configuration-max-message-size
+                    (default #f)
+                    (sanitize (lambda (var)
+                                (my/sanitize var "opensmtpd-smtp-configuration" "max-message-size"
+                                             (list false? integer? string?)))))
+  ;; FIXME/TODO the sanitize function of sub-addr-delim should accept a string of length one not string?
+  (sub-addr-delim opensmtpd-smtp-configuration-sub-addr-delim
+                  (default #f)
+                  (sanitize (lambda (var)
+                              (my/sanitize var "opensmtpd-smtp-configuration" "sub-addr-delim"
+                                           (list false? integer? string?))))))
+
+(define-record-type* <opensmtpd-srs-configuration>
+  opensmtpd-srs-configuration make-opensmtpd-srs-configuration
+  opensmtpd-srs-configuration?
+  ;; TODO should this be a file?
+  (key opensmtpd-srs-configuration-key
+       (default #f)
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-srs-configuration" "key"
+                                (list false? boolean? string?)))))
+  ;; TODO should this also be a file?
+  (backup-key opensmtpd-srs-configuration-backup-key
+              (default #f)
+              (sanitize (lambda (var)
+                          (my/sanitize var "opensmtpd-srs-configuration" "backup-key"
+                                       (list false? integer?)))))
+  (ttl-delay opensmtpd-srs-configuration-ttl-delay
+             (default #f)
+             (sanitize (lambda (var)
+                         (my/sanitize var "opensmtpd-srs-configuration" "ttl-delay"
+                                      (list false? string?))))))
+
+(define-record-type* <opensmtpd-queue-configuration>
+  opensmtpd-queue-configuration make-opensmtpd-queue-configuration
+  opensmtpd-queue-configuration?
+  (compression opensmtpd-queue-configuration-compression
+               (default #f)
+               (sanitize (lambda (var)
+                           (my/sanitize var "opensmtpd-queue-configuration" "compression"
+                                        (list boolean?)))))
+  (encryption opensmtpd-queue-configuration-encryption
+              (default #f)
+              (sanitize (lambda (var)
+                          (my/sanitize var "opensmtpd-queue-configuration" "encryption"
+                                       (list boolean? file-exists? string?)))))
+  (ttl-delay opensmtpd-queue-configuration-ttl-delay
+             (default #f)
+             (sanitize (lambda (var)
+                         (my/sanitize var "opensmtpd-queue-configuration" "ttl-delay"
+                                      (list false? string?))))))
+
 (define-record-type* <opensmtpd-configuration>
   opensmtpd-configuration make-opensmtpd-configuration
   opensmtpd-configuration?
-  (package     opensmtpd-configuration-package
-               (default opensmtpd))
+  (package opensmtpd-configuration-package
+           (default opensmtpd))
   (config-file opensmtpd-configuration-config-file
-               (default %default-opensmtpd-config-file)))
+               (default #f))
+  ;; FIXME/TODO should I include a admd authservid entry?
+
+  ;; TODO sanitize this properly with perhaps a <sanitize-configuration>.
+  (bounce opensmtpd-configuration-bounce
+          (default #f)
+          (sanitize (lambda (var)
+                      (my/sanitize var "opensmtpd-configuration" "bounce"
+                                   (list false? list?)))))
+  (cas opensmtpd-configuration-cas
+       (default #f)
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-configuration" "cas"
+                                (list false? list-of-opensmtpd-ca-configuration?)))))
+  ;; list of many records of type opensmtpd-listen-on-configuration
+  (listen-ons opensmtpd-configuration-listen-ons
+              (default (list (opensmtpd-listen-on-configuration)))
+              (sanitize (lambda (var)
+                          (if (list-of-opensmtpd-listen-on-configuration? var)
+                              var
+                              (begin
+                                (display "<opensmtpd-configuration> fieldname 'listen-ons' expects a list of records ")
+                                (display "of one or more unique <opensmtpd-listen-on-configuration> records.\n")
+                                (throw 'bad! var))))))
+  ;; accepts type <opensmtpd-listen-on-socket-configuration-configuration>
+  (listen-on-socket opensmtpd-configuration-listen-on-socket
+                    (default (opensmtpd-listen-on-socket-configuration-configuration)))
+  (includes opensmtpd-configuration-includes ;; list of strings of absolute path names
+            (default #f)
+            (sanitize (lambda (var)
+                        (my/sanitize var "opensmtpd-configuration" "includes"
+                                     (list false? list-of-strings?)))))
+  (matches opensmtpd-configuration-matches
+           (default (list (opensmtpd-match-configuration
+                           (action (opensmtpd-action-local-delivery-configuration
+                                    (name "local")
+                                    (method "mbox")))
+                           (options (list
+                                     (opensmtpd-option-configuration
+                                      (option "for local")))))
+                          (opensmtpd-match-configuration
+                           (action (opensmtpd-action-relay-configuration
+                                    (name "outbound")))
+                           (options (list
+                                     (opensmtpd-option-configuration
+                                      (option "from local"))
+                                     (opensmtpd-option-configuration
+                                      (option "for any")))))))
+           ;; TODO perhaps I should sanitize this function like I sanitized the 'filters'.
+           ;; I definitely should sanitize this function a bit more.  For example, you could have two different
+           ;; actions, one for local delivery and one for remote, with the same name.  I should make sure that
+           ;; I have no two different actions with the same name.
+           (sanitize (lambda (var)
+                       ;; Should we do more sanitizing here?  eg: "from socket" should NOT have a table or value
+                       var
+                       (my/sanitize var "opensmtpd-configuration" "matches"
+                                    (list list-of-unique-opensmtpd-match-configuration?)))))
+  ;; list of many records of type mda-wrapper
+  ;; TODO/FIXME support using gexps here
+  ;; eg (list "name" gexp)
+  (mda-wrappers opensmtpd-configuration-mda-wrappers
+                (default #f)
+                (sanitize (lambda (var)
+                            (my/sanitize var
+                                         "opensmtpd-configuration"
+                                         "mda-wrappers"
+                                         (list false? string?)))))
+  (mta-max-deferred opensmtpd-configuration-mta-max-deferred
+                    (default 100)
+                    (sanitize (lambda (var)
+                                (my/sanitize var "opensmtpd-configuration" "mta-max-deferred"
+                                             (list number?)))))
+
+  ;; TODO should I add a fieldname proc _proc-name_ _command_ as found in the man 5 smtpd.conf ?
+
+  (queue opensmtpd-configuration-queue
+         (default #f)
+         (sanitize (lambda (var)
+                     (my/sanitize var "opensmtpd-configuration" "queue"
+                                  (list false? opensmtpd-queue-configuration?)))))
+  (smtp opensmtpd-configuration-smtp
+        (default #f)
+        (sanitize (lambda (var)
+                    (my/sanitize var "opensmtpd-configuration" "smtp"
+                                 (list false? opensmtpd-smtp-configuration?)))))
+  (srs opensmtpd-configuration-srs
+       (default #f)
+       (sanitize (lambda (var)
+                   (my/sanitize var "opensmtpd-configuration" "srs"
+                                (list false? opensmtpd-srs-configuration?))))))
+
+;; This is a non-exported record for passing around sanitize procedures.
+;; As of 5/2/2022 I am not using it.  I should probably just delete it.
+(define-record-type* <sanitize-configuration>
+  sanitize-configuration make-sanitize-configuration
+  sanitize-configuration?
+  (proc sanitize-configuration-proc
+          (default #f)
+          ;;(sanitize (lambda (var) (procedure? var)))
+          )
+  (args sanitize-configuration-args
+       (default #f)
+       ;;(sanitize (lambda (var) (lambda (var) (list? var))))
+       )
+  (error-message sanitize-configuration-error-message
+         (default #f)
+         ;;(sanitize (lambda (var) (list? var)))
+         )
+  (error-if-proc-fails sanitize-configuration-error-if-proc-fails
+                       (default #f)))
+
+;; this help procedure is used 3 or 4 times by sanitize-list-of-options-for-match-configuration
+(define (throw-error-duplicate-option option error-arg)
+  (throw-error error-arg
+               (list "<opensmtpd-match-configuration>'s fieldname 'options' has two\n"
+                     (string-append "<opensmtpd-option-configuration> records with fieldname 'option' with value '" option "'. \n")
+                     (string-append "You can only have one option with value '" option "' in the options list.\n"))))
+
+;; this procedure sanitizes the fieldname opensmtpd-match-configuration-options
+(define* (sanitize-list-of-options-for-match-configuration %options)
+  (let loop ([%traversing-options %options]
+             [%sanitized-options '()])
+    (if (null? %traversing-options)
+           (remove false?
+                   (list
+                    (assoc-ref %sanitized-options "for")
+                    (assoc-ref %sanitized-options "from")
+                    (assoc-ref %sanitized-options "auth")
+                    (assoc-ref %sanitized-options "helo")
+                    (assoc-ref %sanitized-options "mail-from")
+                    (assoc-ref %sanitized-options "rcpt-to")
+                    (assoc-ref %sanitized-options "tag")
+                    (assoc-ref %sanitized-options "tls")))
+          (let* ((option-record (car %traversing-options))
+                 (option-string (opensmtpd-option-configuration-option option-record)))
+            (cond [(string=? "auth" option-string)
+                   (if (assoc-ref %sanitized-options "auth")
+                       (throw-error-duplicate-option "auth" %traversing-options)
+                       (loop (cdr %traversing-options) (alist-cons "auth" option-record %sanitized-options)))]
+                  [(string=? "helo" option-string)
+                   (cond [(assoc-ref %sanitized-options "helo")
+                          (throw-error-duplicate-option "helo" %traversing-options)]
+                         [(not (opensmtpd-option-configuration-data option-record))
+                          (throw-error option-record
+                                       (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'helo' \n"
+                                             "must have a 'data' of type string or <opensmtpd-table-configuration>.\n"))]
+                         [else (loop (cdr %traversing-options) (alist-cons "helo" option-record %sanitized-options))])]
+                  [(string=? "mail-from" option-string)
+                   (cond ((assoc-ref %sanitized-options "mail-from")
+                          (throw-error-duplicate-option "mail-from" %traversing-options))
+                         ((not (opensmtpd-option-configuration-data option-record))
+                          (throw-error option-record
+                                       (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'mail-from' \n"
+                                             "must have a 'data' of type string or <opensmtpd-table-configuration>.\n")))
+                         (else (loop (cdr %traversing-options) (alist-cons "mail-from" option-record %sanitized-options))))]
+                  [(string=? "rcpt-to" option-string)
+                   (cond [(assoc-ref %sanitized-options "rcpt-to")
+                          (throw-error-duplicate-option "rcpt-to" %traversing-options)]
+                         [(not (opensmtpd-option-configuration-data option-record))
+                          (throw-error option-record
+                                       (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'rcpt-to' \n"
+                                             "must have a 'data' of type string or <opensmtpd-table-configuration>.\n"))]
+                         [else (loop (cdr %traversing-options) (alist-cons "rcpt-to" option-record %sanitized-options))])]
+                  [(string=? "tag" option-string)
+                   (cond ((assoc-ref %sanitized-options "tag")
+                          (throw-error-duplicate-option "tag" %traversing-options))
+                         ((not (string? (opensmtpd-option-configuration-data option-record)))
+                          (throw-error option-record
+                                       (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'tag' \n"
+                                             "must have a 'data' of type string.\n")))
+                         (else (loop (cdr %traversing-options) (alist-cons "tag" option-record %sanitized-options))))]
+                  [(string=? "tls" option-string)
+                   (cond [(assoc-ref %sanitized-options "tls")
+                          (throw-error-duplicate-option "tls" %traversing-options)]
+                         [(or (opensmtpd-option-configuration-data option-record)
+                              (opensmtpd-option-configuration-regex option-record))
+                          (throw-error option-record
+                                       (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'tls' \n"
+                                             "cannot have a string or table 'data'.\n"))]
+                         [else (loop (cdr %traversing-options) (alist-cons "tls" option-record %sanitized-options))])]
+                  [(string=? "for" (substring option-string 0 3))
+                   (cond ((assoc-ref %sanitized-options "for")
+                          (throw-error %options
+                                       `("<opensmtpd-match-configuration>'s fieldname 'options' can only have one 'for' option. \n"
+                                         "But '" ,option-string "' and '"
+                                         ,(opensmtpd-option-configuration-option (assoc-ref %sanitized-options "for")) "' are present.\n")))
+                         ((and (string-in-list? option-string (list "for any" "for local")) ; for any cannot have a data field.
+                               (or (opensmtpd-option-configuration-data option-record)
+                                   (opensmtpd-option-configuration-regex option-record)))
+                          (throw-error option-record
+                                       (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'for any' \n"
+                                             "or 'for local', then its 'data' and 'regex' field must be #f. \n")))
+                         ((and (string-in-list? option-string (list "for domain" "for rcpt-to")) ; for domain must have a data field.
+                               (not (opensmtpd-option-configuration-data option-record)))
+                          (throw-error option-record
+                                       (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'for domain' \n"
+                                             "or 'for rcpt-to', then its 'data' field must be a string or an \n"
+                                             "<opensmtpd-table-configuration> record.\n")))
+                         (else (loop (cdr %traversing-options) (alist-cons "for" option-record %sanitized-options))))]
+                  [(string=? "from" (substring option-string 0 4))
+                   (cond ((assoc-ref %sanitized-options "from")
+                          (throw-error %options
+                                       `("<opensmtpd-match-configuration>'s fieldname 'options' can only have one 'from' option. \n"
+                                         "But '" ,option-string "' and '"
+                                         ,(opensmtpd-option-configuration-option (assoc-ref %sanitized-options "from")) "' are present.\n")))
+                         ((and (string-in-list? option-string (list "from any" "from local" "from socket")) ; for any cannot have a data field.
+                               (or (opensmtpd-option-configuration-data option-record)
+                                   (opensmtpd-option-configuration-regex option-record)))
+                          (throw-error option-record
+                                       (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'from any', \n"
+                                             " 'from local', or 'from socket', then its 'data' and 'regex' field must be #f. \n")))
+                         ((and (string-in-list? option-string (list "from mail-from" "from src")) ; for domain must have a data field.
+                               (not (opensmtpd-option-configuration-data option-record)))
+                          (throw-error option-record
+                                       (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'from mail-from' \n"
+                                             "or 'from src', then its 'data' field must be a string or an \n"
+                                             "<opensmtpd-table-configuration> record.\n")))
+                         (else (loop (cdr %traversing-options) (alist-cons "from" option-record %sanitized-options))))])))))
+
+;; some procedures for <opensmtpd-listen-on-configuration> and
+;; <opensmtpd-listen-on-socket-configuration-configuration>.
+(define (sanitize-filters %list)
+  ;; the order of the first two tests in this cond is important.
+  ;; (false?) has to be 1st and (list-has-duplicates-or-non-filters?) has to be second.
+  ;; You may optionally re-order the other alternates in the cond.
+  (cond [(false? %list)
+         #f]
+        [(list-has-duplicates-or-non-filters? %list)
+         (begin
+           (display (string-append "<opensmtpd-listen-on-configuration> fieldname: 'filters' is a list, in which each unique element \n"
+                                   "is of type <opensmtpd-filter-configuration> or <opensmtpd-filter-phase-configuration>.\n"))
+           (throw 'bad! %list))]
+        [else
+         (let loop ([%traversing-list %list]
+                    [%original-list %list])
+           (if (null? %traversing-list)
+               %original-list
+               (cond
+                [(opensmtpd-filter-configuration? (car %traversing-list))
+                 (loop (cdr %traversing-list) %original-list)]
+                [(filter-phase-has-message-and-value? (car %traversing-list))
+                 (begin
+                   (display (string-append "<opensmtpd-filter-phase-configuration> cannot have defined fieldnames 'value' \n"
+                                           "and 'message'.\n"))
+                   (throw 'bad! (car %traversing-list)))]
+                [(filter-phase-decision-lacks-proper-message? (car %traversing-list))
+                 (begin
+                   (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'decision' options \n"
+                                           "\"disconnect\" and \"reject\" require fieldname 'message' to have a string.\n"
+                                           "The 'message' string must be RFC commpliant, which means that the string \n"
+                                           "must begin with a 4xx or 5xx status code.\n"))
+                   (throw 'bad! (car %traversing-list)))]
+                [(filter-phase-lacks-proper-value? (car %traversing-list))
+                 (begin
+                   (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'decision' option \n"
+                                           "\"rewrite\" requires fieldname 'value' to have a number.\n"))
+                   (throw 'bad! (car %traversing-list)))]
+                [(filter-phase-has-incorrect-junk-or-bypass? (car %traversing-list))
+                 (begin
+                   (display (string-append "<opensmtpd-filter-phase-configuration> fieldname 'decision' option \n"
+                                           "\"junk\" or 'bypass' cannot have a defined fieldnames 'message' or 'value'.\n"))
+                   (throw 'bad! (car %traversing-list)))]
+                [(filter-phase-junks-after-commit? (car %traversing-list))
+                 (begin
+                   (display (string-append "<opensmtpd-filter-phase-configuration> fieldname 'decision' option \n"
+                                           "\"junk\" cannot junk an email during 'phase' \"commit\".\n"))
+                   (throw 'bad! (car %traversing-list)))]
+                [else (loop (cdr %traversing-list) %original-list)])))]))
+
+(define (list-has-duplicates-or-non-filters? list)
+  (not (list-of-unique-filter-or-filter-phase? list)))
+
+(define (filter-phase-has-message-and-value? record)
+  (and (opensmtpd-filter-phase-configuration-message record)
+       (opensmtpd-filter-phase-configuration-value record)))
+
+;; return #t if phase needs a message. Or if the message did not start with a 4xx or 5xx status code.
+;; otherwise #f
+(define (filter-phase-decision-lacks-proper-message? record)
+  (define decision (opensmtpd-filter-phase-configuration-decision record))
+  (if (string-in-list? decision (list "disconnect" "reject"))
+      ;; this message needs to be RFC compliant, meaning
+      ;; that it need to start with 4xx or 5xx status code
+      (cond [(eq? #f (opensmtpd-filter-phase-configuration-message record))
+             #t]
+            [(string? (opensmtpd-filter-phase-configuration-message record))
+             (let ((number (string->number
+                            (substring
+                             (opensmtpd-filter-phase-configuration-message record) 0 3))))
+               (if (and (number? number)
+                        (and (< number 600) (> number 399)))
+                   #f
+                   #t))])
+      #f))
+
+;; 'decision' "rewrite" requires 'value' to be a number.
+(define (filter-phase-lacks-proper-value? record)
+  (define decision (opensmtpd-filter-phase-configuration-decision record))
+  (if (string=? "rewrite" decision)
+      (if (and (number? (opensmtpd-filter-phase-configuration-value record))
+               (eq? #f (opensmtpd-filter-phase-configuration-message record)))
+          #f
+          #t)
+      #f))
+
+;; 'decision' "junk" or "bypass" cannot have a message or a value.
+(define (filter-phase-has-incorrect-junk-or-bypass? record)
+  (and
+   (string-in-list?
+    (opensmtpd-filter-phase-configuration-decision record)
+    (list "junk" "bypass"))
+   (or
+    (opensmtpd-filter-phase-configuration-value record)
+    (opensmtpd-filter-phase-configuration-message record))))
+
+(define (filter-phase-junks-after-commit? record)
+  (and (string=? (opensmtpd-filter-phase-configuration-decision record) "junk")
+       (string=? (opensmtpd-filter-phase-configuration-phase record) "commit")))
+
+;; returns #t if list is a unique list of <opensmtpd-filter-configuration> or <opensmtpd-filter-phase-configuration>
+;; returns # otherwise
+(define (list-of-unique-filter-or-filter-phase? %filters)
+  (and (list? %filters)
+       (not (null? %filters))
+       ;; this list is made up of only <opensmtpd-filter-phase-configuration> or <opensmtpd-filter-configuration>
+       (primitive-eval
+        (cons 'and (map (lambda (filter)
+                          (or (opensmtpd-filter-configuration? filter)
+                              (opensmtpd-filter-phase-configuration? filter)))
+                        %filters)))
+       (not (contains-duplicate? %filters))))
+
+(define (throw-error var %strings)
+  (display (apply string-append %strings))
+  (throw 'bad! var))
+
+;; this is used for sanitizing <opensmtpd-filter-phase-configuration> fieldname 'options'
+(define (contains-duplicate? list)
+  (if (null? list)
+      #f
+      (or
+      ;; check if (car list) is in (cdr list)
+       (primitive-eval (cons 'or
+                                (map (lambda (var) (equal? var (car list)))
+                                     (cdr list))))
+       ;; check if (cdr list) contains duplicate
+       (contains-duplicate? (cdr list)))))
+
+;; given a list and procedure, this tests that each element of list is of type
+;; ie: (list-of-type? list string?) tests each list is of type string.
+(define (list-of-type? list proc?)
+  (if (and (list? list)
+           (not (null? list)))
+      (let loop ([list list])
+        (if (null? list)
+            #t
+            (if (proc? (car list))
+                (loop (cdr list))
+                #f)))
+      #f))
+
+(define (list-of-strings? list)
+  (list-of-type? list string?))
+
+(define (list-of-unique-opensmtpd-option-configuration? list)
+  (and (list-of-type?
+        list opensmtpd-option-configuration?)
+       (not (contains-duplicate? list))))
+
+(define (list-of-opensmtpd-ca-configuration? list)
+  (list-of-type? list opensmtpd-ca-configuration?))
+
+(define (list-of-opensmtpd-pki-configuration? list)
+  (list-of-type? list opensmtpd-pki-configuration?))
+
+(define (list-of-opensmtpd-listen-on-configuration? list)
+  (and (list-of-type? list opensmtpd-listen-on-configuration?)
+       (not (contains-duplicate? list))))
+
+(define (list-of-unique-opensmtpd-match-configuration? list)
+  (and (list-of-type? list opensmtpd-match-configuration?)
+       (not (contains-duplicate? list))))
+
+(define* (list-of-strings->string list
+                                  #:key
+                                  (string-delimiter ", ")
+                                  (postpend "")
+                                  (append "")
+                                  (drop-right-number 2))
+  (string-drop-right
+   (string-append (let loop ([list list])
+                    (if (null? list)
+                        ""
+                        (string-append append (car list) postpend
+                                       string-delimiter
+                                       (loop (cdr list)))))
+                  append)
+   drop-right-number))
+
+;; at the moment I cannot define this by using list-of-type?
+;; the first (not (null? assoc-list)) prevents that.
+(define (assoc-list? assoc-list)
+  (list-of-type? assoc-list (lambda (pair)
+                              (if (and (pair? pair)
+                                       (string? (car pair))
+                                       (string? (cdr pair)))
+                                  #t
+                                  #f))))
+
+(define* (variable->string var #:key (append "") (postpend " "))
+  (let ([var (if (number? var)
+                 (number->string var)
+                 var)])
+    (if var
+        (string-append append var postpend)
+        "")))
+
+;; this procedure takes in one argument.
+;; if that argument is an <opensmtpd-table-configuration> whose fieldname 'values' is an assoc-list, then it returns
+;; #t, #f if otherwise.
+;; TODO should I remove these two functions?  And instead use the (opensmtpd-table-configuration-type) procedure?
+(define (table-whose-data-are-assoc-list? table)
+  (if (not (opensmtpd-table-configuration? table))
+      #f
+      (assoc-list? (opensmtpd-table-configuration-data table))))
+
+;; this procedure takes in one argument
+;; if that argument is an <opensmtpd-table-configuration> whose fieldname 'values' is a list of strings, then it returns
+;; #t, #f if otherwise.
+(define (table-whose-data-are-a-list-of-strings? table)
+  (if (not (opensmtpd-table-configuration? table))
+      #f
+      (list-of-strings? (opensmtpd-table-configuration-data table))))
+
+;; these next few functions help me to turn <table>s
+;; into strings suitable to fit into "opensmtpd.conf".
+(define (assoc-list->string assoc-list)
+  (string-drop-right
+   (let loop ([assoc-list assoc-list])
+     (if (null? assoc-list)
+         ""
+         ;; pair is (cons "hello" "world") -> ("hello" . "world")
+         (let ([pair (car assoc-list)])
+           (string-append
+            "\"" (car pair)  "\""
+            " = "
+            "\"" (cdr pair) "\""
+            ", "
+            (loop (cdr assoc-list))))))
+   2))
+
+;; can be of type: (quote list-of-strings) or (quote assoc-list)
+(define (opensmtpd-table-configuration->string table)
+  (string-append "table " (opensmtpd-table-configuration-name table) " "
+                 (let ([type (opensmtpd-table-configuration-type table)])
+                   (cond [(eq? type (quote list-of-strings))
+                          (string-append "{ " (list-of-strings->string (opensmtpd-table-configuration-data table)
+                                                                       #:append "\""
+                                                                       #:drop-right-number 3
+                                                                       #:postpend "\"") " }")]
+                         [(eq? type (quote assoc-list))
+                          (string-append "{ " (assoc-list->string (opensmtpd-table-configuration-data table)) " }")]
+                         [(eq? type (quote db))
+                          (string-append "db:" (opensmtpd-table-configuration-data table))]
+                         [(eq? type (quote file))
+                          (string-append "file:" (opensmtpd-table-configuration-data table))]
+                         [else (throw 'youMessedUp table)]))
+                 " \n"))
+
+;;  The following functions convert various records into strings.
+
+(define (opensmtpd-listen-on-configuration->string record)
+  (string-append "listen on "
+                 (opensmtpd-listen-on-configuration-interface record) " "
+                 (let* ([hostname (opensmtpd-listen-on-configuration-hostname record)]
+                        [hostnames (if (opensmtpd-listen-on-configuration-hostnames record)
+                                       (opensmtpd-table-configuration-name (opensmtpd-listen-on-configuration-hostnames record))
+                                       #f)]
+                        [filters (opensmtpd-listen-on-configuration-filters record)]
+                        [filter-name (if filters
+                                         (if (< 1 (length filters))
+                                             (generate-filter-chain-name filters)
+                                             (if (opensmtpd-filter-configuration? (car filters))
+                                                 (opensmtpd-filter-configuration-name (car filters))
+                                                 (opensmtpd-filter-phase-configuration-name (car filters))))
+                                         #f)]
+                        [mask-src (opensmtpd-listen-on-configuration-mask-src record)]
+                        [tag (opensmtpd-listen-on-configuration-tag record)]
+                        [secure-connection (opensmtpd-listen-on-configuration-secure-connection record)]
+                        [port (opensmtpd-listen-on-configuration-port record)]
+                        [pki (opensmtpd-listen-on-configuration-pki record)]
+                        [auth (opensmtpd-listen-on-configuration-auth record)]
+                        [auth-optional (opensmtpd-listen-on-configuration-auth-optional record)])
+                   (string-append
+                    (if mask-src
+                        (string-append "mask-src ")
+                        "")
+                    (variable->string hostname #:append "hostname ")
+                    (variable->string hostnames #:append "hostnames <" #:postpend "> ")
+                    (variable->string filter-name #:append "filter \"" #:postpend "\" ")
+                    (variable->string tag #:append "tag \"" #:postpend "\" ")
+                    (if secure-connection
+                        (cond [(string=? "smtps" secure-connection)
+                               "smtps "]
+                              [(string=? "tls" secure-connection)
+                               "tls "]
+                              [(string=? "tls-require" secure-connection)
+                               "tls-require "]
+                              [(string=? "tls-require-verify" secure-connection)
+                               "tls-require verify "])
+                        "")
+                    (variable->string port #:append "port " #:postpend " ")
+                    (if pki
+                        (variable->string (opensmtpd-pki-configuration-domain pki) #:append "pki ")
+                        "")
+                    (if auth
+                        (string-append "auth "
+                                       (if (opensmtpd-table-configuration? auth)
+                                           (string-append "<" (opensmtpd-table-configuration-name auth) "> ")
+                                           ""))
+                        "")
+                    (if auth-optional
+                        (string-append "auth-optional "
+                                       (if (opensmtpd-table-configuration? auth-optional)
+                                           (string-append "<" (opensmtpd-table-configuration-name auth-optional) "> ")
+                                           ""))
+                        "")
+                    "\n"))))
+
+(define (opensmtpd-listen-on-socket-configuration->string record)
+  (string-append "listen on socket "
+                 (let* ([filters (opensmtpd-listen-on-socket-configuration-configuration-filters record)]
+                        [filter-name (if filters
+                                         (if (< 1 (length filters))
+                                             (generate-filter-chain-name filters)
+                                             (if (opensmtpd-filter-configuration? (car filters))
+                                                 (opensmtpd-filter-configuration-name (car filters))
+                                                 (opensmtpd-filter-phase-configuration-name (car filters))))
+                                         #f)]
+                        [mask-src (opensmtpd-listen-on-socket-configuration-configuration-mask-src record)]
+                        [tag (opensmtpd-listen-on-socket-configuration-configuration-tag record)])
+                   (string-append
+                    (if mask-src
+                        (string-append "mask-src ")
+                        "")
+                    (variable->string filter-name #:append "filter \"" #:postpend "\" ")
+                    (variable->string tag #:append "tag \"" #:postpend "\" ")
+                    "\n"))))
+
+(define (opensmtpd-action-relay-configuration->string record)
+  (let ([backup (opensmtpd-action-relay-configuration-backup record)]
+        [backup-mx (opensmtpd-action-relay-configuration-backup-mx record)]
+        [helo (opensmtpd-action-relay-configuration-helo record)]
+        ;; helo-src can either be a string IP address or an <opensmtpd-table-configuration>
+        [helo-src (if (opensmtpd-action-relay-configuration-helo-src record)
+                      (if (string? (opensmtpd-action-relay-configuration-helo-src record))
+                          (opensmtpd-action-relay-configuration-helo-src record)
+                          (string-append "<\""
+                                         (opensmtpd-table-configuration-name
+                                          (opensmtpd-action-relay-configuration-src record))
+                                         "\">"))
+                      #f)]
+        [domain (if (opensmtpd-action-relay-configuration-domain record)
+                    (opensmtpd-table-configuration-name
+                     (opensmtpd-action-relay-configuration-domain record))
+                    #f)]
+        [host (opensmtpd-action-relay-configuration-host record)]
+        [name (opensmtpd-action-relay-configuration-name record)]
+        [pki (if (opensmtpd-action-relay-configuration-pki record)
+                 (opensmtpd-pki-configuration-domain (opensmtpd-action-relay-configuration-pki record))
+                 #f)]
+        [srs (opensmtpd-action-relay-configuration-srs record)]
+        [tls (opensmtpd-action-relay-configuration-tls record)]
+        [auth (if (opensmtpd-action-relay-configuration-auth record)
+                  (opensmtpd-table-configuration-name
+                   (opensmtpd-action-relay-configuration-auth record))
+                  #f)]
+        [mail-from (opensmtpd-action-relay-configuration-mail-from record)]
+        ;; src can either be a string IP address or an <opensmtpd-table-configuration>
+        [src (if (opensmtpd-action-relay-configuration-src record)
+                 (if (string? (opensmtpd-action-relay-configuration-src record))
+                     (opensmtpd-action-relay-configuration-src record)
+                     (string-append "<\""
+                                    (opensmtpd-table-configuration-name
+                                     (opensmtpd-action-relay-configuration-src record))
+                                    "\">"))
+                 #f)]
+        )
+    (string-append
+     "\""
+     name
+     "\" " "relay "
+     ;;FIXME should I always quote the host fieldname? do I need to quote localhost via "localhost" ?
+     (variable->string host #:append "host \"" #:postpend "\" ")
+     (variable->string backup)
+     (variable->string backup-mx #:append "backup mx ")
+     (variable->string helo #:append "helo ")
+     (variable->string helo-src #:append "helo-src ")
+     (variable->string domain #:append "domain <\"" #:postpend "\"> ")
+     (variable->string host #:append "host ")
+     (variable->string pki #:append "pki ")
+     (variable->string srs)
+     (variable->string tls #:append "tls ")
+     (variable->string auth #:append "auth <" #:postpend "> ")
+     (variable->string mail-from #:append "mail-from ")
+     (variable->string src #:append "src ")
+     "\n")))
+
+(define (opensmtpd-lmtp-configuration->string record)
+  (string-append "lmtp "
+                 (opensmtpd-lmtp-configuration-destination record)
+                 (if (opensmtpd-lmtp-configuration-rcpt-to record)
+                     (begin
+                       " " (opensmtpd-lmtp-configuration-rcpt-to record))
+                     "")))
+
+(define (opensmtpd-mda-configuration->string record)
+  (string-append "mda "
+                 (opensmtpd-mda-configuration-command record) " "))
+
+(define (opensmtpd-maildir-configuration->string record)
+  (string-append "maildir "
+                 "\""
+                 (if (opensmtpd-maildir-configuration-pathname record)
+                     (opensmtpd-maildir-configuration-pathname record)
+                     "~/Maildir")
+                 "\""
+                 (if (opensmtpd-maildir-configuration-junk record)
+                     " junk "
+                     " ")))
+
+(define (opensmtpd-action-local-delivery-configuration->string record)
+  (let ([name (opensmtpd-action-local-delivery-configuration-name record)]
+        [method (opensmtpd-action-local-delivery-configuration-method record)]
+        [alias (if (opensmtpd-action-local-delivery-configuration-alias record)
+                   (opensmtpd-table-configuration-name
+                    (opensmtpd-action-local-delivery-configuration-alias record))
+                   #f)]
+        [ttl (opensmtpd-action-local-delivery-configuration-ttl record)]
+        [user (opensmtpd-action-local-delivery-configuration-user record)]
+        [userbase (if (opensmtpd-action-local-delivery-configuration-userbase record)
+                      (opensmtpd-table-configuration-name
+                       (opensmtpd-action-local-delivery-configuration-userbase record))
+                      #f)]
+        [virtual (if (opensmtpd-action-local-delivery-configuration-virtual record)
+                     (opensmtpd-table-configuration-name
+                      (opensmtpd-action-local-delivery-configuration-virtual record))
+                     #f)]
+        [wrapper (opensmtpd-action-local-delivery-configuration-wrapper record)])
+    (string-append
+     "\"" name "\" "
+     (cond [(string? method)
+            (string-append method " ")]
+           [(opensmtpd-mda-configuration? method)
+            (opensmtpd-mda-configuration->string method)]
+           [(opensmtpd-lmtp-configuration? method)
+            (opensmtpd-lmtp-configuration->string method)]
+           [(opensmtpd-maildir-configuration? method)
+            (opensmtpd-maildir-configuration->string method)])
+     ;; FIXME/TODO support specifying alias file:/path/to/alias-file  ?
+     ;; I do not think that is something that I can do...
+     (variable->string alias #:append "alias <\"" #:postpend "\"> ")
+     (variable->string ttl #:append "ttl ")
+     (variable->string user #:append "user ")
+     (variable->string userbase #:append "userbase <\"" #:postpend "\"> ")
+     (variable->string virtual #:append "virtual <" #:postpend "> ")
+     (variable->string wrapper #:append "wrapper "))))
+
+;; this function turns both opensmtpd-action-local-delivery-configuration and
+;; opensmtpd-action-relay-configuration into strings.
+(define (opensmtpd-action->string record)
+  (string-append "action "
+                 (cond [(opensmtpd-action-local-delivery-configuration? record)
+                        (opensmtpd-action-local-delivery-configuration->string record)]
+                       [(opensmtpd-action-relay-configuration? record)
+                        (opensmtpd-action-relay-configuration->string record)])
+                 " \n"))
+
+;; this turns option records found in <opensmtpd-match-configuration> into strings.
+(define* (opensmtpd-option-configuration->string record
+                                                 #:key
+                                                 (space-after-! #f))
+  (let ([not (opensmtpd-option-configuration-not record)]
+        [option (opensmtpd-option-configuration-option record)]
+        [regex (opensmtpd-option-configuration-regex record)]
+        [data (opensmtpd-option-configuration-data record)])
+    (string-append
+     (if not
+         (if space-after-!
+             "! "
+             "!")
+         "")
+     option " "
+     (if regex
+         "regex "
+         "")
+     (if data
+         (if (opensmtpd-table-configuration? data)
+             (string-append "<" (opensmtpd-table-configuration-name data) "> ")
+             (string-append data " "))
+         ""))))
+
+(define (opensmtpd-match-configuration->string record)
+  (string-append "match "
+                 (let* ([action (opensmtpd-match-configuration-action record)]
+                        [name (cond [(opensmtpd-action-relay-configuration? action)
+                                     (opensmtpd-action-relay-configuration-name action)]
+                                    [(opensmtpd-action-local-delivery-configuration? action)
+                                     (opensmtpd-action-local-delivery-configuration-name action)]
+                                    [else 'reject])]
+                        [options (opensmtpd-match-configuration-options record)])
+                   (string-append
+                    (if options
+                        (apply string-append
+                               (map opensmtpd-option-configuration->string options))
+                        "")
+                    (if (string? name)
+                        (string-append "action " "\"" name "\" ")
+                        "reject ")
+                    "\n"))))
+
+(define (opensmtpd-ca-configuration->string record)
+  (string-append "ca " (opensmtpd-ca-configuration-name record) " "
+                 "cert \"" (opensmtpd-ca-configuration-file record) "\"\n"))
+
+(define (opensmtpd-pki-configuration->string record)
+  (let ([domain (opensmtpd-pki-configuration-domain record)]
+        [cert (opensmtpd-pki-configuration-cert record)]
+        [key (opensmtpd-pki-configuration-key record)]
+        [dhe (opensmtpd-pki-configuration-dhe record)])
+    (string-append "pki " domain " " "cert \"" cert "\" \n"
+                   "pki " domain " " "key \"" key "\" \n"
+                   (if dhe
+                       (string-append
+                        "pki " domain " " "dhe " dhe "\n")
+                       ""))))
+
+(define (generate-filter-chain-name list-of-filters)
+  (string-drop-right (apply string-append
+                            (flatten
+                             (map (lambda (filter)
+                                    (list
+                                     (if (opensmtpd-filter-configuration? filter)
+                                         (opensmtpd-filter-configuration-name filter)
+                                         (opensmtpd-filter-phase-configuration-name filter))
+                                     "-"))
+                                  list-of-filters)))
+                     1))
+
+;; this procedure takes in a list of <opensmtpd-filter-configuration> and <opensmtpd-filter-phase-configuration>,
+;; returns a string of the form:
+;; filter "uniquelyGeneratedName" chain chain { "filter-name", "filter-name2" [, ...]}
+(define (opensmtpd-filter-chain->string list-of-filters)
+  (string-append "filter \""
+                 (generate-filter-chain-name list-of-filters)
+                 "\" "
+                 "chain {"
+                 (string-drop-right
+                  (apply string-append
+                         (flatten
+                          (map (lambda (filter)
+                                 (list
+                                  "\""
+                                  (if (opensmtpd-filter-configuration? filter)
+                                      (opensmtpd-filter-configuration-name filter)
+                                      (opensmtpd-filter-phase-configuration-name filter))
+                                  "\", "))
+                               list-of-filters))
+                         ) 2)
+                 "}\n"))
+
+(define (opensmtpd-filter-phase-configuration->string record)
+  (let ([name (opensmtpd-filter-phase-configuration-name record)]
+        [phase (opensmtpd-filter-phase-configuration-phase record)]
+        [decision (opensmtpd-filter-phase-configuration-decision record)]
+        [options (opensmtpd-filter-phase-configuration-options record)]
+        [message (opensmtpd-filter-phase-configuration-message record)]
+        [value (opensmtpd-filter-phase-configuration-value record)])
+    (string-append "filter "
+                   "\"" name "\" "
+                   "phase " phase " "
+                   "match "
+                   (apply string-append ; turn the options into a string
+                          (flatten
+                           (map (lambda (option)
+                                  (opensmtpd-option-configuration->string option #:space-after-! #f))
+                                options)))
+                   " "
+                   decision " "
+                   (if (string-in-list? decision (list "reject" "disconnect"))
+                       (string-append "\"" message "\"")
+                       "")
+                   (if (string=? "rewrite" decision)
+                       (string-append "rewrite " (number->string value))
+                       "")
+                   "\n")))
+
+;; filters elements may be <opensmtpd-filter-configuration>, <opensmtpd-filter-phase-configuration>,
+;; and lists that look like (list (opensmtpd-filter-configuration...) (opensmtpd-filter-phase-configuration ...)
+;; ...)
+;; this function converts it to a string.
+;; Consider if a user passed in a valid <opensmtpd-configuration>, whose total valid filters
+;; so that (get-opensmtpd-filters (opensmtpd-configuration)) returns
+;; look like this: (we will call this list "total filters"):
+;; (list (opensmtpd-filter
+;;         (name "rspamd")
+;;         (proc "rspamd"))
+;;       (list (opensmtpd-filter-phase-configuration ; this is a listen-on, with a filter-chain.
+;;               (name "dkimsign")
+;;               ...)
+;;               (opensmtpd-filter
+;;                 (name "rspamd")
+;;                 (proc "rspamd"))))
+;;
+;; did you notice that filter "rspamd" is listed twice?  How do you make sure that it is NOT
+;; printed twice in smtpd.conf?
+;; 1st flatten "total filters", then remove its duplicates.  Then print all of those filters.
+;; 2nd now we go through "total filters", and we only print the non-filter-chains.
+(define (opensmtpd-filters->string filters)
+  ;; first display the unique <opensmtpd-filter-configuration>s. and <opensmtpd-filter-phase-configuration>s.
+  ;; to do this: flatten filters, then remove duplicates.
+  (string-append
+   (apply string-append
+          (map (lambda (filter)
+                 (cond ((opensmtpd-filter-phase-configuration? filter)
+                        (opensmtpd-filter-phase-configuration->string filter))
+                       (else            ; you are a <opensmtpd-filter-configuration>
+                        (string-append "filter "
+                                       "\"" (opensmtpd-filter-configuration-name filter) "\" "
+                                       (if (opensmtpd-filter-exec filter)
+                                           "proc-exec "
+                                           "proc ")
+                                       "\"" (opensmtpd-filter-configuration-proc filter) "\""
+                                       "\n"))))
+               (delete-duplicates (flatten filters))))
+   ;; now we have to print the filter chains.
+   (apply string-append
+          (remove boolean?
+                  (map (lambda (filter)
+                         (cond ((list? filter)
+                                (opensmtpd-filter-chain->string filter))
+                               (else    ; you are a <opensmtpd-filter-configuration>
+                                #f)))
+                       filters)))))
+
+(define (opensmtpd-configuration-listen->string string)
+  (string-append
+   "include \"" string "\"\n"))
+
+(define (opensmtpd-configuration-srs->string record)
+  (let ([key (opensmtpd-srs-configuration-key record)]
+        [backup-key (opensmtpd-srs-configuration-backup-key record)]
+        [ttl-delay (opensmtpd-srs-configuration-ttl-delay record)])
+    (string-append
+     (variable->string key #:append "srs key " #:postpend "\n")
+     (variable->string backup-key #:append "srs key backup " #:postpend "\n")
+     (variable->string ttl-delay #:append "srs ttl " #:postpend "\n")
+     "\n")))
+
+;; TODO make sure all options here work!  I just fixed limit-max-rcpt!
+(define (opensmtpd-smtp-configuration->string record)
+  (let ([ciphers (opensmtpd-smtp-configuration-ciphers record)]
+        [limit-max-mails (opensmtpd-smtp-configuration-limit-max-mails record)]
+        [limit-max-rcpt (opensmtpd-smtp-configuration-limit-max-rcpt record)]
+        [max-message-size (opensmtpd-smtp-configuration-max-message-size record)]
+        [sub-addr-delim (opensmtpd-smtp-configuration-sub-addr-delim record)])
+    (string-append
+     (variable->string ciphers #:append "smtp ciphers " #:postpend "\n")
+     (variable->string limit-max-mails #:append "smtp limit max-mails " #:postpend "\n")
+     (variable->string limit-max-rcpt #:append "smtp limit max-rcpt " #:postpend "\n")
+     (variable->string max-message-size #:append "smtp max-message-size " #:postpend "\n")
+     (variable->string sub-addr-delim #:append "smtp sub-addr-delim " #:postpend "\n")
+     "\n")))
+
+(define (opensmtpd-configuration-queue->string record)
+  (let ([compression (opensmtpd-queue-configuration-compression record)]
+        [encryption (opensmtpd-queue-configuration-encryption record)]
+        [ttl-delay (opensmtpd-queue-configuration-ttl-delay record)])
+    (string-append
+     (if compression
+         "queue compression\n"
+         "")
+     (if encryption
+         (string-append
+          "queue encryption "
+          (if (not (boolean? encryption))
+              encryption
+              "")
+          "\n")
+         "")
+     (if ttl-delay
+         (string-append "queue ttl" ttl-delay "\n")
+         ""))))
+
+;; build a list of <opensmtpd-action> from
+;; opensmtpd-configuration-matches, which is a list of <opensmtpd-match-configuration>.
+;; Each <opensmtpd-match-configuration> has a fieldname 'action', which accepts an <opensmtpd-action>.
+(define (get-opensmtpd-actions record)
+  (define opensmtpd-actions
+    (let loop ([list (opensmtpd-configuration-matches record)])
+      (if (null? list)
+          '()
+          (cons (opensmtpd-match-configuration-action (car list))
+                (loop (cdr list))))))
+  (delete-duplicates (append opensmtpd-actions)))
+
+;; build a list of opensmtpd-pki-configurations from
+;; opensmtpd-configuration-listen-ons and
+;; get-opensmtpd-actions
+(define (get-opensmtpd-pki-configurations record)
+  ;; TODO/FIXME/maybe/wishlist could get-opensmtpd-actions -> NOT have an opensmtpd-action-relay-configuration?
+  ;; I think so.  And if it did NOT have a relay configuration, then action-pkis would be '() when
+  ;; it needs to be #f.  because if the opensmtpd-configuration has NO pkis, then this function will
+  ;; return '(), when it should return #f.  If it returns '(), then opensmtpd-configuration-fieldname->string will
+  ;; print the string "\n" instead of ""
+  (define action-pkis
+    (let loop1 ([list (get-opensmtpd-actions record)])
+      (if (null? list)
+          '()
+          (if (and (opensmtpd-action-relay-configuration? (car list))
+                   (opensmtpd-action-relay-configuration-pki (car list)))
+              (cons (opensmtpd-action-relay-configuration-pki (car list))
+                    (loop1 (cdr list)))
+              (loop1 (cdr list))))))
+  ;; FIXME/TODO/maybe/wishlist
+  ;; this could be #f aka left blank. aka there are no listen-ons records with pkis.
+  ;; aka there are no lines in the configuration like:
+  ;; listen on eth0 tls pki smtp.gnucode.me in that case the smtpd.conf will have an extra "\n"
+  (define listen-on-pkis
+    (let loop2 ([list (opensmtpd-configuration-listen-ons record)])
+      (if (null? list)
+          '()
+          (if (opensmtpd-listen-on-configuration-pki (car list))
+              (cons (opensmtpd-listen-on-configuration-pki (car list))
+                    (loop2 (cdr list)))
+              (loop2 (cdr list))))))
+  (delete-duplicates (append action-pkis listen-on-pkis)))
+
+;; takes in a <opensmtpd-configuration> and returns a list whose elements are <opensmtpd-filter-configuration>,
+;; <opensmtpd-filter-phase-configuration>, and a filter-chain.
+;; It returns a list of <opensmtpd-filter-configuration> and/or <opensmtpd-filter-phase-configuration>
+;; here's an example of what this procedure might return:
+;; (list (opensmtpd-filter-configuration...) (opensmtpd-filter-phase-configuration ...)
+;;       (openmstpd-filter ...) (opensmtpd-filter-phase-configuration ...)
+;;       ;; this next list is a filter-chain.
+;;       (list (opensmtpd-filter-phase-configuration ...) (opensmtpd-filter-configuration...)))
+;;
+;; This procedure handles filter chains a little odd.
+(define (get-opensmtpd-filters record)
+  (define list-of-listen-on-records (if (opensmtpd-configuration-listen-ons record)
+                                        (opensmtpd-configuration-listen-ons record)
+                                        '()))
+
+  (define listen-on-socket-filters
+    (if (opensmtpd-listen-on-socket-configuration-configuration-filters (opensmtpd-configuration-listen-on-socket record))
+        (opensmtpd-listen-on-socket-configuration-configuration-filters (opensmtpd-configuration-listen-on-socket record))
+        '()))
+
+    (delete-duplicates
+     (append (remove boolean?
+                     (map-in-order (lambda (listen-on-record) ; get the filters found in the <listen-on-record>s
+                                     (if (and (opensmtpd-listen-on-configuration-filters listen-on-record)
+                                              (= 1 (length (opensmtpd-listen-on-configuration-filters
+                                                            listen-on-record))))
+                                         (car (opensmtpd-listen-on-configuration-filters listen-on-record))
+                                         (opensmtpd-listen-on-configuration-filters listen-on-record)))
+                                   list-of-listen-on-records))
+             listen-on-socket-filters)))
+
+(define (flatten . lst)
+  "Return a list that recursively concatenates all sub-lists of LST."
+  (define (flatten1 head out)
+    (if (list? head)
+        (fold-right flatten1 out head)
+        (cons head out)))
+  (fold-right flatten1 '() lst))
+
+;; This function takes in a record, or list, or anything, and returns
+;; a list of <opensmtpd-table-configuration>s assuming the thing you passed into it had
+;; any <opensmtpd-table-configuration>s.
+;;
+;; is object record? call func on it's fieldnames
+;; is object list? loop through it's fieldnames calling func on it's records
+;; is object #f or string? or '()? -> #f
+(define (get-opensmtpd-tables value)
+  (delete-duplicates
+   (remove boolean? (flatten ;; turn (list '(1) '(2 '(3))) -> '(1 2 3)
+               (cond ((opensmtpd-table-configuration? value)
+                      value)
+                     ((record? value)
+                      (let* ([record-type (record-type-descriptor value)]
+                             [list-of-record-fieldnames (record-type-fields record-type)])
+                        (map (lambda (fieldname)
+                               (get-opensmtpd-tables ((record-accessor record-type fieldname) value)))
+                             list-of-record-fieldnames)))
+                     ((and (list? value) (not (null? value)))
+                      (map get-opensmtpd-tables value))
+                     (else #f))))))
+
+(define (opensmtpd-configuration-fieldname->string record fieldname-accessor record->string)
+  (if (fieldname-accessor record)
+      (begin
+        (string-append
+         (list-of-records->string (fieldname-accessor record) record->string) "\n"))
+      ""))
+
+(define (list-of-records->string list-of-records record->string)
+  (string-append
+   (cond [(not (list? list-of-records))
+          (record->string list-of-records)]
+         [else
+          (let loop ([list list-of-records])
+            (if (null? list)
+                ""
+                (string-append
+                 (record->string (car list))
+                 (loop (cdr list)))))])))
+
+
+;; FIXME/TODO should I use format here srfi-28 ?
+;; web.scm nginx does a (format #f "string" "another string")
+;; this could be a list like (list (file-append opensmtpd-dkimsign "/libexec/filter") "-d gnucode.me -s /path/to/selector.cert")
+;; Then opensmtpd-configuration->mixed-text-file could be rewritten to be something like
+;; (mixed-text-file (eval `(string-append (opensmtpd-configuration-fieldname->string ...)) (gnu services mail)))
+(define (opensmtpd-configuration->mixed-text-file record)
+  ;; should I use this named let, or should I give this a name, or not use it at all...
+  ;; eg: (write-all-fieldnames (list (cons fieldname fieldname->string) (cons fieldname2 fieldname->string)))
+  ;; (let loop ([list (list (cons opensmtpd-configuration-includes (lambda (string)
+  ;;                                                                 (string-append
+  ;;                                                                  "include \"" string "\"\n")))
+  ;;                        (cons opensmtpd-configuration-smtp opensmtpd-smtp->string)
+  ;;                        (cons opensmtpd-configuration-srs opensmtpd-srs->string))])
+  ;;   (if (null? list)
+  ;;       ""
+  ;;       (string-append (opensmtpd-configuration-fieldname->string record
+  ;;                                                                 (caar list)
+  ;;                                                                 (cdar list))
+  ;;                      (loop (cdr list)))))
+
+  ;;(mixed-text-file "opensmtpd.conf")
+  (string-append
+   ;; write out the includes
+   (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-includes
+                                              opensmtpd-configuration-listen->string)
+   (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-bounce
+                                              (lambda (%bounce)
+                                                (if %bounce
+                                                    (list-of-strings->string %bounce)
+                                                    "")))
+   (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-smtp
+                                              opensmtpd-smtp-configuration->string)
+   (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-srs
+                                              opensmtpd-configuration-srs->string)
+   (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-queue
+                                              opensmtpd-configuration-queue->string)
+   ;; write out the mta-max-deferred
+   (opensmtpd-configuration-fieldname->string
+    record opensmtpd-configuration-mta-max-deferred
+    (lambda (var)
+      (string-append "mta max-deferred "
+                     (number->string (opensmtpd-configuration-mta-max-deferred record)) "\n")))
+   ;;write out all the tables
+   (opensmtpd-configuration-fieldname->string record get-opensmtpd-tables opensmtpd-table-configuration->string)
+   ;; TODO should I change the below line of code into these two lines of code?
+   ;;(opensmtpd-configuration-fieldname->string record get-opensmtpd-filters-and-filter-phases opensmtpd-filter-and-filter-phase->string)
+   ;;(opensmtpd-configuration-fieldname->string record get-opensmtpd-filter-chains opensmtpd-filter-chain->string)
+   ;; write out all the filters
+   (opensmtpd-filters->string (get-opensmtpd-filters record))
+   ;; write out all the cas
+   (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-cas opensmtpd-ca-configuration->string)
+   ;; write out all the pkis
+   (opensmtpd-configuration-fieldname->string record get-opensmtpd-pki-configurations opensmtpd-pki-configuration->string)
+   ;; write all of the listen-on-records
+   (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-listen-ons
+                                              opensmtpd-listen-on-configuration->string)
+   (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-listen-on-socket
+                                              opensmtpd-listen-on-socket-configuration->string)
+   ;; write all the actions
+   (opensmtpd-configuration-fieldname->string record get-opensmtpd-actions
+                                              opensmtpd-action->string)
+   ;; write all of the matches
+   (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-matches opensmtpd-match-configuration->string)))
+
 
 (define %default-opensmtpd-config-file
   (plain-file "smtpd.conf" "
-- 
2.36.1
J
J
Joshua Branson wrote on 17 Jun 23:54 +0200
[PATCH] gnu: services: opensmtpd-records-task-list.org: Some notes about how I thought about building this service. And some additional task lists, as well as the WIP documentation.
(address . 56046@debbugs.gnu.org)(name . Joshua Branson)(address . jbranso@dismail.de)
20220617215407.21290-1-jbranso@dismail.de
---
gnu/services/opensmtpd-records-task-list.org | 5122 ++++++++++++++++++
1 file changed, 5122 insertions(+)
create mode 100644 gnu/services/opensmtpd-records-task-list.org

Toggle diff (5130 lines)
diff --git a/gnu/services/opensmtpd-records-task-list.org b/gnu/services/opensmtpd-records-task-list.org
new file mode 100644
index 0000000000..c138aab8fe
--- /dev/null
+++ b/gnu/services/opensmtpd-records-task-list.org
@@ -0,0 +1,5122 @@
+#+title: Opensmtpd Records Task List
+#+AUTHOR: Joshua Branson
+
+
+(service (@ (gnu services mail) opensmtpd-service-type)
+             ((@ (gnu services mail) opensmtpd-configuration)
+              (config-file …)))
+
+* tasks
+** PROJ I have decent data structures. now let's get some good code. [0/6]
+*** why are good data structures important?
+**** nckx's advice: use a simple 1-1 mapping
+"...as I think Guix services ought to faithfully wrap the native
+syntax whenever possible (implement alternative simple APIs on top of
+that — fine)."
+
+-nckx from irc on #guix
+
+**** To follow nckx's advice, one might create the =<opensmtpd-service>= like this:
+#+BEGIN_SRC scheme
+  (service opensmtpd-service
+           (opensmtpd-configuration
+            (includes ...)
+            (tables ...)
+            (pkis ...)
+            (filters ...)
+            (listen-on ...)
+            (actions ...)
+            (matches ...)))
+#+END_SRC
+
+Defining the service this way, makes it VERY easy from a development point of
+view.  But it makes it possible for users to create simple mistakes when
+defining the service.
+
+For example, it is possible to define an nginx service that will successfully
+reconfigure the system. BUT after reboot nginx refuses to start. Why? Who knows.
+Guix won't tell you. Neither will the Shepherd. To fix this, the user has to go
+digging into the nginx logs, and he might not know where to find those. If
+possible, when the user specificies a =<opensmtpd-configuration>= that has
+obvious errors, then the guix services should make reconfigure fail and print a
+helpful error message.
+
+**** BUT it would be better if the service uses better datastructures.
+
+I should follow nckx's advice, and Linus' advice: good programmers use good
+datastructures. If you have good datastructures, then your code will almost
+write itself.
+
+It might make the service a little harder to develop, but end-users will find
+the service easier to use. This would eliminate common errors like misspellings
+and give appropriate error messages. Practically it would ensure each
+=<opensmtpd-match-configuration>= has a corresponding =<opensmtpd-action>=,
+creating a table name and then misspelling the table name later, and defining
+a table but never using it, etc.
+
+**** Example configuration
+
+#+BEGIN_SRC scheme
+(service opensmtpd-service-type
+         (let ([interface "lo"]
+               [creds-table (opensmtpd-table-configuration
+                             (name "creds")
+                             (data
+                              (list
+                               (cons "joshua"
+                                     "$6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86."))))]
+               [receive-action (opensmtpd-action-local-delivery-configuration
+                                (name "receive")
+                                (method (opensmtpd-maildir-configuration
+                                         (pathname "/home/%{rcpt.user}/Maildir")
+                                         (junk #t)))
+                                (virtual (opensmtpd-table-configuration
+                                          (name "virtual")
+                                          (data (list "josh" "jbranso@dismail.de")))))]
+               [filter-dkimsign (opensmtpd-filter-configuration
+                                 (name "dkimsign")
+                                 (exec #t)
+                                 (proc (string-append "/path/to/dkimsign  -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k "
+                                                      "/path/to/dkimsign-key user nobody group nobody")))]
+               [smtp.gnucode.me (opensmtpd-pki-configuration
+                                 (domain "smtp.gnucode.me")
+                                 (cert "opensmtpd.scm")
+                                 (key "opensmtpd.scm"))])
+           (opensmtpd-configuration
+            (mta-max-deferred 50)
+            (queue
+             (opensmtpd-queue-configuration
+              (compression #t)))
+            (smtp
+             (opensmtpd-smtp-configuration
+              (max-message-size "10M")))
+            (srs
+             (opensmtpd-srs-configuration
+              (ttl-delay "5d")))
+            (listen-ons
+             (list
+              (opensmtpd-listen-on-configuration
+               (interface interface)
+               (port 25)
+               (secure-connection "tls")
+               (filters (list (opensmtpd-filter-phase-configuration
+                               (name "noFRDNS")
+                               (phase "commit")
+                               (options (list (opensmtpd-option-configuration
+                                                  (option "fcrdns")
+                                                  (not #t))))
+                               (decision "disconnect")
+                               (message "433 No FCRDNS"))))
+               (pki smtp.gnucode.me))
+              ;; this lets local users logged into the system via ssh send email
+              (opensmtpd-listen-on-configuration
+               (interface interface)
+               (port 465)
+               (secure-connection "smtps")
+               (pki smtp.gnucode.me)
+               (auth creds-table)
+               (filters (list filter-dkimsign)))
+              (opensmtpd-listen-on-configuration
+               (interface interface)
+               (port 587)
+               (secure-connection "tls-require")
+               (pki smtp.gnucode.me)
+               (auth creds-table)
+               (filters (list filter-dkimsign)))))
+            (matches (list
+                      (opensmtpd-match-configuration
+                       (action (opensmtpd-action-relay-configuration
+                                (name "relay")))
+                       (options (list (opensmtpd-option-configuration
+                                       (option "for any"))
+                                      (opensmtpd-option-configuration
+                                       (option "from any"))
+                                      (opensmtpd-option-configuration
+                                       (option "auth")))))
+                      (opensmtpd-match-configuration
+                       (action receive-action)
+                       (options (list (opensmtpd-option-configuration
+                                       (option "from any"))
+                                      (opensmtpd-option-configuration
+                                       (option "for domain")
+                                       (data (opensmtpd-table-configuration
+                                              (name "domain-table")
+                                              (data (list "gnucode.me" "gnu-hurd.com"))))))))
+                      (opensmtpd-match-configuration
+                       (action receive-action)
+                       (options (list (opensmtpd-option-configuration
+                                       (option "for local"))))))))))
+#+END_SRC
+
+:OldConfigurationSyntax:
+#+BEGIN_SRC scheme
+  (service opensmtpd-service-type
+               (opensmtpd-configuration
+                (pkis (list
+                       (opensmtpd-pki-configuration
+                        ...)))
+                (tables (list
+                         (opensmtpd-table-configuration
+                          ...)
+                         (opensmtpd-table-configuration
+                          ...)))
+                (listen-ons
+                 (list
+                  (opensmtpd-listen-on-configuration
+                   ...)
+                  (opensmtpd-listen-on-configuration
+                   ...)))
+                (actions
+                 (list
+                  (opensmtpd-action
+                     ...)
+                  (opensmtpd-action
+                   ...)))
+                (matches (list
+                          (opensmtpd-match-configuration
+                           ...)
+                          (opensmtpd-match-configuration
+                           ...)))
+                (filter-chains
+                 (list
+                  (opensmtpd-filter-chain
+                   (name "dropDumbEmails")
+                   (filter-names (list "nofcrdnsDisconnect"
+                                       "nordnsDisconnect")))))
+                (filter-phases
+                 (list (opensmtpd-filter-phase-configuration
+                        ...)
+                       (opensmtpd-filter-phase-configuration
+                        ...)))))
+#+END_SRC
+
+Here you have to define the =pki=s twice!  You define it once in the =pkis=
+section, and then you reference it later.  This could potentially cause a
+mispelling error.  That would be silly to debug as an end-user.
+
+:END:
+
+*** PROJ tweek the code for =<opensmtpd-filter-configuration>= & =<opensmtpd-filter-phase-configuration>= records [4/7]
+**** Why I chose the current datastructures of =<opensmtpd-filter-configuration>= & =<opensmtpd-filter-phase-configuration>=
+
+According to the man page I have a four kinds of filters:
+
+#+BEGIN_EXAMPLE
+1. filter chain-name chain {filter-name [, ...]}
+         Register a chain of filters chain-name, consisting of the filters listed from filter-name.
+         Filters part of a filter chain are executed in order of declaration for each phase that
+         they are registered for.  A filter chain may be used in place of a filter for any direc‐
+         tive but filter chains themselves.
+2. filter filter-name phase phase-name match conditions decision
+         Register a filter filter-name.  A decision about what to do with the mail is taken at
+         phase phase-name when matching conditions.  Phases, matching conditions, and decisions are
+         described in MAIL FILTERING, below.
+3. filter filter-name proc proc-name
+         Register "proc" filter filter-name backed by the proc-name process.
+4. filter filter-name proc-exec command
+         Register and execute "proc" filter filter-name from command.  If command starts with a
+         slash it is executed with an absolute path, else it will be run from
+         “/gnu/store/2d13sdz76ldq8zgwv4wif0zx7hkr3mh2-opensmtpd-6.8.0p2/libexec/opensmtpd”.
+#+END_EXAMPLE
+
+=chain-name= could be easily represented as a list of filters. in the
+opensmtpd-configuration-filter fieldname:
+
+#+BEGIN_SRC scheme
+(opensmtpd-configuration
+ (listen-on
+  (filter
+   (list (opensmtpd-filter-configuration)
+         (opensmtpd-filter-configuration)
+         (opensmtpd-filter-configuration)))))
+#+END_SRC
+
+For example, this is probably easier
+
+#+BEGIN_SRC scheme
+  (opensmtpd-configuration
+   (actions (list
+             (opensmtpd-action
+              (name "relay")
+              (method (opensmtpd-relay-configuration
+                       (domain (opensmtpd-table-configuration
+                                ;;(name "domains")  ;; with some smart coding, the name would NOT be needed.
+                                (data (list
+                                         "gnucode.me"
+                                         "gnu-hurd.com"))))))))))
+#+END_SRC
+
+than the alternative:
+
+#+BEGIN_SRC scheme
+  (opensmtpd-configuration
+   (tables (list
+            (opensmtpd-table-configuration
+             (name "domains")
+             (data (list
+                      "gnucode.me"
+                      "gnu-hurd.com")))))
+   (actions (list
+             (opensmtpd-action
+              (name "relay")
+              (method (opensmtpd-relay-configuration
+                       (domain "domains")))))))
+#+END_SRC
+
+**** some example code for each of the 3 types of filters
+
+1. filter phase
+#+BEGIN_SRC scheme
+(opensmtpd-filter-phase-configuration
+ (name "phase")
+ (phase "connect")
+ (options
+  (list
+   (opensmtpd-option-configuration
+    (option "src")
+    (not #t)
+    (regex #t)
+    (table (opensmtpd-table-configuration (name "src-option-table")
+                            (data (list "cat" "hot")))))))
+ (decision "reject")
+ (message "We do not want spam here!"))
+#+END_SRC
+
+#+RESULTS:
+
+2. filter proc
+this is a filter-proc
+#+BEGIN_SRC scheme
+(opensmtpd-filter
+ (name "proc")
+ (proc "dkimsign"))
+#+END_SRC
+
+3. filter proc-exec
+#+BEGIN_SRC scheme
+(opensmtpd-filter
+ (name "proc")
+ (exec #t)
+ (proc "dkimsign"))
+#+END_SRC
+
+***** Why am I doing the data structure like the above?
+
+filter-proc and proc-exec as defined in man smtpd.conf can both use the same
+<opensmtpd-filter-configuration> record.  That works just fine.
+
+But filter-phase is a different beast.  I do NOT want someone to accidentally
+define something like the following which is BAD data:
+
+#+BEGIN_SRC scheme
+(opensmtpd-filter
+ (name "proc")
+ (exec #t)
+ (proc "dkimsign"))
+
+#+END_SRC
+**** NO Is it advantageous/desireable to merge =<opensmtpd-filter-configuration>= & <opensmtpd-filter-phase-configuration>
+
+When a user creates a filter, he is either going to create a
+=<opensmtpd-filter-configuration>~ or an ~<opensmtpd-filter-phase>= NOT both. If
+we define separate records, then it is impossible for a user to accidentally
+define a filter record using fieldnames from both filter types. eg:
+
+#+BEGIN_SRC scheme
+(opensmtpd-filter-configuration
+ (name "filter")
+ (exec #t)
+ (proc "dkimsign")
+ (phase "connect"))  ;; this phase should NOT be there. this is a <opensmtpd-filter-configuration>
+#+END_SRC
+
+If =<opensmtpd-filter-configuration>= & =<opensmtpd-filter-phase-configuration>= are separte then the above
+would correctly result in an error message for free.
+**** TODO make <opensmtpd-filter-configuration> fieldname 'proc' accept a list of strings and/or a <gexp>s
+
+Suppose you want to do dkimsigning in smtpd.conf.  Here is how you might
+register the official opensmtpd dkimsign filter:
+
+#+BEGIN_EXAMPLE
+filter "dkimsign" proc-exec "filter-dkimsign -d <domain> -s <selector>  \
+       -k /gnu/store/2d13sdz76ldq8zgwv4wif0zx7hkr3mh2-opensmtpd-6.8.0p2/etc/dkim/private.key" user _dkimsign group _dkimsign
+#+END_EXAMPLE
+
+For example my hacky code to do dkimsigning looks like:
+
+#+BEGIN_SRC scheme
+filter \"dkimsign\"  \
+  proc-exec \"" path-to-filter-dkimsign " -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k " etc-dkimsign-key-file
+  " \"  \
+  user nobody group nogroup
+
+#+END_SRC
+
+Here is some example code of how we could create an
+=<opensmtpd-filter-configuration>= that registers a dkimsign filter.  The code
+below probably will NOT work.
+
+#+BEGIN_SRC scheme
+(let ((etc-dkimsign-key-file "filename.key")
+      (path-to-dkimsign-key "/etc/opensmtpd/")))
+(opensmtpd-filter-configuration
+ (name "dkimsign")
+ (proc (list
+        (file-append opensmtpd-filter-dkimsign "/libexec/opensmtpd/filter-dkimsign")
+        " -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k "
+        ~#(let ([UID-nobody (passwd:uid (getpw "nobody"))]
+                [GID-root (group:gid (getgr "root"))]
+                [GID-nogroup (group:gid (getgr "nogroup"))])
+            ;; #o550 user root can read/open the directory
+            ;; and the group "root" can read/open the directory.
+            ;; change these two lines to (mkdir-p) ?
+            (unless (file-exists? "/etc/opensmtpd")
+              (mkdir "/etc/opensmtpd" #o550))
+
+            ;; root can read/write/execute on directory dkimsign
+            ;; group "root" can read and execute
+            (unless (file-exists? "/etc/opensmtpd/dkimsign")
+              (mkdir "/etc/opensmtpd/dkimsign" #o750))
+
+            (copy-file path-to-dkimsign-key etc-dkimsign-key-file)
+            ;; make the etc-dkimsign-key-file to owned by nobody and group nogroup.
+            (chown "/etc/opensmtpd" UID-nobody GID-root)
+            (chown "/etc/opensmtpd/dkimsign" UID-nobody GID-root)
+            (chown etc-dkimsign-key-file UID-nobody GID-nogroup)
+            "/etc/opensmtpd/dkimsign/2021-09-22-rsa1024-gnucode.me.key")
+        "user nobody group nogroup"))
+ (exec #))
+#+END_SRC
+
+Here is the full for how I currently run opensmtpd:
+
+#+BEGIN_SRC sh :dir ~/prog/gnu/guix/guix-config/linode-guix-system-configuration/ :results raw
+cat opensmtpd.scm
+#+END_SRC
+
+#+RESULTS:
+#+BEGIN_SRC scheme
+(define-module (opensmtpd)
+  #:use-module (guix gexp)
+  #:use-module (guix records)
+  #:use-module (gnu packages mail) ;;for finding location of filter-dkimsign
+  #:export (
+            %smtpd.conf
+            ))
+
+
+;; to create credentials for now, I need to do the following:
+;;  find /gnu/store -name '*encrypt*' | grep opensmtpd
+;; /gnu/store/blah/opensmtpd/encrypt
+(define creds
+  (plain-file "creds"
+              ;; this is my joshua's password for server.  This can be found on dobby's /home/joshua/.authinfo/
+              "joshua $6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86."))
+
+(define vdoms
+  (plain-file
+   "vdoms"
+   "gnucode.me
+gnu-hurd.com"))
+
+(define vusers
+  (plain-file
+   "vusers"
+   "joshua@gnucode.me  joshua
+jbranso@gnucode.me     joshua
+postmaster@gnucode.me  joshua"))
+
+(define path-to-filter-dkimsign
+  (file-append opensmtpd-filter-dkimsign "/libexec/opensmtpd/filter-dkimsign"))
+
+(define path-to-dkimsign-key (string-append (getcwd) "/email-dkim-ssh-keys/2021-09-22-rsa1024-gnucode.me.key"))
+(define etc-dkimsign-key-file "/etc/opensmtpd/dkimsign/2021-09-22-rsa1024-gnucode.me.key")
+
+;; FIXME:  This should become a derivation.  Currently it just runs when I evaluate
+;; %smtpd.conf.  For example it should look like this?
+;; (define build-exp
+;;   #~(begin
+;;       (mkdir #$output)
+;;       (chdir #$output)
+;;       (symlink (string-append #$coreutils "/bin/ls")
+;;                "list-files")))
+
+;; I will need to extend the opensmtpd service, to create a directory
+;; in etc.  This line needs to be added to etc-service.
+;; (service-extension etc-service-type opensmtpd-etc-service)
+;; I'll then need to create a opensmtpd-etc-service procedure.  ganeti has
+;; a good example.
+
+;; It should also use the /etc service, which is a service for creating
+;; directories and files in /etc ?
+(define (create-etc-dkimsign-key-file)
+  #~(let ([UID-nobody (passwd:uid (getpw "nobody"))]
+          [GID-root (group:gid (getgr "root"))]
+          [GID-nogroup (group:gid (getgr "nogroup"))])
+    ;; #o550 user root can read/open the directory
+    ;; and the group "root" can read/open the directory.
+    ;; change these two lines to (mkdir-p) ?
+      (unless (file-exists? "/etc/opensmtpd")
+        (mkdir "/etc/opensmtpd" #o550))
+
+      ;; root can read/write/execute on directory dkimsign
+      ;; group "root" can read and execute
+      (unless (file-exists? "/etc/opensmtpd/dkimsign")
+        (mkdir "/etc/opensmtpd/dkimsign" #o750))
+
+      (copy-file path-to-dkimsign-key etc-dkimsign-key-file)
+      ;; ;; ;; make the etc-dkimsign-key-file to owned by nobody and group nogroup.
+      (chown "/etc/opensmtpd" UID-nobody GID-root)
+      (chown "/etc/opensmtpd/dkimsign" UID-nobody GID-root)
+      (chown etc-dkimsign-key-file UID-nobody GID-nogroup)
+      etc-dkimsign-key-file))
+
+(define %smtpd.conf
+  (mixed-text-file "smtpd.conf"
+                   "
+# This is the smtpd server system-wide configuration file.
+# See smtpd.conf(5) for more information.
+# borrowed from the archlinux guix
+# https://wiki.archlinux.org/index.php/OpenSMTPD#Simple_OpenSMTPD/mbox_configuration
+
+# My TLS certificate and key
+table aliases file:/etc/aliases
+pki smtp.gnucode.me cert \"/etc/letsencrypt/live/gnucode.me/fullchain.pem\"
+pki smtp.gnucode.me key \"/etc/letsencrypt/live/gnucode.me/privkey.pem\"
+
+# for now I am NOT using the virtual credentials
+# table creds { joshua = $6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86. }
+table creds \"" creds "\"
+table vdoms \"" vdoms "\"
+# table vdoms { gnucode.me, gnu-hurd.com }
+# table vusers { joshua@gnucode.me = joshua, jbranso@gnucode.me = joshua, postmaster@gnucode.me = joshua }
+table vusers \"" vusers "\"
+
+# this totally works!  run this as user nobody!
+# info about dkimsign ...ing
+# https://openports.pl/path/mail/opensmtpd-filters/dkimsign
+# sudo -u nobody /gnu/store/g17vdv4l03bacn7qbdpb5v8l8vgdxcld-opensmtpd-filter-dkimsign-0.5/libexec/opensmtpd/filter-dkimsign -d gnucode.me -s 2020 -c relaxed/relaxed -k etc-dkimsign-key-file /home/joshua/linode-guix-system-configuration/email-dkim-ssh-keys/20201004-gnucode.me.key user nobody group nogroup
+
+filter \"dkimsign\"  \
+  proc-exec \"" path-to-filter-dkimsign " -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k " etc-dkimsign-key-file ;;(create-etc-dkimsign-key-file)
+  " \"  \
+  user nobody group nogroup
+
+# port 25 is used only for receiving from external servers, and they may start a
+# TLS session if the want.
+listen on eth0 port 25 tls pki smtp.gnucode.me
+
+# For sending messages from outside of this server, you need to authenticate and use
+# TLS
+listen on eth0 port 465 smtps pki smtp.gnucode.me auth <creds> filter \"dkimsign\"
+listen on eth0 port 587 tls-require pki smtp.gnucode.me auth <creds> filter \"dkimsign\"
+
+# users logged-in/ssh-ed into the system can send email
+listen on lo port 25 tls pki smtp.gnucode.me
+
+# receive email action
+action \"receive\" maildir \"/home/%{rcpt.user}/Maildir\" junk virtual <vusers>
+# action send the email to the world
+action \"send\" relay
+
+# We accept to send email from any mail from authenticated users
+match for any from any auth action \"send\"
+
+#finally we receive any incoming email
+# maybe the next \"from any\" should be changed to \"for rdns\".
+match from any for domain <vdoms> action \"receive\"
+match for local action \"receive\""))
+(define-module (opensmtpd)
+  #:use-module (guix gexp)
+  #:use-module (guix records)
+  #:use-module (gnu packages mail) ;;for finding location of filter-dkimsign
+  #:export (
+            %smtpd.conf
+            ))
+
+
+;; to create credentials for now, I need to do the following:
+;;  find /gnu/store -name '*encrypt*' | grep opensmtpd
+;; /gnu/store/blah/opensmtpd/encrypt
+(define creds
+  (plain-file "creds"
+              ;; this is my joshua's password for server.  This can be found on dobby's /home/joshua/.authinfo/
+              "joshua $6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86."))
+
+(define vdoms
+  (plain-file
+   "vdoms"
+   "gnucode.me
+gnu-hurd.com"))
+
+(define vusers
+  (plain-file
+   "vusers"
+   "joshua@gnucode.me  joshua
+jbranso@gnucode.me     joshua
+postmaster@gnucode.me  joshua"))
+
+(define path-to-filter-dkimsign
+  (file-append opensmtpd-filter-dkimsign "/libexec/opensmtpd/filter-dkimsign"))
+
+(define path-to-dkimsign-key (string-append (getcwd) "/email-dkim-ssh-keys/2021-09-22-rsa1024-gnucode.me.key"))
+(define etc-dkimsign-key-file "/etc/opensmtpd/dkimsign/2021-09-22-rsa1024-gnucode.me.key")
+
+;; FIXME:  This should become a derivation.  Currently it just runs when I evaluate
+;; %smtpd.conf.  For example it should look like this?
+;; (define build-exp
+;;   #~(begin
+;;       (mkdir #$output)
+;;       (chdir #$output)
+;;       (symlink (string-append #$coreutils "/bin/ls")
+;;                "list-files")))
+
+;; I will need to extend the opensmtpd service, to create a directory
+;; in etc.  This line needs to be added to etc-service.
+;; (service-extension etc-service-type opensmtpd-etc-service)
+;; I'll then need to create a opensmtpd-etc-service procedure.  ganeti has
+;; a good example.
+
+;; It should also use the /etc service, which is a service for creating
+;; directories and files in /etc ?
+(define (create-etc-dkimsign-key-file)
+  #~(let ([UID-nobody (passwd:uid (getpw "nobody"))]
+          [GID-root (group:gid (getgr "root"))]
+          [GID-nogroup (group:gid (getgr "nogroup"))])
+    ;; #o550 user root can read/open the directory
+    ;; and the group "root" can read/open the directory.
+    ;; change these two lines to (mkdir-p) ?
+      (unless (file-exists? "/etc/opensmtpd")
+        (mkdir "/etc/opensmtpd" #o550))
+
+      ;; root can read/write/execute on directory dkimsign
+      ;; group "root" can read and execute
+      (unless (file-exists? "/etc/opensmtpd/dkimsign")
+        (mkdir "/etc/opensmtpd/dkimsign" #o750))
+
+      (copy-file path-to-dkimsign-key etc-dkimsign-key-file)
+      ;; ;; ;; make the etc-dkimsign-key-file to owned by nobody and group nogroup.
+      (chown "/etc/opensmtpd" UID-nobody GID-root)
+      (chown "/etc/opensmtpd/dkimsign" UID-nobody GID-root)
+      (chown etc-dkimsign-key-file UID-nobody GID-nogroup)
+      etc-dkimsign-key-file))
+
+(define %smtpd.conf
+  (mixed-text-file "smtpd.conf"
+                   "
+# This is the smtpd server system-wide configuration file.
+# See smtpd.conf(5) for more information.
+# borrowed from the archlinux guix
+# https://wiki.archlinux.org/index.php/OpenSMTPD#Simple_OpenSMTPD/mbox_configuration
+
+# My TLS certificate and key
+table aliases file:/etc/aliases
+pki smtp.gnucode.me cert \"/etc/letsencrypt/live/gnucode.me/fullchain.pem\"
+pki smtp.gnucode.me key \"/etc/letsencrypt/live/gnucode.me/privkey.pem\"
+
+# for now I am NOT using the virtual credentials
+# table creds { joshua = $6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86. }
+table creds \"" creds "\"
+table vdoms \"" vdoms "\"
+# table vdoms { gnucode.me, gnu-hurd.com }
+# table vusers { joshua@gnucode.me = joshua, jbranso@gnucode.me = joshua, postmaster@gnucode.me = joshua }
+table vusers \"" vusers "\"
+
+# this totally works!  run this as user nobody!
+# info about dkimsign ...ing
+# https://openports.pl/path/mail/opensmtpd-filters/dkimsign
+# sudo -u nobody /gnu/store/g17vdv4l03bacn7qbdpb5v8l8vgdxcld-opensmtpd-filter-dkimsign-0.5/libexec/opensmtpd/filter-dkimsign -d gnucode.me -s 2020 -c relaxed/relaxed -k etc-dkimsign-key-file /home/joshua/linode-guix-system-configuration/email-dkim-ssh-keys/20201004-gnucode.me.key user nobody group nogroup
+
+filter \"dkimsign\"  \
+  proc-exec \"" path-to-filter-dkimsign " -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k " etc-dkimsign-key-file ;;(create-etc-dkimsign-key-file)
+  " \"  \
+  user nobody group nogroup
+
+# port 25 is used only for receiving from external servers, and they may start a
+# TLS session if the want.
+listen on eth0 port 25 tls pki smtp.gnucode.me
+
+# For sending messages from outside of this server, you need to authenticate and use
+# TLS
+listen on eth0 port 465 smtps pki smtp.gnucode.me auth <creds> filter \"dkimsign\"
+listen on eth0 port 587 tls-require pki smtp.gnucode.me auth <creds> filter \"dkimsign\"
+
+# users logged-in/ssh-ed into the system can send email
+listen on lo port 25 tls pki smtp.gnucode.me
+
+# receive email action
+action \"receive\" maildir \"/home/%{rcpt.user}/Maildir\" junk virtual <vusers>
+# action send the email to the world
+action \"send\" relay
+
+# We accept to send email from any mail from authenticated users
+match for any from any auth action \"send\"
+
+#finally we receive any incoming email
+# maybe the next \"from any\" should be changed to \"for rdns\".
+match from any for domain <vdoms> action \"receive\"
+match for local action \"receive\""))
+
+#+END_SRC
+**** TODO what does rewrite needs value mean?  Should it be a number?  this is for =<opensmtpd-filter-phase-configuration>=
+from the documentation
+
+rewrite value            the command parameter is rewritten with value
+**** DONE sanitize the <opensmtpd-listen-on-socket-configuration> fieldname 'filters'.
+
+I can probably reuse existing code from the sanitize procedure found in
+=<opensmtpd-listen-on-configuration>= fieldname 'filters'.
+**** DONE write a get-opensmtpd-filters procedure
+
+This procedure takes all the values of <opensmtpd-listen-on-configuration> fieldname 'filters'
+and <opensmtpd-listen-on-socket-configuration> fieldname 'filters'.  It returns a list of
+<opensmtpd-filter-configuration>, <opensmtpd-filter-phase-configuration>, and filter-chains, which is a list
+of <opensmtpd-filter-configuration> and <opensmtpd-filter-phase-configuration>.  An example of what this
+might return is:
+
+#+BEGIN_SRC scheme
+(list (list (opensmtpd-filter))
+      (list (opensmtpd-filter-phase-configuration))
+      (list (opensmtpd-filter) ; this list is a filter-chain
+            (opensmtpd-filter-phase-configuration))
+      (list (opensmtpd-filter-phase-configuration) ; this list is also a filter chain
+            (opensmtpd-filter)
+            (opensmtpd-filter)))
+#+END_SRC
+
+These are some example bits of code that I can test my resulting code on.
+
+All unique filters of <opensmtpd-filter-configuration> and <opensmtpd-filter-phase-configuration>.  4 of
+them.  One listen-on has no filters.
+#+BEGIN_SRC scheme
+(let ([interface "lo"]
+      [filter-dkimsign (opensmtpd-filter
+                        (name "dkimsign")
+                        (exec #t)
+                        (proc (string-append "/path/to/dkimsign  -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k "
+                                             "/path/to/dkimsign-key user nobody group nobody")))])
+  (opensmtpd-configuration
+   (listen-on-socket
+    (opensmtpd-listen-on-socket-configuration-configuration
+     (filters (list (opensmtpd-filter
+                     (name "rspamd")
+                     (proc "rspamd"))))))
+   (listen-ons
+    (list
+     (opensmtpd-listen-on-configuration
+      (interface interface)
+      (filters (list (opensmtpd-filter-phase-configuration
+                      (name "noFRDNS")
+                      (phase "commit")
+                      (options (list (opensmtpd-option-configuration
+                                         (option "fcrdns")
+                                         (not #t))))
+                      (decision "disconnect")
+                      (message "No FCRDNS")))))
+     ;; this lets local users logged into the system via ssh send email
+     (opensmtpd-listen-on
+      (interface interface)
+      (port 27)
+      (filters (list filter-dkimsign)))
+     (opensmtpd-listen-on-configuration
+      (port 29)
+      (interface interface))
+     (opensmtpd-listen-on-configuration
+      (interface interface)
+      (filters (list (opensmtpd-filter
+                      (name "rspamd")
+                      (proc "rspamd")
+                      (exec #t)))))))))
+#+END_SRC
+
+4 unique filters.  One of the filters is a filter chain.
+#+BEGIN_SRC scheme
+(let ([interface "lo"]
+      [filter-dkimsign (opensmtpd-filter
+                        (name "dkimsign")
+                        (exec #t)
+                        (proc (string-append "/path/to/dkimsign  -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k "
+                                             "/path/to/dkimsign-key user nobody group nobody")))])
+  (opensmtpd-configuration
+   (listen-on-socket
+    (opensmtpd-listen-on-socket-configuration-configuration
+     (filters (list (opensmtpd-filter
+                     (name "spamassassain")
+                     (proc "spamassassain"))
+                   (opensmtpd-filter-phase-configuration
+                      (name "rdns")
+                      (phase "data")
+                      (options (list
+                                   (opensmtpd-option-configuration
+                                    (option "rdns")
+                                    (not #t))))
+                      (decision "reject")
+                      (message "No RDNS"))
+                    (opensmtpd-filter
+                     (name "block")
+                     (proc "block"))
+                    (opensmtpd-filter-phase-configuration
+                     (name "auth")
+                     (phase "commit")
+                     (options (list
+                                  (opensmtpd-option-configuration
+                                   (option "auth")
+                                   (regex #t)
+                                   (not #t)
+                                   (table (opensmtpd-table-configuration
+                                           (name "auth-table")
+                                           (data (list ".*@gmail.com"
+                                                         ".*@dismail.de")))))))
+                     (decision "junk"))))))
+   (listen-ons
+    (list
+     (opensmtpd-listen-on-configuration
+      (interface interface)
+      (filters (list (opensmtpd-filter-phase-configuration
+                      (name "noFRDNS")
+                      (phase "commit")
+                      (options (list (opensmtpd-option-configuration
+                                         (option "fcrdns")
+                                         (not #t))))
+                      (decision "disconnect")
+                      (message "No FCRDNS")))))
+     ;; this lets local users logged into the system via ssh send email
+     (opensmtpd-listen-on-configuration
+      (interface interface)
+      (port 27)
+      (filters (list filter-dkimsign)))
+     (opensmtpd-listen-on-configuration
+      (port 29)
+      (interface interface))
+     (opensmtpd-listen-on-configuration
+      (interface interface)
+      (filters (list (opensmtpd-filter
+                      (name "rspamd")
+                      (proc "rspamd")
+                      (exec #t)))))))))
+#+END_SRC
+
+No filters at all.
+#+BEGIN_SRC scheme
+(get-opensmtpd-filters (opensmtpd-configuration)) ; no filters at all.
+#+END_SRC
+
+This one prints rspamd twice!  The get-opensmtpd-filters procedure returns a
+duplicate filter.  While get-opensmtpd-filters does return a duplicate filter.
+#+BEGIN_SRC scheme
+(opensmtpd-configuration
+ (listen-on-socket
+  (opensmtpd-listen-on-configuration-socket-configuration
+   (filters
+    (list (opensmtpd-filter
+           (name "rando")
+           (proc "rando"))))))
+ (listen-ons
+  (list
+   (opensmtpd-listen-on-configuration
+    (port 25)
+    (filters (list (opensmtpd-filter-phase-configuration
+                    (name "noFRDNS")
+                    (phase "commit")
+                    (options (list (opensmtpd-option-configuration
+                                       (option "fcrdns")
+                                       (not #t))))
+                    (decision "disconnect")
+                    (message "No FCRDNS"))
+                   (opensmtpd-filter
+                    (name "rspamd")
+                    (proc "rspamd")))))
+   (opensmtpd-listen-on-configuration
+    (port 465)
+    (filters (list
+              (opensmtpd-filter
+               (name "rspamd")
+               (proc "rspamd"))
+              (opensmtpd-filter
+               (name "block")
+               (proc "block")))))
+   (opensmtpd-listen-on-configuration
+    (port 587))
+   (opensmtpd-listen-on-configuration
+    (port 999)
+    (filters (list
+              (opensmtpd-filter
+               (name "bogofilter")
+               (proc "bogofilter"))))))))
+#+END_SRC
+
+#+BEGIN_SRC scheme
+(opensmtpd-configuration
+ (listen-ons
+  (list
+   (opensmtpd-listen-on-configuration
+    (filters (list (opensmtpd-filter
+                    (name "bogofilter")
+                    (proc "bogofilter")))))
+   (opensmtpd-listen-on-configuration
+    (filters (list (opensmtpd-filter
+                    (name "noFRDNS")
+                    (proc "noFRDNS"))
+                   (opensmtpd-filter
+                    (name "rspamd")
+                    (proc "rspamd"))))))))
+#+END_SRC
+**** PROJ by which method should I turn the filters in =<opensmtpd-configuration>= into strings? [1/3]
+
+in (opensmtpd-configuration->mixed-text-file ) do either
+
+1. one line of code. faster, but violates the convention set by the other lines
+   of code around it.
+    #+BEGIN_SRC scheme
+   ;; write out all the filters
+   (opensmtpd-filters->string (get-opensmtpd-filters record))
+    #+END_SRC
+
+    This task in done: [[*(opensmtpd-filters->string (get-opensmtpd-filters record))][(opensmtpd-filters->string (get-opensmtpd-filters record))]]
+2. two lines of code. slower, but follows the convention set by the other lines
+   of code around it.
+    #+BEGIN_SRC scheme
+   (opensmtpd-configuration-fieldname->string record get-opensmtpd-filters-and-filter-phases opensmtpd-filter-and-filter-phase->string)
+   (opensmtpd-configuration-fieldname->string record get-opensmtpd-filter-chains opensmtpd-filter-chain->string)
+    #+END_SRC
+
+***** DONE (opensmtpd-filters->string (get-opensmtpd-filters record))
+
+Have one procedure that prints out all filters.
+
+ #+BEGIN_SRC scheme
+;; write out all the filters
+(opensmtpd-filters->string (get-opensmtpd-filters record))
+ #+END_SRC
+
+***** PROJ 4 procedures: get-filter-and-filter-phases, filter-and-filter-phases->string, get-filter-chains, filter-chains->string
+
+The bonus with this method is that I can add these two lines in
+opensmtpd-configuration->mixed-text-file and keep a consistent coding framework:
+
+#+BEGIN_SRC scheme
+;; write out all the filters and filter-phases
+(opensmtpd-configuration-fieldname->string record get-opensmtpd-filters-and-filter-phases opensmtpd-filter-and-filter-phase->string)
+;; write out all the filter chains
+(opensmtpd-configuration-fieldname->string record get-opensmtpd-filter-chains opensmtpd-filter-chain->string)
+#+END_SRC
+**** DONE fix the sanitize procedure for =<opensmtpd-filter-phase-configuration>= fieldnames 'phase-name', 'decision', etc. [5/5]
+***** DONE sanitize =<opensmtpd-filter-phase-configuration>= so that fieldname 'decision' option "reject" and "disconnect" requires a 'message'.
+:LOGBOOK:
+CLOCK: [2022-04-01 Fri 22:45]--[2022-04-02 Sat 04:13] =>  5:28
+:END:
+
+This message must be RFC compliant.  The message must start with 4xx or 5xx
+status code.
+
+#+BEGIN_SRC scheme
+(opensmtpd-listen-on-configuration
+ (filters (list
+           (opensmtpd-filter-phase-configuration
+            (name "junk")
+            (phase "connect")
+            (decision "junk")
+            (options
+             (list
+              (opensmtpd-option-configuration
+               (option "rdns")))))
+           (opensmtpd-filter-phase-configuration
+            (name "src")
+            (phase "connect")
+            (options
+             (list
+              (opensmtpd-option-configuration
+               (option "src")
+               (data (opensmtpd-table-configuration
+                       (name "src-table")
+                       (data (list "cat" "hat")))))))
+            (decision "reject")))))
+#+END_SRC
+
+#+RESULTS:
+
+#+BEGIN_EXAMPLE
+<opensmtpd-filter-phase-configuration> fieldname: 'decision' options "disconnect" and "reject" require fieldname 'message'
+to have a string.
+ice-9/boot-9.scm:1685:16: In procedure raise-exception:
+Throw to key `bad!' with args `(#<<opensmtpd-filter-phase-configuration> name: "src" phase: "connect" options: (#<<opensmtpd-option-configuration> option: "src" not: #f regex: #f table: #<<opensmtpd-table-configuration> name: "src-table" file-db: #f values: ("cat" "hat") type: #<procedure 435f560 at <unknown port>:5894:22 (x)>>>) decision: "reject" message: #f value: #f>)'.
+
+Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
+#+END_EXAMPLE
+
+#+BEGIN_SRC scheme
+(opensmtpd-listen-on-configuration
+ (filters (list
+           (opensmtpd-filter-phase-configuration
+            (name "junk")
+            (phase "connect")
+            (decision "junk")
+            (options
+             (list
+              (opensmtpd-option-configuration
+               (option "rdns")))))
+           (opensmtpd-filter-phase-configuration
+            (name "src")
+            (phase "connect")
+            (options
+             (list
+              (opensmtpd-option-configuration
+               (option "src")
+               (data (opensmtpd-table-configuration
+                       (name "src-table")
+                       (data (list "cat" "hat")))))))
+            (decision "reject")
+            (message "322 Hello")))))
+<opensmtpd-filter-phase-configuration> fieldname: 'decision' options
+"disconnect" and "reject" require fieldname 'message' to have a string.
+The 'message' string must be RFC commpliant, which means that the string
+must begin with a 4xx or 5xx status code.
+ice-9/boot-9.scm:1685:16: In procedure raise-exception:
+Throw to key `bad!' with args `(#<<opensmtpd-filter-phase-configuration> name: "src" phase: "connect" options: (#<<opensmtpd-option-configuration> option: "src" not: #f regex: #f data: #<<opensmtpd-table-configuration> name: "src-table" file-db: #f data: ("cat" "hat") type: #<procedure 44d2140 at <unknown port>:1153:21 (x)>>>) decision: "reject" message: "322 Hello" value: #f>)'.
+
+Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
+#+END_SRC
+***** DONE sanitize =<opensmtpd-filter-phase-configuration>= so that fieldname 'decision' option "rewrite" requires a 'value'.
+:LOGBOOK:
+CLOCK: [2022-04-01 Fri 22:45]--[2022-04-02 Sat 04:13] =>  5:28
+:END:
+
+#+BEGIN_SRC scheme
+(opensmtpd-listen-on-configuration
+ (filters
+  (list
+   (opensmtpd-filter-phase-configuration
+    (name "noFRDNS")
+    (phase "commit")
+    (options (list (opensmtpd-option-configuration
+                    (option "fcrdns")
+                    (not #t))))
+    (value #f)
+    (decision "rewrite")) ;; there needs to be a value here.  rewrite requires a value!
+   )))
+$12 = #<<opensmtpd-listen-on-configuration> interface: "lo" family: #f auth: #f auth-optional: #f filters: (#<<opensmtpd-filter-phase-configuration> name: "noFRDNS" phase: "commit" options: (#<<opensmtpd-option-configuration> option: "fcrdns" not: #t regex: #f data: #f>) decision: "rewrite" message: #f value: 343>) hostname: #f hostnames: #f mask-src: #f disable-dsn: #f pki: #f port: #f proxy-v2: #f received-auth: #f secure-connection: #f tag: #f>
+scheme@(opensmtpd-records) [10]> (opensmtpd-listen-on-configuration
+ (filters
+  (list
+   (opensmtpd-filter-phase-configuration
+    (name "noFRDNS")
+    (phase "commit")
+    (options (list (opensmtpd-option-configuration
+                    (option "fcrdns")
+                    (not #t))))
+    (decision "rewrite")) ;; there needs to be a value here.  rewrite requires a value!
+   )))
+<opensmtpd-filter-phase-configuration> fieldname: 'decision' option
+"rewrite" requires fieldname 'value'
+to have a number.
+ice-9/boot-9.scm:1685:16: In procedure raise-exception:
+Throw to key `bad!' with args `(#<<opensmtpd-filter-phase-configuration> name: "noFRDNS" phase: "commit" options: (#<<opensmtpd-option-configuration> option: "fcrdns" not: #t regex: #f data: #f>) decision: "rewrite" message: #f value: #f>)'.
+
+Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
+#+END_SRC
+
+***** DONE sanitize =<opensmtpd-filter-phase-configuration>= so that fieldname 'decision' option "junk" and "bypass" have no message or value
+
+#+BEGIN_SRC scheme
+(opensmtpd-listen-on-configuration
+ (filters
+  (list
+   (opensmtpd-filter-phase-configuration
+    (name "noFRDNS")
+    (phase "commit")
+    (options (list (opensmtpd-option-configuration
+                    (option "fcrdns")
+                    )))
+    (decision "junk")
+    (message "This is not a good email."))))) ; there should NOT be a message here!  junk has no message.
+#+END_SRC
+***** DONE sanitize the options too.  rdns requires a table for instance:
+
+#+BEGIN_EXAMPLE
+At each phase, various conditions may be matched.  The fcrdns, rdns, and src data are
+available in all phases, but other data must have been already submitted before they are
+available.
+
+      fcrdns                   forward-confirmed reverse DNS is valid
+      rdns                     session has a reverse DNS
+      rdns <table>             session has a reverse DNS in table
+      src <table>              source address is in table
+      helo <table>             helo name is in table
+      auth                     session is authenticated
+      auth <table>             session username is in table
+      mail-from <table>        sender address is in table
+      rcpt-to <table>          recipient address is in table
+#+END_EXAMPLE
+***** DONE sanitize <opensmtpd-filter-phase-configuration> make sure that 'junking happens before phase 'committed'.
+#+BEGIN_EXAMPLE
+     Descisions can be taken at any phase, though junking can only happen before a message is committed.
+#+END_EXAMPLE
+
+#+BEGIN_SRC scheme
+(opensmtpd-listen-on-configuration
+ (filters
+  (list
+   (opensmtpd-filter-phase-configuration
+    (name "junk-after-commit")
+    (phase "commit")
+    (options (list (opensmtpd-option-configuration
+                    (option "fcrdns")
+                    )))
+    (decision "junk")))))
+#+END_SRC
+*** PROJ make fieldnames that need a table accept a value of table [3/4]
+**** DONE opensmtpd-action-local-delivery-configuration [3/3]
+***** DONE alias =<alias>= [2/2]
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 03:53]
+:END:
+****** DONE change the sanitize portion of the fieldname alias in the <opensmtpd-action-local-delivery-configuration>
+
+The below code does work in a REPL.
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+(opensmtpd-action-local-delivery-configuration
+ (alias
+  (opensmtpd-table-configuration
+   (name "My-table")
+   (data (list "gnu-hurd.com" "gnucode.me")))))
+#+END_SRC
+
+#+RESULTS:
+****** DONE change relevant portions in opensmtpd-action-local-delivery-configuration->string
+This bit of code works in the repl too!
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+((@@ (opensmtpd-records) opensmtpd-action-local-delivery-configuration->string)
+ (opensmtpd-action-local-delivery-configuration
+  (alias
+   (opensmtpd-table-configuration
+    (name "My-table")
+    (data (list "gnu-hurd.com" "gnucode.me"))))))
+#+END_SRC
+
+***** DONE userbase =<users>= [2/2]
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 03:53]
+:END:
+
+****** DONE change the sanitize portion of the fieldname userbase in the <opensmtpd-action-local-delivery-configuration>
+
+The below code does work in a REPL.
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+(opensmtpd-action-local-delivery-configuration
+ (userbase
+  (opensmtpd-table-configuration
+   (name "this")
+   (data (list "job" "done")))))
+#+END_SRC
+
+#+RESULTS:
+****** DONE change relevant portions in opensmtpd-action-local-delivery-configuration->string
+This bit of code works in the repl too!
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+((@@ (opensmtpd-records) opensmtpd-action-local-delivery-configuration->string)
+ (opensmtpd-action-local-delivery-configuration
+  (userbase
+   (opensmtpd-table-configuration
+    (name "this")
+    (data (list "job" "done"))))))
+#+END_SRC
+
+***** DONE virtual [2/2]
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 03:53]
+:END:
+****** DONE change the sanitize portion of the fieldname virtual in the <opensmtpd-action-local-delivery-configuration>
+
+The below code does work in a REPL.
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+(opensmtpd-action-local-delivery-configuration
+ (virtual
+  (opensmtpd-table-configuration
+   (name "this")
+   (data (list "job" "done")))))
+#+END_SRC
+
+#+RESULTS:
+****** DONE change relevant portions in opensmtpd-action-local-delivery-configuration->string
+This bit of code works in the repl too!
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+((@@ (opensmtpd-records) opensmtpd-action-local-delivery-configuration->string)
+ (opensmtpd-action-local-delivery-configuration
+  (virtual
+   (opensmtpd-table-configuration
+    (name "this")
+    (data (list "job" "done"))))))
+
+#+END_SRC
+
+**** DONE opensmtpd-relay-configuration  [4/4]
+***** DONE helo-src <helos>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:00]
+:END:
+***** DONE domain =<domains>= [2/2]
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:00]
+:END:
+
+****** DONE change the sanitize portion of the fieldname domain in the <opensmtpd-action-relay-configuration>
+
+The below code does work in a REPL.
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+(opensmtpd-action-relay-configuration
+                           (domain
+                            (opensmtpd-table-configuration
+                             (name "this")
+                             (data (list "gnucode.me" "gnu-hurd.com")))))
+
+#+END_SRC
+
+#+RESULTS:
+****** DONE change relevant portions in opensmtpd-action-relay-configuration->string
+This bit of code works in the repl too!
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+((@@ (opensmtpd-records) opensmtpd-action-relay-configuration->string)
+                          (opensmtpd-action-relay-configuration
+                           (domain
+                            (opensmtpd-table-configuration
+                             (name "this")
+                             (data (list "gnucode.me" "gnu-hurd.com"))))))
+
+
+#+END_SRC
+
+***** DONE auth =<credentials>= [2/2]
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:00]
+:END:
+****** DONE change the sanitize portion of the fieldname =<auth>= in the <opensmtpd-action-relay-configuration>
+
+The below code does work in a REPL.
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+(opensmtpd-action-relay-configuration
+                           (auth
+                            (opensmtpd-table-configuration
+                             (name "this")
+                             (data (list "gnucode.me" "gnu-hurd.com")))))
+
+#+END_SRC
+
+#+RESULTS:
+****** DONE change relevant portions in opensmtpd-action-relay-configuration->string
+This bit of code works in the repl too!
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+((@@ (opensmtpd-records) opensmtpd-action-relay-configuration->string)
+                          (opensmtpd-action-relay-configuration
+                           (auth
+                            (opensmtpd-table-configuration
+                             (name "this")
+                             (data (list "gnucode.me" "gnu-hurd.com"))))))
+
+
+#+END_SRC
+
+***** DONE src srcaddress | <IP addresses>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:01]
+:END:
+****** DONE change the sanitize portion of the fieldname =<auth>= in the <opensmtpd-action-relay-configuration>
+
+The below code does work in a REPL.
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+(opensmtpd-action-relay-configuration
+                           (src
+                            (opensmtpd-table-configuration
+                             (name "this")
+                             (data (list "gnucode.me" "gnu-hurd.com")))))
+
+#+END_SRC
+
+#+RESULTS:
+****** DONE change relevant portions in opensmtpd-action-relay-configuration->string
+This bit of code works in the repl too!
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+((@@ (opensmtpd-records) opensmtpd-action-relay-configuration->string)
+                          (opensmtpd-action-relay-configuration
+                           (src
+                            (opensmtpd-table-configuration
+                             (name "this")
+                             (data (list "gnucode.me" "gnu-hurd.com"))))))
+
+
+#+END_SRC
+
+Use the string scraddress or list table <IP addresses> for the source IP address.
+**** DONE opensmtpd-listen-on-configuration [3/3]
+***** DONE auth =<credentials>= [3/3]
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:01]
+:END:
+****** DONE change the sanitize portion of the fieldname 'auth' in the <opensmtpd-listen-on-configuration>
+
+The below code does work in a REPL.
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+(opensmtpd-listen-on-configuration
+ (auth
+  (opensmtpd-table-configuration
+   (name "My-table")
+   (data '(("joshua" . "$some$Long$EncrytpedPassword"))))))
+#+END_SRC
+
+#+RESULTS:
+****** DONE change relevant portions in opensmtpd-listen-on-configuration->string
+This bit of code works in the repl too!
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+((@@ (opensmtpd-records) opensmtpd-listen-on-configuration->string)
+ (opensmtpd-listen-on-configuration
+  (auth
+   (opensmtpd-table-configuration
+    (name "credentials")
+    (data '(("joshua" . "$someLongEncrytpedPassword")))))))
+#+END_SRC
+
+****** DONE sanitize the =<opensmtpd-table-configuration>= so that it can only be an opensmtpd-table-configuration, whose fieldname values are an assoc-list
+
+#+BEGIN_SRC scheme
+(opensmtpd-listen-on-configuration (auth (opensmtpd-table-configuration (name "the") (data (list "the" "cat")))))
+#+END_SRC
+
+#+RESULTS: =<opensmtpd-listen-on-configuration>= fieldname: 'auth' is of type boolean, or an =<opensmtpd-table-configuration>= record whose fieldname 'values' are an assoc-list.
+
+***** DONE auth-optional =<credentials>= [2/2]
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:01]
+:END:
+****** DONE change the sanitize portion of the fieldname 'auth-optional' in the <opensmtpd-listen-on-configuration>
+
+The below code does work in a REPL.
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+(opensmtpd-listen-on-configuration
+ (auth
+  (opensmtpd-table-configuration
+   (name "My-table")
+   (data '(("joshua" . "$some$Long$EncrytpedPassword"))))))
+#+END_SRC
+
+#+RESULTS:
+
+AND the below code will correctly result in an error!
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+(opensmtpd-listen-on-configuration
+ (auth
+  (opensmtpd-table-configuration
+   (name "My-table")
+   (data '("joshua" "$some$Long$EncrytpedPassword")))))
+#+END_SRC
+
+#+RESULTS:
+: =<opensmtpd-listen-on-configuration>= fieldname: 'auth' is of type boolean, or an =<opensmtpd-table-configuration>= record whose fieldname 'values' are an assoc-list
+: (eg: (opensmtpd-table-configuration (name "table") (data '("joshua" . "$encrypted$password")))).
+
+****** DONE change relevant portions in opensmtpd-listen-on-configuration->string
+This bit of code works in the repl too!
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+((@@ (opensmtpd-records) opensmtpd-listen-on-configuration->string)
+ (opensmtpd-listen-on-configuration
+  (auth
+   (opensmtpd-table-configuration
+    (name "credentials")
+    (data '(("joshua" . "$someLongEncrytpedPassword")))))))
+#+END_SRC
+
+***** DONE hostnames =<hostnames>= [2/2]
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:01]
+:END:
+****** DONE change the sanitize portion of the fieldname 'hostnames' in the <opensmtpd-listen-on-configuration>
+
+The below code does work in a REPL.
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+(opensmtpd-listen-on-configuration
+ (hostnames
+  (opensmtpd-table-configuration
+   (name "My-table")
+   (data '(("joshua" . "$some$Long$EncrytpedPassword"))))))
+#+END_SRC
+
+#+RESULTS:
+
+AND the below code will correctly result in an error!
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+(opensmtpd-listen-on-configuration
+ (hostnames
+  (opensmtpd-table-configuration
+   (name "My-table")
+   (data '("joshua" "$some$Long$EncrytpedPassword")))))
+#+END_SRC
+
+#+RESULTS:
+: =<opensmtpd-listen-on-configuration>= fieldname: 'hostname' is of type boolean, or an =<opensmtpd-table-configuration>= record whose fieldname 'values' are an assoc-list
+: (eg: (opensmtpd-table-configuration (name "table") (data '("joshua" . "$encrypted$password")))).
+
+****** DONE change relevant portions in opensmtpd-listen-on-configuration->string
+This bit of code works in the repl too!
+#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/
+(add-to-load-path (dirname (current-filename)))
+(use-modules (opensmtpd-records))
+
+((@@ (opensmtpd-records) opensmtpd-listen-on-configuration->string)
+ (opensmtpd-listen-on-configuration
+  (hostnames
+   (opensmtpd-table-configuration
+    (name "credentials")
+    (data '(("joshua" . "$someLongEncrytpedPassword")))))))
+#+END_SRC
+
+**** TODO opensmtpd-match [20/24]
+******* NO list approach
+Guix probably won't like the list approach.
+#+BEGIN_SRC scheme
+  (openmstpd-match
+   (for
+    (list 'not "for domain regex"
+          (opensmtpd-table-configuration
+           (data (list "gnucode.me" "gnu-hurd.com"))))))
+#+END_SRC
+
+#+BEGIN_SRC scheme
+  (openmstpd-match
+   (for
+    (list "! for domain"
+          (opensmtpd-table-configuration
+           (data (list "gnucode.me" "gnu-hurd.com"))))))
+#+END_SRC
+******* opensmtpd-options-configuration approach  I like this one quite a bit.
+
+This method is a little bit more verbose.  Well I guess it's a lot
+more verbose.  But it's easier for me to properly parse what the user wants.
+
+I would sanitize the options in the opensmtpd-match-configuration-for,
+openmsmtpd-match-from, opensmtpd-match-configuration-auth, opensmtpd-match-configuration-helo,
+opensmtpd-match-configuration-mail-from, opensmtpd-match-configuration-rcpt-to fieldnames.
+********* for
+#+BEGIN_SRC scheme
+  (openmstpd-match
+   (for
+    (opensmtpd-options-configuration
+     (not #t)
+     (method "domain regex") ;; valid options for "for" are "domain" or "domain regex"
+     (opensmtpd-table-configuration
+      (data (list "gnucode.me" "gnu-hurd.com"))))))
+#+END_SRC
+
+Do I want a regex fieldname?  Probably not.  It makes it more verbose...
+
+#+BEGIN_SRC scheme
+  (openmstpd-match
+   (for
+    (opensmtpd-options-configuration
+     (not #t)
+     (regex #t)
+     (method "domain") ;; valid options for "for" are "domain"
+     (opensmtpd-table-configuration
+      (data (list "gnucode.me" "gnu-hurd.com"))))))
+#+END_SRC
+
+********* from
+#+BEGIN_SRC scheme
+  (openmstpd-match
+   (from
+    (opensmtpd-options-configuration
+     (not #t)
+     (method "rdns regex") ;;valid options for from are "auth" "auth regex", "mail-from" "mail-from regex",
+                           ;; "rdns", "rdns regex", "src", "src regex"
+     (opensmtpd-table-configuration
+      (data (list "gnucode.me" "gnu-hurd.com"))))))
+#+END_SRC
+
+Do I want a regex fieldname?
+
+#+BEGIN_SRC scheme
+  (openmstpd-match
+   (from
+    (opensmtpd-options-configuration
+     (not #t)
+     (regex #t)
+     (method "rdns") ;;valid options for from are "auth", "mail-from", "rdns", "src"
+     (opensmtpd-table-configuration
+      (data (list "gnucode.me" "gnu-hurd.com"))))))
+#+END_SRC
+
+********* auth
+#+BEGIN_SRC scheme
+  (openmstpd-match
+   (auth
+    (opensmtpd-options-configuration
+     (not #t)
+     (method "auth regex")
+     (opensmtpd-table-configuration
+      (data (list "gnucode.me" "gnu-hurd.com"))))))
+#+END_SRC
+
+Do I want a regex fieldname?
+
+#+BEGIN_SRC scheme
+  (openmstpd-match
+   (auth
+    (opensmtpd-options-configuration
+     (not #t)
+     (regex #t)
+     (method "auth") ;; valid options for auth are "auth" or this method can be left blank.
+     (opensmtpd-table-configuration
+      (data (list "gnucode.me" "gnu-hurd.com"))))))
+#+END_SRC
+
+********* mail-from
+#+BEGIN_SRC scheme
+  (openmstpd-match
+   (mail-from
+    (opensmtpd-options-configuration
+     (not #t)
+     (method "mail from")
+     (opensmtpd-table-configuration
+      (data (list "gnucode.me" "gnu-hurd.com"))))))
+#+END_SRC
+****** I tweak opensmtpd-match record and add a opensmtpd-options
+
+#+BEGIN_SRC scheme
+  (opensmtpd-match-configuration
+   (name "action-name")
+   (options
+    (list
+     (opensmtpd-options-configuration
+      (method "for domain regex")))
+    ))
+#+END_SRC
+
+****** PROJ many of these options are not completely sanitized.
+
+For example: "for domain" requires a domain | <domain> BUT this record, which
+does not have a domain gives no errors:
+
+#+BEGIN_SRC scheme
+(opensmtpd-match-configuration
+ (for (opensmtpd-option-configuration
+       (option "for domain")))
+ (action (opensmtpd-action-local-delivery-configuration
+          (name "local") )))
+#+END_SRC
+
+And there are a ton of other examples of this.
+****** DONE for domain <domains>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:17]
+:END:
+
+The datastructures work:
+
+#+BEGIN_SRC scheme
+(opensmtpd-match-configuration
+ (name "local")
+ (for (opensmtpd-option-configuration
+       (option "for domain")
+       (value (opensmtpd-table-configuration
+               (name "this")
+               (data (list "helo" "hello"))))))
+ (action (opensmtpd-action-local-delivery-configuration)))
+#+END_SRC
+
+#Results
+: $4 = #<<opensmtpd-match-configuration> name: "local" action: #<<opensmtpd-action-local-delivery-configuration> method: "mbox" alias: #f ttl: #f user: #f userbase: #f virtual: #f wrapper: #f> for: #<<opensmtpd-option-configuration> option: "for domain" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "this" file-db: #f values: ("helo" "hello") type: #<procedure 2b33238 at <unknown port>:148:97 (x)>>> from: #f auth: #f helo: #f mail-from: #f rcpt-to: #f tag: #f tls: #f>
+
+****** DONE for domain regexp <domains>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:19]
+:END:
+
+the datastructure works
+#+BEGIN_SRC scheme
+(opensmtpd-match-configuration
+ (name "local")
+ (for (opensmtpd-option-configuration
+       (regex #t)
+       (option "for domain")
+       (value (opensmtpd-table-configuration
+               (name "this")
+               (data (list "helo" "hello"))))))
+ (action (opensmtpd-action-local-delivery-configuration)))
+#+END_SRC
+
+#Results
+: $4 = #<<opensmtpd-match-configuration> name: "local" action: #<<opensmtpd-action-local-delivery-configuration> method: "mbox" alias: #f ttl: #f user: #f userbase: #f virtual: #f wrapper: #f> for: #<<opensmtpd-option-configuration> option: "for domain" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "this" file-db: #f values: ("helo" "hello") type: #<procedure 2b33238 at <unknown port>:148:97 (x)>>> from: #f auth: #f helo: #f mail-from: #f rcpt-to: #f tag: #f tls: #f>
+
+****** DONE make opensmtpd-match-configuration->string work print for rcpt the appropriate match lines if some values now accept <opensmtpd-table-configuration>
+
+Seems to work:
+
+#+BEGIN_SRC scheme
+(opensmtpd-match-configuration->string (opensmtpd-match-configuration
+ (name "local")
+ (for (opensmtpd-option-configuration
+       (regex #t)
+       (option "for domain")
+       (value (opensmtpd-table-configuration
+               (name "this")
+               (data (list "helo" "hello"))))))
+ (action (opensmtpd-action-local-delivery-configuration))))
+$6 = "match for domain regex =<this>= action \"local\" \n"
+#+END_SRC
+
+also seems to work
+
+#+BEGIN_SRC scheme
+(opensmtpd-match-configuration->string (opensmtpd-match-configuration
+ (name "local")
+ (for (opensmtpd-option-configuration
+       (option "for domain")
+       (value (opensmtpd-table-configuration
+               (name "this")
+               (data (list "helo" "hello"))))))
+ (action (opensmtpd-action-local-delivery-configuration))))
+$7 = "match for domain =<this>= action \"local\" \n"
+#+END_SRC
+****** DONE for rcpt <receipt>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:19]
+:END:
+****** DONE for rcpt regexp <recepit>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:19]
+:END:
+****** DONE from auth user | <user>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:19]
+:END:
+
+#+BEGIN_SRC scheme
+(opensmtpd-option-configuration
+       (regex #t)
+       (option "from auth")
+       (value (opensmtpd-table-configuration
+               (name "this")
+               (data (list "helo" "hello")))))
+$8 = #<<opensmtpd-option-configuration> option: "from auth" not: #f regex: #t value: #<<opensmtpd-table-configuration> name: "this" file-db: #f values: ("helo" "hello") type: #<procedure 2bfa848 at <unknown port>:224:14 (x)>>>
+
+#+END_SRC
+****** DONE from auth regex user | <user>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:20]
+:END:
+****** DONE make sure opensmtpd-option-configuration->string works for from auth if they use <opensmtpd-table-configuration>
+
+#+BEGIN_SRC scheme
+(opensmtpd-option-configuration->string
+ (opensmtpd-option-configuration
+  (regex #t)
+  (option "from auth")
+  (value (opensmtpd-table-configuration
+          (name "this")
+          (data (list "helo" "hello"))))))
+$10 = "from auth regex =<this>= "
+#+END_SRC
+****** DONE from mail-from sender | <sender>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:20]
+:END:
+****** DONE from mail-from regexp <sender>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:20]
+:END:
+****** DONE from rdns <hostname>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:20]
+:END:
+****** DONE from rdns regex <hostname>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:20]
+:END:
+****** DONE from src <address>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:21]
+:END:
+****** DONE from src regex <address>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:21]
+:END:
+****** TODO do some more sanitizing of these last couple of options
+There may be some way to specify invalid data.  For example:
+
+tls does not support regex, not, or value fields.  The below code should be an error.
+#+BEGIN_SRC scheme
+ (display (opensmtpd-match-configuration->string (opensmtpd-match-configuration
+ (tls (opensmtpd-option-configuration
+        (option "tls") ;; this should be auth!!! NOT "helo"
+        (regex #t)
+        (not #t)
+        (value (opensmtpd-table-configuration  (name "mytable")
+                                 (data (list "cat" "kitten"))))))
+ (from (opensmtpd-option-configuration (option "from rdns")
+                                             (value (opensmtpd-table-configuration (name "table")
+                                                                     (data (list "cat" "hat"))))))
+ (action (opensmtpd-action-local-delivery-configuration
+          (name "matches"))))))
+match from rdns <table> ! tls regex <mytable> action "matches"
+#+END_SRC
+
+****** DONE auth <username>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:21]
+:END:
+
+Well I need to fix this bug.  Basically, I can make an
+<opensmtpd-option-configuration> with a table of values for 'option' "auth". And
+I can print that table with (opensmtpd-option-configuration->string)...That works
+fine...
+
+But if I put that same record into an =<opensmtpd-match-configuration>= ...for some reason that
+'auth' table is not being printed.
+
+#+BEGIN_SRC scheme
+(opensmtpd-option-configuration
+                                 (option "auth")
+                                 (value (opensmtpd-table-configuration (name "mytable")
+                                                         (data (list "cat" "hat")))))
+$20 = #<<opensmtpd-option-configuration> option: "auth" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "mytable" file-db: #f values: ("cat" "hat") type: #<procedure 1ee6ce8 at <unknown port>:860:40 (x)>>>
+scheme@(opensmtpd-records) [4]> (opensmtpd-option-configuration->string $20)
+$21 = "auth =<mytable>= "
+scheme@(opensmtpd-records) [4]> (opensmtpd-match (name "matches")
+                                                 (auth (opensmtpd-option-configuration
+                                                        (option "auth")
+                                                        (value (opensmtpd-table-configuration  (name "mytable")
+                                                                                 (data (list "cat" "kitten"))))))
+                                             (from (opensmtpd-option-configuration (option "from rdns")
+                                                                                  (value (opensmtpd-table-configuration (name "table")
+                                                                                                          (data (list "cat" "hat"))))))
+                                             (action (opensmtpd-action-local-delivery-configuration)))
+$22 = #<<opensmtpd-match-configuration> name: "matches" action: #<<opensmtpd-action-local-delivery-configuration> method: "mbox" alias: #f ttl: #f user: #f userbase: #f virtual: #f wrapper: #f> for: #f from: #<<opensmtpd-option-configuration> option: "from rdns" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "table" file-db: #f values: ("cat" "hat") type: #<procedure 30bac20 at <unknown port>:876:89 (x)>>> auth: #<<opensmtpd-option-configuration> option: "auth" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "mytable" file-db: #f values: ("cat" "kitten") type: #<procedure 30baa80 at <unknown port>:873:63 (x)>>> helo: #f mail-from: #f rcpt-to: #f tag: #f tls: #f>
+scheme@(opensmtpd-records) [5]> (opensmtpd-match-configuration->string $22)
+$23 = "match from rdns =<table>= auth action \"matches\" \n"  ;; THERE IS SUPPOSED TO BE a "auth <mytable>" here
+scheme@(opensmtpd-records) [5]>
+#+END_SRC
+****** TODO [!] auth
+****** TODO auth regex <username>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:21]
+:END:
+
+This does NOT show the regex for the auth option.  or the table <mytable> why?
+#+BEGIN_SRC scheme
+(opensmtpd-match-configuration
+ (auth (opensmtpd-option-configuration
+        (option "auth") ;; this should be auth!!! NOT "helo"
+        (regex #t)
+        (value (opensmtpd-table-configuration  (name "mytable")
+                                 (data (list "cat" "kitten"))))))
+ (from (opensmtpd-option-configuration (option "from rdns")
+                                             (value (opensmtpd-table-configuration (name "table")
+                                                                     (data (list "cat" "hat"))))))
+ (action (opensmtpd-action-local-delivery-configuration
+          (name "matches"))))
+$9 = #<<opensmtpd-match-configuration> action: #<<opensmtpd-action-local-delivery-configuration> name: "matches" method: "mbox" alias: #f ttl: #f user: #f userbase: #f virtual: #f wrapper: #f> for: #f from: #<<opensmtpd-option-configuration> option: "from rdns" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "table" file-db: #f values: ("cat" "hat") type: #<procedure 1fb5fc0 at <unknown port>:144:52 (x)>>> auth: #<<opensmtpd-option-configuration> option: "auth" not: #f regex: #t value: #<<opensmtpd-table-configuration> name: "mytable" file-db: #f values: ("cat" "kitten") type: #<procedure 1fb5c40 at <unknown port>:141:15 (x)>>> helo: #f mail-from: #f rcpt-to: #f tag: #f tls: #f>
+scheme@(opensmtpd-records) [4]> (display (opensmtpd-match-configuration->string $9))
+match from rdns <table> auth action "matches" ;; there should be a regex in there.
+#+END_SRC
+****** DONE helo <helo-name>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:21]
+:END:
+#+BEGIN_SRC scheme
+(opensmtpd-match-configuration
+ (helo (opensmtpd-option-configuration
+        (option "helo") ;; this should be auth!!! NOT "helo"
+        (regex #t)
+        (value (opensmtpd-table-configuration  (name "mytable")
+                                 (data (list "cat" "kitten"))))))
+ (from (opensmtpd-option-configuration (option "from rdns")
+                                             (value (opensmtpd-table-configuration (name "table")
+                                                                     (data (list "cat" "hat"))))))
+ (action (opensmtpd-action-local-delivery-configuration
+          (name "matches"))))
+$10 = #<<opensmtpd-match-configuration> action: #<<opensmtpd-action-local-delivery-configuration> name: "matches" method: "mbox" alias: #f ttl: #f user: #f userbase: #f virtual: #f wrapper: #f> for: #f from: #<<opensmtpd-option-configuration> option: "from rdns" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "table" file-db: #f values: ("cat" "hat") type: #<procedure 24d6840 at <unknown port>:252:52 (x)>>> auth: #f helo: #<<opensmtpd-option-configuration> option: "helo" not: #f regex: #t value: #<<opensmtpd-table-configuration> name: "mytable" file-db: #f values: ("cat" "kitten") type: #<procedure 24d64c0 at <unknown port>:249:15 (x)>>> mail-from: #f rcpt-to: #f tag: #f tls: #f>
+scheme@(opensmtpd-records) [5]> (opensmtpd-match-configuration->string $10)
+$11 = "match from rdns <table> helo regex <mytable> action \"matches\" \n"
+#+END_SRC
+****** DONE mail-from <sender>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:22]
+:END:
+#+BEGIN_SRC scheme
+ (display (opensmtpd-match-configuration->string (opensmtpd-match-configuration
+ (mail-from (opensmtpd-option-configuration
+        (option "mail-from") ;; this should be auth!!! NOT "helo"
+        (regex #t)
+        (not #t)
+        (value (opensmtpd-table-configuration  (name "mytable")
+                                 (data (list "cat" "kitten"))))))
+ (from (opensmtpd-option-configuration (option "from rdns")
+                                             (value (opensmtpd-table-configuration (name "table")
+                                                                     (data (list "cat" "hat"))))))
+ (action (opensmtpd-action-local-delivery-configuration
+          (name "matches"))))))
+match from rdns <table> ! mail-from regex <mytable> action "matches"
+#+END_SRC
+****** DONE mail-from regex <sender>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:22]
+:END:
+#+BEGIN_SRC scheme
+ (display (opensmtpd-match-configuration->string (opensmtpd-match-configuration
+ (mail-from (opensmtpd-option-configuration
+        (option "mail-from") ;; this should be auth!!! NOT "helo"
+        (regex #t)
+        (not #t)
+        (value (opensmtpd-table-configuration  (name "mytable")
+                                 (data (list "cat" "kitten"))))))
+ (from (opensmtpd-option-configuration (option "from rdns")
+                                             (value (opensmtpd-table-configuration (name "table")
+                                                                     (data (list "cat" "hat"))))))
+ (action (opensmtpd-action-local-delivery-configuration
+          (name "matches"))))))
+match from rdns <table> ! mail-from regex <mytable> action "matches"
+#+END_SRC
+****** DONE rcpt-to <receipient>
+:LOGBOOK:
+- State "TODO"       from              [2021-11-02 Tue 04:22]
+:END:
+****** DONE rcpt-to regex <receipient>
+:LOGBOOK:
+- State "TODO"       from "TODO"       [2021-11-02 Tue 04:23]
+:END:
+
+*** PROJ sanitize the =<opensmtpd-option-configuration>= records in =<opensmtpd-filter-phase>= & =<opensmtpd-match-configuration>=
+**** PROJ testing the sanitize-list-of-options-for-match-configuration-assoc precodure [5/5]
+***** DONE make sure each option is unique (no duplicate "for"s).
+
+#+BEGIN_SRC scheme
+(opensmtpd-configuration
+ (matches (list
+           (opensmtpd-match-configuration
+            (options (list
+                      (opensmtpd-option-configuration
+                       (option "for any"))
+                      (opensmtpd-option-configuration
+                       (option "for local"))))
+            (action
+             (opensmtpd-action-relay-configuration
+              (name "relay")))))))
+#+END_SRC
+***** DONE make sure there is no duplicate from's
+#+BEGIN_SRC scheme
+(opensmtpd-configuration
+ (matches (list
+           (opensmtpd-match-configuration
+            (options (list
+                      (opensmtpd-option-configuration
+                       (option "from any"))
+                      (opensmtpd-option-configuration
+                       (option "from auth"))))
+            (action
+             (opensmtpd-action-relay-configuration
+              (name "relay")))))))
+#+END_SRC
+***** DONE for any data and regex must be false
+
+#+BEGIN_SRC scheme
+(opensmtpd-configuration
+ (matches (list
+           (opensmtpd-match-configuration
+            (options (list
+                      (opensmtpd-option-configuration
+                       (option "for any")
+                       (regex #t))))
+            (action
+             (opensmtpd-action-relay-configuration
+              (name "relay")))))))
+#+END_SRC
+***** DONE 'rcpt-to' must have data
+
+#+BEGIN_SRC scheme
+(opensmtpd-configuration
+ (matches (list
+           (opensmtpd-match-configuration
+            (options (list
+                      (opensmtpd-option-configuration
+                       (option "rcpt-to"))))
+            (action
+             (opensmtpd-action-relay-configuration
+              (name "relay")))))))
+#+END_SRC
+***** DONE 'tls' cannot have a 'data' or 'regex'
+
+#+BEGIN_SRC scheme
+(opensmtpd-configuration
+ (matches (list
+           (opensmtpd-match-configuration
+            (options (list
+                      (opensmtpd-option-configuration
+                       (option "tls")
+                       (data "hello")
+                       )))
+            (action
+             (opensmtpd-action-relay-configuration
+              (name "relay")))))))
+#+END_SRC
+*** TODO sanitize <opensmtpd-configuration> fieldname 'matches' so that no two unique actions have the same name
+I definitely should sanitize 'matches' a bit more.  For example, you could have two different
+actions, one for local delivery and one for remote, with the same name.  I
+should make sure that all unique actions have unique names.
+
+Here is an example of two actions that have the same name, but different ttl values:
+
+#+BEGIN_SRC scheme
+(opensmtpd-configuration
+ (matches
+  (list (opensmtpd-match-configuration
+         (action
+          (opensmtpd-action-local-delivery-configuration
+           (name "my-local-delivery")
+           (ttl "50m"))))               ; 50 minutes
+        (opensmtpd-match-configuration
+         (action
+          (opensmtpd-action-local-delivery-configuration
+           (name "my-local-delivery")
+           (ttl "50h"))))))) ; 50 hours
+#+END_SRC
+
+*** PROJ follow the style guide and style up my project [0/4]
+https://mumble.net/~campbell/scheme/style.txt
+
+:SchemeStyleGuide:
+#+BEGIN_SRC org
+Riastradh's Lisp Style Rules                            -*- outline -*-
+
+   Copyright (C) 2007--2011 Taylor R. Campbell
+
+   CC BY-NC-SA 3.0
+
+   This work is licensed under a Creative Commons
+   Attribution-NonCommercial-ShareAlike 3.0 Unported License:
+   <http://creativecommons.org/licenses/by-nc-sa/3.0/>.
+
+This is a guide to Lisp style, written by Taylor R. Campbell, to
+describe the standard rules of Lisp style as well as a set of more
+stringent rules for his own style.  This guide should be useful for
+Lisp in general, but there are [or will be in the final draft] parts
+that are focussed on or specific to Scheme or Common Lisp.
+
+This guide is written primarily as a collection of rules, with
+rationale for each rule.  (If a rule is missing rationale, please
+inform the author!)  Although a casual reader might go through and read
+the rules without the rationale, perhaps reasoning that reading of the
+rationale would require more time than is available, such a reader
+would derive little value from this guide.  In order to apply the rules
+meaningfully, their spirit must be understood; the letter of the rules
+serves only to hint at the spirit.  The rationale is just as important
+as the rules.
+
+There are many references in this document to `Emacs', `GNU Emacs',
+`Edwin', and so on.  In this document, `Emacs' means any of a class of
+editors related in design to a common ancestor, the EMACS editor macros
+written for TECO on ITS on the PDP-10 in the middle of the nineteen
+seventies.  All such editors -- or `all Emacsen', since `Emacsen' is
+the plural of `Emacs' -- have many traits in common, such as a very
+consistent set of key bindings, extensibility in Lisp, and so on.  `GNU
+Emacs' means the member of the class of editors collectively known as
+Emacsen that was written for the GNU Project in the middle of the
+nineteen eighties, and which is today probably the most popular Emacs.
+`Edwin' is MIT Scheme's Emacs, which is bundled as part of MIT Scheme,
+and not available separately.  There are other Emacsen as well, such as
+Hemlock and Climacs, but as the author of this document has little
+experience with Emacsen other than GNU Emacs and Edwin, there is little
+mention of other Emacsen.
+
+This guide is a work in progress.  To be written:
+
+- Indentation rules for various special operators.
+- Philosophical rambling concerning naming.
+- Rules for breaking lines.
+- Many more examples.
+- A more cohesive explanation of the author's principles for composing
+  programs, and their implications.
+- Rules for writing portable code.
+- Some thoughts concerning extensions to the lexical syntax.
+- Rules for writing or avoiding macros.
+- Some unfinished rationale.
+- More on documentation.
+- The `Dependencies' subsection of the `General Layout' section should
+  be put in a different section, the rest of which has yet to be
+  written, on organization of programs, module systems, and portable
+  code.
+
+Feedback is welcome; address any feedback by email to the host
+mumble.net's user `campbell', or by IRC to Riastradh in the #scheme
+channel on Freenode (irc.freenode.net).  Feedback includes reports of
+typos, questions, requests for clarification, and responses to the
+rationale, except in the case of round brackets versus square
+brackets, the argument surrounding which is supremely uninteresting
+and now not merely a dead horse but a rotting carcass buzzing with
+flies and being picked apart by vultures.
+
+As this document has grown, the line between standard Lisp rules and
+the author's own style has been blurred.  The author is considering
+merging of the partition, but has not yet decided on this with
+certainty.  Opinions on the subject are welcome -- is the partition
+still useful to keep the author's biases and idiosyncrasies out of the
+standard rules, or has the partition with its arbitrary nature only
+caused disorganization of the whole document?
+
+Unfortunately, this document is entirely unscientific.  It is at best a
+superstition or philosophy, but one that the author of this document
+has found to have improved his programs.  Furthermore, the author is
+somewhat skeptical of claims of scientific analyses of these matters:
+analyzing human behaviour, especially confined to the set of excellent
+programmers who often have strong opinions about their methods for
+building programs, is a very tricky task.
+
+,* Standard Rules
+
+These are the standard rules for formatting Lisp code; they are
+repeated here for completeness, although they are surely described
+elsewhere.  These are the rules implemented in Emacs Lisp modes, and
+auxiliary utilities such as Paredit.
+
+The rationale given here is merely the author's own speculation of the
+origin of these rules, and should be taken as nothing more than it.
+The reader shall, irrespective of the author's rationale, accept the
+rules as sent by the reader's favourite deity, or Cthulhu if no such
+deity strikes adequate fear into the heart of the reader.
+
+,** Parentheses
+
+,*** Terminology
+
+This guide avoids the term /parenthesis/ except in the general use of
+/parentheses/ or /parenthesized/, because the word's generally accepted
+definition, outside of the programming language, is a statement whose
+meaning is peripheral to the sentence in which it occurs, and *not* the
+typographical symbols used to delimit such statements.
+
+The balanced pair of typographical symbols that mark parentheses in
+English text are /round brackets/, i.e. ( and ).  There are several
+other balanced pairs of typographical symbols, such as /square
+brackets/ (commonly called simply `brackets' in programming circles),
+i.e. [ and ]; /curly braces/ (sometimes called simply `braces'), i.e. {
+and }; /angle brackets/ (sometimes `brokets' (for `broken brackets')),
+i.e. < and >.
+
+In any balanced pair of typographical symbols, the symbol that begins
+the region delimited by the symbols is called the /opening bracket/ or
+the /left bracket/, such as ( or [ or { or <.  The symbol that ends
+that region is called the /right bracket/ or the /closing bracket/,
+such as > or } or ] or ).
+
+,*** Spacing
+
+If any text precedes an opening bracket or follows a closing bracket,
+separate that text from that bracket with a space.  Conversely, leave
+no space after an opening bracket and before following text, or after
+preceding text and before a closing bracket.
+
+  Unacceptable:
+
+    (foo(bar baz)quux)
+    (foo ( bar baz ) quux)
+
+  Acceptable:
+
+    (foo (bar baz) quux)
+
+  Rationale:  This is the same spacing found in standard typography of
+  European text.  It is more aesthetically pleasing.
+
+,*** Line Separation
+
+Absolutely do *not* place closing brackets on their own lines.
+
+  Unacceptable:
+
+    (define (factorial x)
+      (if (< x 2)
+          1
+          (* x (factorial (- x 1
+                          )
+               )
+          )
+      )
+    )
+
+  Acceptable:
+
+    (define (factorial x)
+      (if (< x 2)
+          1
+          (* x (factorial (- x 1)))))
+
+  Rationale:  The parentheses grow lonely if their closing brackets are
+  all kept separated and segregated.
+
+,**** Exceptions to the Above Rule Concerning Line Separation
+
+Do not heed this section unless you know what you are doing.  Its title
+does *not* make the unacceptable example above acceptable.
+
+When commenting out fragments of expressions with line comments, it may
+be necessary to break a line before a sequence of closing brackets:
+
+  (define (foo bar)
+    (list (frob bar)
+          (zork bar)
+          ;; (zap bar)
+          ))
+
+This is acceptable, but there are other alternatives.  In Common Lisp,
+one can use the read-time optional syntax, `#+' or `#-', with a
+feature optional that is guaranteed to be false or true -- `#+(OR)'
+or `#-(AND)' --; for example,
+
+  (define (foo bar)
+    (list (frob bar)
+          (zork bar)
+          ,#+(or) (zap bar))).
+
+Read-time optionals are expression-oriented, not line-oriented, so
+the closing brackets need not be placed on the following line.  Some
+Scheme implementations, and SRFI 62, also support expression comments
+with `#;', which are operationally equivalent to the above read-time
+optionals for Common Lisp:
+
+  (define (foo bar)
+    (list (frob bar)
+          (zork bar)
+          #;
+           (zap bar)))
+
+The expression is placed on another line in order to avoid confusing
+editors that do not recognize S-expression comments; see the section
+titled `Comments' below for more details.  However, the `#;' notation
+is not standard -- it appears in neither the IEEE 1178 document nor in
+the R5RS --, so line comments are preferable for portable Scheme code,
+even if they require breaking a line before a sequence of closing
+brackets.
+
+Finally, it is acceptable to break a line immediately after an opening
+bracket and immediately before a closing bracket for very long lists,
+especially in files under version control.  This eases the maintenance
+of the lists and clarifies version diffs.  Example:
+
+  (define colour-names         ;Add more colour names to this list!
+    '(
+      blue
+      cerulean
+      green
+      magenta
+      purple
+      red
+      scarlet
+      turquoise
+      ))
+
+,*** Parenthetical Philosophy
+
+The actual bracket characters are simply lexical tokens to which little
+significance should be assigned.  Lisp programmers do not examine the
+brackets individually, or, Azathoth forbid, count brackets; instead
+they view the higher-level structures expressed in the program,
+especially as presented by the indentation.  Lisp is not about writing
+a sequence of serial instructions; it is about building complex
+structures by summing parts.  The composition of complex structures
+from parts is the focus of Lisp programs, and it should be readily
+apparent from the Lisp code.  Placing brackets haphazardly about the
+presentation is jarring to a Lisp programmer, who otherwise would not
+even have seen them for the most part.
+
+,** Indentation and Alignment
+
+The operator of any form, i.e. the first subform following the opening
+round bracket, determines the rules for indenting or aligning the
+remaining forms.  Many names in this position indicate special
+alignment or indentation rules; these are special operators, macros, or
+procedures that have certain parameter structures.
+
+If the first subform is a non-special name, however, then if the second
+subform is on the same line, align the starting column of all following
+subforms with that of the second subform.  If the second subform is on
+the following line, align its starting column with that of the first
+subform, and do the same for all remaining subforms.
+
+In general, Emacs will indent Lisp code correctly.  Run `C-M-q'
+(indent-sexp) on any code to ensure that it is indented correctly, and
+configure Emacs so that any non-standard forms are indented
+appropriately.
+
+  Unacceptable:
+
+    (+ (sqrt -1)
+      (* x y)
+      (+ p q))
+
+    (+
+       (sqrt -1)
+       (* x y)
+       (+ p q))
+
+  Acceptable:
+
+    (+ (sqrt -1)
+       (* x y)
+       (+ p q))
+
+    (+
+     (sqrt -1)
+     (* x y)
+     (+ p q))
+
+  Rationale:  The columnar alignment allows the reader to follow the
+  operands of any operation straightforwardly, simply by scanning
+  downward or upward to match a common column.  Indentation dictates
+  structure; confusing indentation is a burden on the reader who wishes
+  to derive structure without matching parentheses manually.
+
+,*** Non-Symbol Indentation and Alignment
+
+The above rules are not exhaustive; some cases may arise with strange
+data in operator positions.
+
+,**** Lists
+
+Unfortunately, style varies here from person to person and from editor
+to editor.  Here are some examples of possible ways to indent lists
+whose operators are lists:
+
+  Questionable:
+
+    ((car x)                            ;Requires hand indentation.
+       (cdr x)
+       foo)
+
+    ((car x) (cdr x)                    ;GNU Emacs
+     foo)
+
+  Preferable:
+
+    ((car x)                            ;Any Emacs
+     (cdr x)
+     foo)
+
+    ((car x) (cdr x)                    ;Edwin
+             foo)
+
+  Rationale:  The operands should be aligned, as if it were any other
+  procedure call with a name in the operator position; anything other
+  than this is confusing because it gives some operands greater visual
+  distinction, allowing others to hide from the viewer's sight.  For
+  example, the questionable indentation
+
+    ((car x) (cdr x)
+     foo)
+
+  can make it hard to see that FOO and (CDR X) are both operands here
+  at the same level.  However, GNU Emacs will generate that indentation
+  by default.  (Edwin will not.)
+
+,**** Strings
+
+If the form in question is meant to be simply a list of literal data,
+all of the subforms should be aligned to the same column, irrespective
+of the first subform.
+
+  Unacceptable:
+
+    ("foo" "bar" "baz" "quux" "zot"
+           "mumble" "frotz" "gargle" "mumph")
+
+  Questionable, but acceptable:
+
+    (3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 2 3 8 4 6 2 6 4
+       3 3 8 3 2 7 9 5 0 2 8 8 4 1 9 7 1 6 9 3 9 9 3)
+
+  Acceptable:
+
+    ("foo" "bar" "baz" "quux" "zot"
+     "mumble" "frotz" "gargle" "mumph")
+
+    ("foo"
+     "bar" "baz" "quux" "zot"
+     "mumble" "frotz" "gargle" "mumph")
+
+  Rationale:  Seldom is the first subform distinguished for any reason,
+  if it is a literal; usually in this case it indicates pure data, not
+  code.  Some editors and pretty-printers, however, will indent
+  unacceptably in the example given unless the second subform is on the
+  next line anyway, which is why the last way to write the fragment is
+  usually best.
+
+,** Names
+
+Naming is subtle and elusive.  Bizarrely, it is simultaneously
+insignificant, because an object is independent of and unaffected by
+the many names by which we refer to it, and also of supreme
+importance, because it is what programming -- and, indeed, almost
+everything that we humans deal with -- is all about.  A full
+discussion of the concept of name lies far outside the scope of this
+document, and could surely fill not even a book but a library.
+
+Symbolic names are written with English words separated by hyphens.
+Scheme and Common Lisp both fold the case of names in programs;
+consequently, camel case is frowned upon, and not merely because it is
+ugly.  Underscores are unacceptable separators except for names that
+were derived directly from a foreign language without translation.
+
+  Unacceptable:
+
+    XMLHttpRequest
+    foreach
+    append_map
+
+  Acceptable:
+
+    xml-http-request
+    for-each
+    append-map
+
+,*** Funny Characters
+
+There are several different conventions in different Lisps for the use
+of non-alphanumeric characters in names.
+
+,**** Scheme
+
+,***** Question Marks: Predicates
+
+Affix a question mark to the end of a name for a procedure whose
+purpose is to ask a question of an object and to yield a boolean
+answer.  Such procedures are called `predicates'.  Do not use a
+question mark if the procedure may return any object other than a
+boolean.
+
+  Examples:  pair? procedure? proper-list?
+  Non-examples:  member assoc any every
+
+Pronounce the question mark as if it were the isolated letter `p'.  For
+example, to read the fragment (PAIR? OBJECT) aloud, say: `pair-pee
+object.'
+
+,***** Exclamation Marks: Destructive Operations
+
+Affix an exclamation mark to the end of a name for a procedure (or
+macro) whose primary purpose is to modify an object.  Such procedures
+are called `destructive'.
+
+  Examples: set-car! append!
+
+Avoid using the exclamation mark willy nilly for just *any* procedure
+whose operation involves any kind of mutation or side effect; instead,
+use the exclamation mark to identify procedures that exist *solely* for
+the purpose of destructive update (e.g., SET-CAR!), or to distinguish a
+destructive, or potentially destructive (in the case of linear-update
+operations such as APPEND!), variant of a procedure of which there also
+exists a purely functional variant (e.g., APPEND).
+
+Pronounce the exclamation mark as `bang'.  For example, to read the
+fragment (APPEND! LIST TAIL) aloud, say: `append-bang list tail.'
+
+,***** Asterisks: Variants, Internal Routines, Mutable Globals
+
+Affix an asterisk to the end of a name to make a variation on a theme
+of the original name.
+
+  Example: let -> let*
+
+Prefer a meaningful name over an asterisk; the asterisk does not
+explain what variation on the theme the name means.
+
+Affix an asterisk to the beginning of a name to make an internal
+routine for that name.  Again, prefer a meaningful name over an
+asterisk.
+
+Affix asterisks to the beginning and end of a globally mutable
+variable.  This allows the reader of the program to recognize very
+easily that it is badly written!
+
+,***** `WITH-' and `CALL-WITH-': Dynamic State and Cleanup
+
+Prefix `WITH-' to any procedure that establishes dynamic state and
+calls a nullary procedure, which should be the last (required)
+argument.  The dynamic state should be established for the extent of
+the nullary procedure, and should be returned to its original state
+after that procedure returns.
+
+  Examples: with-input-from-file with-output-to-file
+
+  Exception:  Some systems provide a procedure (WITH-CONTINUATION
+  <continuation> <thunk>), which calls <thunk> in the given
+  continuation, using that continuation's dynamic state.  If <thunk>
+  returns, it will return to <continuation>, not to the continuation of
+  the call to WITH-CONTINUATION.  This is acceptable.
+
+Prefix `CALL-WITH-' to any procedure that calls a procedure, which
+should be its last argument, with some arguments, and is either somehow
+dependent upon the dynamic state or continuation of the program, or
+will perform some action to clean up data after the procedure argument
+returns.  Generally, `CALL-WITH-' procedures should return the values
+that the procedure argument returns, after performing the cleaning
+action.
+
+  Examples:
+
+  - CALL-WITH-INPUT-FILE and CALL-WITH-OUTPUT-FILE both accept a
+    pathname and a procedure as an argument, open that pathname (for
+    input or output, respectively), and call the procedure with one
+    argument, a port corresponding with the file named by the given
+    pathname.  After the procedure returns, CALL-WITH-INPUT-FILE and
+    CALL-WITH-OUTPUT-FILE close the file that they opened, and return
+    whatever the procedure returned.
+
+  - CALL-WITH-CURRENT-CONTINUATION is dependent on the continuation
+    with which it was called, and passes as an argument an escape
+    procedure corresponding with that continuation.
+
+  - CALL-WITH-OUTPUT-STRING, a common but non-standard procedure
+    definable in terms of OPEN-OUTPUT-STRING and GET-OUTPUT-STRING from
+    SRFI 6 (Basic String Ports), calls its procedure argument with an
+    output port, and returns a string of all of the output written to
+    that port.  Note that it does not return what the procedure
+    argument returns, which is an exception to the above rule.
+
+Generally, the distinction between these two classes of procedures is
+that `CALL-WITH-...' procedures should not establish fresh dynamic
+state and instead pass explicit arguments to their procedure arguments,
+whereas `WITH-...' should do the opposite and establish dynamic state
+while passing zero arguments to their procedure arguments.
+
+,** Comments
+
+Write heading comments with at least four semicolons; see also the
+section below titled `Outline Headings'.
+
+Write top-level comments with three semicolons.
+
+Write comments on a particular fragment of code before that fragment
+and aligned with it, using two semicolons.
+
+Write margin comments with one semicolon.
+
+The only comments in which omission of a space between the semicolon
+and the text is acceptable are margin comments.
+
+  Examples:
+
+    ;;;; Frob Grovel
+
+    ;;; This section of code has some important implications:
+    ;;;   1. Foo.
+    ;;;   2. Bar.
+    ;;;   3. Baz.
+
+    (define (fnord zarquon)
+      ;; If zob, then veeblefitz.
+      (quux zot
+            mumble             ;Zibblefrotz.
+            frotz))
+
+,* Riastradh's Non-Standard Rules
+
+Three principles guide this style, roughly ordered according to
+descending importance:
+
+1. The purpose of a program is to describe an idea, and not the way
+   that the idea must be realized; the intent of the program's meaning,
+   rather than peripheral details that are irrelevant to its intent,
+   should be the focus of the program, *irrespective* of whether a
+   human or a machine is reading it.  [It would be nice to express this
+   principle more concisely.]
+
+2. The sum of the parts is easier to understand than the whole.
+
+3. Aesthetics matters.  No one enjoys reading an ugly program.
+
+,** General Layout
+
+This section contains rules that the author has found generally helpful
+in keeping his programs clean and presentable, though they are not
+especially philosophically interesting.
+
+Contained in the rationale for some of the following rules are
+references to historical limitations of terminals and printers, which
+are now considered aging cruft of no further relevance to today's
+computers.  Such references are made only to explain specific measures
+chosen for some of the rules, such as a limit of eighty columns per
+line, or sixty-six lines per page.  There is a real reason for each of
+the rules, and this real reason is not intrinsically related to the
+historical measures, which are mentioned only for the sake of
+providing some arbitrary measure for the limit.
+
+,*** File Length
+
+If a file exceeds five hundred twelve lines, begin to consider
+splitting it into multiple files.  Do not write program files that
+exceed one thousand twenty-four lines.  Write a concise but
+descriptive title at the top of each file, and include no content in
+the file that is unrelated to its title.
+
+  Rationale:  Files that are any larger should generally be factored
+  into smaller parts.  (One thousand twenty-four is a nicer number than
+  one thousand.)  Identifying the purpose of the file helps to break it
+  into parts if necessary and to ensure that nothing unrelated is
+  included accidentally.
+
+,*** Top-Level Form Length
+
+Do not write top-level forms that exceed twenty-one lines, except for
+top-level forms that serve only the purpose of listing large sets of
+data.  If a procedure exceeds this length, split it apart and give
+names to its parts.  Avoid names formed simply by appending a number
+to the original procedure's name; give meaningful names to the parts.
+
+  Rationale:  Top-level forms, especially procedure definitions, that
+  exceed this length usually combine too many concepts under one name.
+  Readers of the code are likely to more easily understand the code if
+  it is composed of separately named parts.  Simply appending a number
+  to the original procedure's name can help only the letter of the
+  rule, not the spirit, however, even if the procedure was taken from a
+  standard algorithm description.  Using comments to mark the code with
+  its corresponding place in the algorithm's description is acceptable,
+  but the algorithm should be split up in meaningful fragments anyway.
+
+  Rationale for the number twenty-one:  Twenty-one lines, at a maximum
+  of eighty columns per line, fits in a GNU Emacs instance running in a
+  24x80 terminal.  Although the terminal may have twenty-four lines,
+  three of the lines are occupied by GNU Emacs: one for the menu bar
+  (which the author of this guide never uses, but which occupies a line
+  nevertheless in a vanilla GNU Emacs installation), one for the mode
+  line, and one for the minibuffer's window.  The writer of some code
+  may not be limited to such a terminal, but the author of this style
+  guide often finds it helpful to have at least four such terminals or
+  Emacs windows open simultaneously, spread across a twelve-inch laptop
+  screen, to view multiple code fragments.
+
+,*** Line Length
+
+Do not write lines that exceed eighty columns, or if possible
+seventy-two.
+
+  Rationale:  Following multiple lines that span more columns is
+  difficult for humans, who must remember the line of focus and scan
+  right to left from the end of the previous line to the beginning of
+  the next line; the more columns there are, the harder this is to do.
+  Sticking to a fixed limit helps to improve readability.
+
+  Rationale for the numbers eighty and seventy-two:  It is true that we
+  have very wide screens these days, and we are no longer limited to
+  eighty-column terminals; however, we ought to exploit our wide
+  screens not by writing long lines, but by viewing multiple fragments
+  of code in parallel, something that the author of this guide does
+  very often.  Seventy-two columns leave room for several nested layers
+  of quotation in email messages before the code reaches eighty
+  columns.  Also, a fixed column limit yields nicer printed output,
+  especially in conjunction with pagination; see the section
+  `Pagination' below.
+
+,*** Blank Lines
+
+Separate each adjacent top-level form with a single blank line (i.e.
+two line breaks).  If two blank lines seem more appropriate, break the
+page instead.  Do not place blank lines in the middle of a procedure
+body, except to separate internal definitions; if there is a blank
+line for any other reason, split the top-level form up into multiple
+ones.
+
+  Rationale:  More than one blank line is distracting and sloppy.  If
+  the two concepts that are separated by multiple blank lines are
+  really so distinct that such a wide separator is warranted, then
+  they are probably better placed on separate pages anyway; see the
+  next section, `Pagination'.
+
+,*** Pagination
+
+Separate each file into pages of no more than sixty-six lines and no
+fewer than forty lines with form feeds (ASCII #x0C, or ^L, written in
+Emacs with `C-q C-l'), on either side of which is a single line break
+(but not a blank line).
+
+  Rationale:  Keeping distinct concepts laid out on separate pages
+  helps to keep them straight.  This is helpful not only for the
+  writer of the code, but also for the reader.  It also allows readers
+  of the code to print it onto paper without fiddling with printer
+  settings to permit pages of more than sixty-six lines (which is the
+  default number for many printers), and pagination also makes the
+  code easier to navigate in Emacs, with the `C-x [' and `C-x ]' keys
+  (`backward-page' and `forward-page', respectively).  To avoid
+  excessively small increments of page-by-page navigation, and to
+  avoid wasting paper, each page should generally exceed forty lines.
+
+  `C-x l' in Emacs will report the number of lines in the page on which
+  the point lies; this is useful for finding where pagination is
+  necessary.
+
+,*** Outline Headings
+
+Use Emacs's Outline Mode to give titles to the pages, and if
+appropriate a hierarchical structure.  Set `outline-regexp' (or
+`outline-pattern' in Edwin) to "\f\n;;;;+ ", so that each form feed
+followed by an line break followed by at least four semicolons and a
+space indicates an outline heading to Emacs.  Use four semicolons for
+the highest level of headings in the hierarchy, and one more for each
+successively nested level of hierarchy.
+
+  Rationale:  Not only does this clarify the organization of the code,
+  but readers of the code can then navigate the code's structure with
+  Outline Mode commands such as `C-c C-f', `C-c C-b', `C-c C-u', and
+  `C-c C-d' (forward, backward, up, down, respectively, headings).
+
+,*** Dependencies
+
+When writing a file or module, minimize its dependencies.  If there are
+too many dependencies, consider breaking the module up into several
+parts, and writing another module that is the sum of the parts and that
+depends only on the parts, not their dependencies.
+
+  Rationale:  A fragment of a program with fewer dependencies is less
+  of a burden on the reader's cognition.  The reader can more easily
+  understand the fragment in isolation; humans are very good at local
+  analyses, and terrible at global ones.
+
+,** Naming
+
+This section requires an elaborate philosophical discussion which the
+author is too ill to have the energy to write at this moment.
+
+Compose concise but meaningful names.  Do not cheat by abbreviating
+words or using contractions.
+
+  Rationale:  Abbreviating words in names does not make them shorter;
+  it only makes them occupy less screen space.  The reader still must
+  understand the whole long name.  This does not mean, however, that
+  names should necessarily be long; they should be descriptive.  Some
+  long names are more descriptive than some short names, but there are
+  also descriptive names that are not long and long names that are not
+  descriptive.  Here is an example of a long name that is not
+  descriptive, from SchMUSE, a multi-user simulation environment
+  written in MIT Scheme:
+
+    frisk-descriptor-recursive-subexpr-descender-for-frisk-descr-env
+
+  Not only is it long (sixty-four characters) and completely
+  impenetrable, but halfway through its author decided to abbreviate
+  some words as well!
+
+Do not write single-letter variable names.  Give local variables
+meaningful names composed from complete English words.
+
+  Rationale:  It is tempting to reason that local variables are
+  invisible to other code, so it is OK to be messy with their names.
+  This is faulty reasoning: although the next person to come along and
+  use a library may not care about anything but the top-level
+  definitions that it exports, this is not the only audience of the
+  code.  Someone will also want to read the code later on, and if it is
+  full of impenetrably terse variable names without meaning, that
+  someone will have a hard time reading the code.
+
+Give names to intermediate values where their expressions do not
+adequately describe them.
+
+  Rationale:  An `expression' is a term that expresses some value.
+  Although a machine needs no higher meaning for this value, and
+  although it should be written to be sufficiently clear for a human to
+  understand what it means, the expression might mean something more
+  than just what it says where it is used.  Consequently, it is helpful
+  for humans to see names given to expressions.
+
+  Example:  A hash table HASH-TABLE maps foos to bars; (HASH-TABLE/GET
+  HASH-TABLE FOO #F) expresses the datum that HASH-TABLE maps FOO to,
+  but that expression gives the reader no hint of any information
+  concerning that datum.  (LET ((BAR (HASH-TABLE/GET HASH-TABLE FOO
+  #F))) ...)  gives a helpful name for the reader to understand the
+  code without having to find the definition of HASH-TABLE.
+
+  Index variables such as i and j, or variables such as A and D naming
+  the car and cdr of a pair, are acceptable only if they are completely
+  unambiguous in the scope.  For example,
+
+    (do ((i 0 (+ i 1)))
+        ((= i (vector-length vector)))
+      (frobnicate (vector-ref vector i)))
+
+  is acceptable because the scope of i is very clearly limited to a
+  single vector.  However, if more vectors are involved, using more
+  index variables such as j and k will obscure the program further.
+
+Avoid functional combinators, or, worse, the point-free (or
+`point-less') style of code that is popular in the Haskell world.  At
+most, use function composition only where the composition of functions
+is the crux of the idea being expressed, rather than simply a procedure
+that happens to be a composition of two others.
+
+  Rationale:  Tempting as it may be to recognize patterns that can be
+  structured as combinations of functional combinators -- say, `compose
+  this procedure with the projection of the second argument of that
+  other one', or (COMPOSE FOO (PROJECT 2 BAR)) --, the reader of the
+  code must subsequently examine the elaborate structure that has been
+  built up to obscure the underlying purpose.  The previous fragment
+  could have been written (LAMBDA (A B) (FOO (BAR B))), which is in
+  fact shorter, and which tells the reader directly what argument is
+  being passed on to what, and what argument is being ignored, without
+  forcing the reader to search for the definitions of FOO and BAR or
+  the call site of the final composition.  The explicit fragment
+  contains substantially more information when intermediate values are
+  named, which is very helpful for understanding it and especially for
+  modifying it later on.
+
+  The screen space that can be potentially saved by using functional
+  combinators is made up for by the cognitive effort on the part of the
+  reader.  The reader should not be asked to search globally for usage
+  sites in order to understand a local fragment.  Only if the structure
+  of the composition really is central to the point of the narrative
+  should it be written as such.  For example, in a symbolic integrator
+  or differentiator, composition is an important concept, but in most
+  code the structure of the composition is completely irrelevant to the
+  real point of the code.
+
+If a parameter is ignored, give it a meaningful name nevertheless and
+say that it is ignored; do not simply call it `ignored'.
+
+In Common Lisp, variables can be ignored with (DECLARE (IGNORE ...)).
+Some Scheme systems have similar declarations, but the portable way to
+ignore variables is just to write them in a command context, where
+their values will be discarded, preferably with a comment indicating
+this purpose:
+
+  (define (foo x y z)
+    x z                         ;ignore
+    (frobnitz y))
+
+  Rationale:  As with using functional combinators to hide names,
+  avoiding meaningful names for ignored parameters only obscures the
+  purpose of the program.  It is helpful for a reader to understand
+  what parameters a procedure is independent of, or if someone wishes
+  to change the procedure later on, it is helpful to know what other
+  parameters are available.  If the ignored parameters were named
+  meaninglessly, then these people would be forced to search for call
+  sites of the procedure in order to get a rough idea of what
+  parameters might be passed here.
+
+When naming top-level bindings, assume namespace partitions unless in a
+context where they are certain to be absent.  Do not write explicit
+namespace prefixes, such as FOO:BAR for an operation BAR in a module
+FOO, unless the names will be used in a context known not to have any
+kind of namespace partitions.
+
+  Rationale:  Explicit namespace prefixes are ugly, and lengthen names
+  without adding much semantic content.  Common Lisp has its package
+  system to separate the namespaces of symbols; most Schemes have
+  mechanisms to do so as well, even if the RnRS do not specify any.  It
+  is better to write clear names which can be disambiguated if
+  necessary, rather than to write names that assume some kind of
+  disambiguation to be necessary to begin with.  Furthermore, explicit
+  namespace prefixes are inadequate to cover name clashes anyway:
+  someone else might choose the same namespace prefix.  Relegating this
+  issue to a module system removes it from the content of the program,
+  where it is uninteresting.
+
+,** Comments
+
+Write comments only where the code is incapable of explaining itself.
+Prefer self-explanatory code over explanatory comments.  Avoid
+`literate programming' like the plague.
+
+  Rationale:  If the code is often incapable of explaining itself, then
+  perhaps it should be written in a more expressive language.  This may
+  mean using a different programming language altogether, or, since we
+  are talking about Lisp, it may mean simply building a combinator
+  language or a macro language for the purpose.  `Literate programming'
+  is the logical conclusion of languages incapable of explaining
+  themselves; it is a direct concession of the inexpressiveness of the
+  computer language implementing the program, to the extent that the
+  only way a human can understand the program is by having it rewritten
+  in a human language.
+
+Do not write interface documentation in the comments for the
+implementation of the interface.  Explain the interface at the top of
+the file if it is a single-file library, or put that documentation in
+another file altogether.  (See the `Documentation' section below if the
+interface documentation comments grow too large for a file.)
+
+  Rationale:  A reader who is interested only in the interface really
+  should not need to read through the implementation to pick out its
+  interface; by putting the interface documentation at the top, not
+  only is such a reader's task of identifying the interface made
+  easier, but the implementation code can be more liberally commented
+  without fear of distracting this reader.  To a reader who is
+  interested in the implementation as well, the interface is still
+  useful in order to understand what concepts the implementation is
+  implementing.
+
+  Example: <http://mumble.net/~campbell/scheme/skip-list.scm>
+
+  In this example of a single-file library implementing the skip list
+  data structure, the first page explains the purpose and dependencies
+  of the file (which are useful for anyone who intends to use it, even
+  though dependencies are really implementation details), and the next
+  few pages explain the usage of skip lists as implemented in that
+  file.  On the first page of implementation, `Skip List Structure',
+  there are some comments of interest only to a reader who wishes to
+  understand the implementation; the same goes for the rest of the
+  file, none of which must a reader read whose interest is only in the
+  usage of the library.
+
+Avoid block comments (i.e. #| ... |#).  Use S-expression comments (`#;'
+in Scheme, with the expression to comment on the next line; `#+(OR)' or
+`#-(AND)' in Common Lisp) to comment out whole expressions.  Use blocks
+of line comments for text.
+
+  Rationale:  Editor support for block comments is weak, because it
+  requires keeping a detailed intermediate parse state of the whole
+  buffer, which most Emacsen do not do.  At the very least, #|| ... ||#
+  is better, because most Emacsen will see vertical bars as symbol
+  delimiters, and lose trying to read a very, very long symbol, if they
+  try to parse #| ... |#, whereas they will just see two empty symbols
+  and otherwise innocuous text between them if they try to parse #||
+  ... ||#.  In any case, in Emacs, `M-x comment-region RET', or `M-;'
+  (comment-dwim), is trivial to type.
+
+  The only standard comments in Scheme are line comments.  There are
+  SRFIs for block comments and S-expression comments, but support for
+  them varies from system to system.  Expression comments are not hard
+  for editors to deal with because it is safe not to deal with them at
+  all; however, in Scheme S-expression comments, which are written by
+  prefixing an expression with `#;', the expression to be commented
+  should be placed on the next line.  This is because editors that do
+  not deal with them at all may see the semicolon as the start of a
+  line comment, which will throw them off.  Expression comments in
+  Common Lisp, however, are always safe.
+
+  In Common Lisp, the two read-time optionals that are guaranteed to
+  ignore any form following them are `#+(OR)' and `#-(AND)'.  `#+NIL'
+  is sometimes used in their stead, but, while it may appear to be an
+  obviously false optional, it actually is not.  The feature
+  expressions are read in the KEYWORD package, so NIL is read not as
+  CL:NIL, i.e. the boolean false value, but as :NIL, a keyword symbol
+  whose name happens to be `NIL'.  Not only is it not read as the
+  boolean false value, but it has historically been used to indicate a
+  feature that might be enabled -- in JonL White's New Implementation
+  of Lisp!  However, the New Implementation of Lisp is rather old these
+  days, and unlikely to matter much...until Alastair Bridgewater writes
+  Nyef's Implementation of Lisp.
+
+,** Documentation
+
+On-line references and documentation/manuals are both useful for
+independent purposes, but there is a very fine distinction between
+them.  Do not generate documentation or manuals automatically from the
+text of on-line references.
+
+  Rationale: /On-line references/ are quick blurbs associated with
+  objects in a running Lisp image, such as documentation strings in
+  Common Lisp or Emacs Lisp.  These assume that the reader is familiar
+  with the gist of the surrounding context, but unclear on details;
+  on-line references specify the details of individual objects.
+
+  /Documentation/ and /manuals/ are fuller, organized, and cohesive
+  documents that explain the surrounding context to readers who are
+  unfamiliar with it.  A reader should be able to pick a manual up and
+  begin reading it at some definite point, perusing it linearly to
+  acquire an understanding of the subject.  Although manuals may be
+  dominated by reference sections, they should still have sections that
+  are linearly readable to acquaint the reader with context.
+
+,** Round and Square Brackets
+
+Some implementations of Scheme provide a non-standard extension of the
+lexical syntax whereby balanced pairs of square brackets are
+semantically indistinguishable from balanced pairs of round brackets.
+Do not use this extension.
+
+  Rationale:  Because this is a non-standard extension, it creates
+  inherently non-portable code, of a nature much worse than using a
+  name in the program which is not defined by the R5RS.  The reason
+  that we have distinct typographical symbols in the first place is to
+  express different meaning.  The only distinction between round
+  brackets and square brackets is in convention, but the precise nature
+  of the convention is not specified by proponents of square brackets,
+  who suggest that they be used for `clauses', or for forms that are
+  parts of enclosing forms.  This would lead to such constructions as
+
+    (let [(x 5) (y 3)] ...)
+
+  or
+
+    (let ([x 5] [y 3]) ...)
+
+  or
+
+    (let [[x 5] [y 3]] ...),
+
+  the first two of which the author of this guide has seen both of, and
+  the last of which does nothing to help to distinguish the parentheses
+  anyway.
+
+  The reader of the code should not be forced to stumble over a
+  semantic identity because it is expressed by a syntactic distinction.
+  The reader's focus should not be directed toward the lexical tokens;
+  it should be directed toward the structure, but using square brackets
+  draws the reader's attention unnecessarily to the lexical tokens.
+
+,* Attribution
+
+#+END_SRC
+:END:
+
+**** TODO I have to get change (let ([x 5] [y 3])) -> (let ((x 5) (y 3)))
+**** TODO comments
+
+#+BEGIN_SRC scheme
+  ;;;; Frob Grovel
+
+    ;;; This section of code has some important implications:
+    ;;;   1. Foo.
+    ;;;   2. Bar.
+    ;;;   3. Baz.
+
+    (define (fnord zarquon)
+      ;; If zob, then veeblefitz.
+      (quux zot
+            mumble             ;Zibblefrotz.
+            frotz))
+
+#+END_SRC
+**** TODO literal data
+Strings
+
+If the form in question is meant to be simply a list of literal data,
+all of the subforms should be aligned to the same column, irrespective
+of the first subform.
+
+  Unacceptable:
+
+    ("foo" "bar" "baz" "quux" "zot"
+           "mumble" "frotz" "gargle" "mumph")
+
+  Questionable, but acceptable:
+
+    (3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 2 3 8 4 6 2 6 4
+       3 3 8 3 2 7 9 5 0 2 8 8 4 1 9 7 1 6 9 3 9 9 3)
+
+  Acceptable:
+
+    ("foo" "bar" "baz" "quux" "zot"
+     "mumble" "frotz" "gargle" "mumph")
+
+    ("foo"
+     "bar" "baz" "quux" "zot"
+     "mumble" "frotz" "gargle" "mumph")
+**** TODO follow this syntax convention proc args
+
+:alignment:
+The operator of any form, i.e. the first subform following the opening
+round bracket, determines the rules for indenting or aligning the
+remaining forms.  Many names in this position indicate special
+alignment or indentation rules; these are special operators, macros, or
+procedures that have certain parameter structures.
+
+If the first subform is a non-special name, however, then if the second
+subform is on the same line, align the starting column of all following
+subforms with that of the second subform.  If the second subform is on
+the following line, align its starting column with that of the first
+subform, and do the same for all remaining subforms.
+
+In general, Emacs will indent Lisp code correctly.  Run `C-M-q'
+(indent-sexp) on any code to ensure that it is indented correctly, and
+configure Emacs so that any non-standard forms are indented
+appropriately.
+
+  Unacceptable:
+
+    (+ (sqrt -1)
+      (* x y)
+      (+ p q))
+
+    (+
+       (sqrt -1)
+       (* x y)
+       (+ p q))
+
+  Acceptable:
+
+    (+ (sqrt -1)
+       (* x y)
+       (+ p q))
+
+    (+
+     (sqrt -1)
+     (* x y)
+     (+ p q))
+
+  Rationale:  The columnar alignment allows the reader to follow the
+  operands of any operation straightforwardly, simply by scanning
+  downward or upward to match a common column.  Indentation dictates
+  structure; confusing indentation is a burden on the reader who wishes
+  to derive structure without matching parentheses manually.
+
+:END:
+
+If you have a procedure, then it's arguments should be on the same line.
+#+BEGIN_SRC scheme
+(proc args
+      (proc (proc
+             args)
+            (proc args)
+            (proc (proc
+                   args)
+                  (proc (proc
+                         (proc (proc
+                                args))))))
+      + (proc (proc
+               args)))
+#+END_SRC
+
+*** TODO write various tests for =<opensmtpd-configuration>=
+
+I have many bits of code in opensmtpd.org.archive that should result in an
+error.  I should write some tests for this.
+** NO should I modifiy some of the records to include a sanitize field?
+Probably not.  It would be cool if this function ran automatically upon record
+initiation, but there's no to make it do that.
+** TODO which sanitize function is better?  Pick the better sanitize method and use that one.
+
+The sanitize function found in opensmtpd-listen-on-configuration-filters
+
+Or the sanitize function
+sanitize-list-of-options-for-match-configuration ?
+
+sanitize-list-of-options-for-match-configuration is probably faster. But is it?
+It is an iteratize loop that checks for all issues as it loops through the
+options. There is a lot of repetitive code in this procedure.
+
+BUT opensmtpd-listen-on-configuration-filters certainly seems easier to follow.
+** TODO remove opensmtpd-table-type fieldname and instead move that it its own procedure outside of the record
+
+ONly use one function instead of
+
+
+;; this procedure takes in one argument.
+;; if that argument is an <opensmtpd-table-configuration> whose fieldname 'values' is an assoc-list, then it returns
+;; #t, #f if otherwise.
+;; TODO should I remove these two functions?  And instead use the (opensmtpd-table-configuration-type) procedure?
+(define (table-whose-data-are-assoc-list? table)
+  (if (not (opensmtpd-table-configuration? table))
+      #f
+      (assoc-list? (opensmtpd-table-configuration-data table))))
+
+;; this procedure takes in one argument
+;; if that argument is an <opensmtpd-table-configuration> whose fieldname 'values' is a list of strings, then it returns
+;; #t, #f if otherwise.
+(define (table-whose-data-are-a-list-of-strings? table)
+  (if (not (opensmtpd-table-configuration? table))
+      #f
+      (list-of-strings? (opensmtpd-table-configuration-data table))))
+
+      And opensmtpd-table-type
+** TODO OpenSMTPD Service documentation
+
+OpenSMTPD is an easy-to-use mail transfer agent (MTA). Its configuration file is
+throughly documented in man 5 =smtpd.conf=. OpenSMTPD *listens* for incoming
+mail and *matches* the mail to *actions*. The following records represent those
+stages: ~<opensmtpd-listen-on-configuration>~,
+~<opensmtpd-listen-on-socket-configuration>=, =<opensmtpd-match-configuration>~,
+~<opensmtpd-action-local-delivery-configuration>~, and
+~<opensmtpd-action-relay-configuration>~.
+
+Additionally, each ~<opensmtpd-listen-on-configuration>~ and
+~<opensmtpd-listen-on-socket-configuration>~ may use a list of
+~<opensmtpd-filter-configuration>~, and/or
+~<opensmtpd-filter-phase-configuration>~ records to filter email/spam. Also
+numerous records' fieldnames use ~<opensmtpd-table-configuration>~ to hold lists
+or key value pairs of data.
+
+A simple example configuration is below:
+
+#+BEGIN_SRC scheme
+(let ((smtp.gnu.org (opensmtpd-pki-configuration
+                        (domain "smtp.gnu.org")
+                        (cert "file.cert")
+                        (key "file.key"))))
+  (service opensmtpd-service-type
+           (opensmtpd-configuration
+            (listen-ons (list
+                         (opensmtpd-listen-on-configuration
+                          (pki smtp.gnu.org))
+                         (opensmtpd-listen-on-configuration
+                          (pki smtp.gnu.org)
+                          (secure-connection "smtps"))))
+            (matches (list
+                      (opensmtpd-match-configuration
+                       (action
+                        (opensmtpd-action-local-delivery-configuration
+                         (name "local-delivery"))))
+                      (opensmtpd-match-configuration
+                       (action
+                        (opensmtpd-action-relay-configuration
+                         (name "relay")))))))))
+#+END_SRC
+
+- Scheme Variable: opensmtpd-service-type
+
+  Service type for the OpenSMTPD ([[https://www.opensmtpd.org][https://www.opensmtpd.org]]) email server. The
+  value for this service type is a  ~<opensmtpd-configuration>~ record.
+
+- Data Type: opensmtpd-configuration
+
+  Data type representing the configuration of OpenSMTPD.
+
+  - ~package~ (default: ~opensmtpd~)
+
+       The OpenSMTPD package to use.
+
+  - ~config-file~ (default: ~#f~)
+
+    File-like object of the OpenSMTPD configuration file to use.  By default it
+    listens on the loopback network interface, and allows for mail from users
+    and daemons on the local machine, as well as permitting email to remote
+    servers.  Run ~man smtpd.conf~ for more information.
+
+  - ~bounce~ (default: ~(list "4h")~)
+
+    ~bounce~ is a list of strings, which send warning messages to the envelope
+    sender when temporary delivery failures cause a message to remain in the
+    queue for longer than string _delay_. Each string _delay_ parameter consists
+    of a string beginning with a positive decimal integer and a unit s, m, h,
+    or d. At most four delay parameters can be specified.
+
+  - ~listen-ons~ (default: ~(list (opensmtpd-listen-on-configuration))~ )
+
+    ~listen-ons~ is a list of ~<opensmtpd-listen-on-configuration>~ records.
+    This list details what interfaces and ports OpenSMTPD listens on as well as
+    other information.
+
+  - ~listen-on-socket~ (default: ~(opensmtpd-listen-on-socket-configuration-configuration)~ )
+
+    Listens for incoming connections on the Unix domain socket.
+
+  - ~includes~ (default: ~#f~)
+
+    # TODO ~includes~ should support a list of string filenames or gexps.
+    ~includes~ is a list of string _filenames_. Each filename's contents is
+    additional configuration that is inserted into the top of the configuration
+    file.
+
+  - ~matches~ default:
+
+    #+BEGIN_SRC scheme
+    (list (opensmtpd-match-configuration
+           (action (opensmtpd-action-local-delivery-configuration
+                    (name "local")
+                    (method "mbox")))
+           (for (opensmtpd-option-configuration
+                 (option "for local"))))
+          (opensmtpd-match-configuration
+           (action (opensmtpd-action-relay-configuration
+                    (name "outbound")))
+           (from (opensmtpd-option-configuration
+                  (option "from local")))
+           (for (opensmtpd-option-configuration
+                 (option "for any")))))
+    #+END_SRC
+
+    ~matches~ is a list of ~<opensmtpd-match-configuration>~ records, which
+    matches incoming mail and sends it to a correspending action. The match
+    records are evaluated sequentially, with the first match winning. If an
+    incoming mail does not match any match records, then it is rejected.
+
+  # TODO when the code supports mda-wrappers, add in this documentation.
+  # - ~mda-wrappers~
+
+  - ~mta-max-deferred~ (default: ~100~)
+
+   When delivery to a given host is suspended due to temporary failures, cache
+   at most _number_ envelopes for that host such that they can be delivered as
+   soon as another delivery succeeds to that host. The default is 100.
+
+  - ~queue~ (default: ~#f~)
+
+    ~queue~ expects an ~<opensmtpd-queue-configuration>~ record. With it, one may
+    compress and encrypt queue-ed emails as well as set the default expiration
+    time for temporarily undeliverable messages.
+
+  - ~smtp~ (default: ~#f~)
+
+    ~smtp~ expects an ~<opensmtpd-smtp-configuration>~ record, which lets one
+    specifiy how large email may be along with other settings.
+
+  - ~srs~ (default: ~#f~)
+
+    ~srs~ expects an ~<opensmtpd-srs-configuration>~ record, which lets one set
+    up SRS, the Sender Rewritting Scheme.
+
+- Data Type: opensmtpd-listen-on-configuration
+
+  Data type representing the configuration of an
+  ~<opensmtpd-listen-on-configuration>~. Listen on the fieldname ~interface~ for
+  incoming connections, using the same syntax as for ifconfig(8). The interface
+  parameter may also be an string interface group, an string IP address, or a
+  string domain name. Listening can optionally be restricted to a specific
+  address fieldname ~family~, which can be either "inet4" or "inet6".
+
+  - ~interface~ (default: "lo")
+
+    The string interface to listen for incoming connections. These interface can
+    usually be found by the command ~ip link~.
+
+  - ~family~ (default: ~#f~)
+
+    The string IP family to use.  Valid strings are "inet4" or "inet6".
+
+  - ~auth~ (default: ~#f~)
+
+    Support SMTPAUTH: clients may only start SMTP transactions after successful
+    authentication. If ~auth~ is ~#t~, then users are authenticated against
+    their own normal login credentials. Alternatively ~auth~ may be an
+    ~<opensmtpd-table-configuration>~ whose users are authenticated against
+    their passwords.
+
+  - ~auth-optional~ (default: ~#f~)
+
+    Support SMTPAUTH optionally: clients need not authenticate, but may do so.
+    This allows the ~<opensmtpd-listen-on-configuration>~ to both accept
+    incoming mail from untrusted senders and permit outgoing mail from
+    authenticated users (using ~<opensmtpd-match-configuration>~ fieldname
+    ~auth~). It can be used in situations where it is not possible to listen on
+    a separate port (usually the submission port, 587) for users to
+    authenticate.
+
+  - ~filters~ (default: ~#f~)
+
+    A list of one or many ~<opensmtpd-filter-configuration>~ or
+    ~<opensmtpd-filter-phase-configuration>~ records. The filters are applied
+    sequentially. These records listen and filter on connections handled by this
+    listener.
+
+  - ~hostname~ (default: ~#f~)
+
+    Use string "hostname" in the greeting banner instead of the default server
+    name.
+
+  - ~hostnames~ (default: ~#f~)
+
+    Override the server name for specific addresses. Use a
+    ~<opensmtpd-table-configuration>~ containing a mapping of string IP
+    addresses to hostnames. If the address on which the connection arrives
+    appears in the mapping, the associated hostname is used.
+
+  - ~mask-src~ (default: ~#f~)
+
+    If ~#t~, then omit the from part when prepending “Received” headers.
+
+  - ~disable-dsn~ (default: ~#f~)
+
+    When ~#t~, then disable the DSN (Delivery Status Notification) extension.
+
+  - ~pki~ (default: ~#f~)
+
+    For secure connections, use an ~<opensmtpd-pki-configuration>~
+    to prove a mail server's identity.
+
+  - ~port~ (default: ~#f~)
+
+    Listen on the _integer_ port instead of the default port of 25.
+
+  - ~proxy-v2~ (default: ~#f~)
+
+    If ~#t~, then support the PROXYv2 protocol, rewriting appropriately source
+    address received from proxy.
+
+  - ~received-auth~ (default: ~#f~)
+
+    If ~#t~, then in “Received” headers, report whether the session was
+    authenticated and by which local user.
+
+  - ~senders~ (default: ~#f~)
+
+    Look up the authenticated user in the supplied
+    ~<opensmtpd-table-configuration>~ to find the email addresses that user is
+    allowed to submit mail as.
+
+  - ~secure-connection~ (default: ~#f~)
+
+    This is a string of one of these options:
+
+    |----------------------+---------------------------------------------|
+    | "smtps"              | Support SMTPS, by default on port 465.      |
+    |----------------------+---------------------------------------------|
+    | "tls"                | Support STARTTLS, by default on port 25.    |
+    |----------------------+---------------------------------------------|
+    | "tls-require-verify" | Like tls, but force clients to establish    |
+    |                      | a secure connection before being allowed to |
+    |                      | start an SMTP transaction.  With the verify |
+    |                      | option, clients must also provide a valid   |
+    |                      | certificate to establish an SMTP session.   |
+    |----------------------+---------------------------------------------|
+
+  - ~tag~ (default: ~#f~)
+
+    Clients connecting to the listener are tagged with the given string tag.
+
+- Data Type: opensmtpd-listen-on-socket-configuration
+
+  Data type representing the configuration of an
+  ~<opensmtpd-listen-on-socket-configuration>~. Listen for incoming SMTP
+  connections on the Unix domain socket =/var/run/smtpd.sock=. This is done by
+  default, even if the directive is absent.
+
+  - ~filters~ (default: ~#f~)
+
+    A list of one or many ~<opensmtpd-filter-configuration>~ or
+    ~<opensmtpd-filter-phase-configuration>~ records. These filter incoming
+    connections handled by this listener.
+
+  - ~mask-src~ (default: ~#f~)
+
+    If ~#t~, then omit the from part when prepending “Received” headers.
+
+  - ~tag~ (default: ~#f~)
+
+    Clients connecting to the listener are tagged with the given string tag.
+
+- Data Type: opensmtpd-match-configuration
+
+  This data type represents the configuration of an
+  ~<opensmtpd-match-configuration>~ record.
+
+  If at least one mail envelope matches the options of one match record, receive
+  the incoming message, put a copy into each matching envelope, and atomically
+  save the envelopes to the mail spool for later processing by the respective
+  ~<opensmtpd-action-configuration>~ found in fieldname ~action~.
+
+  - ~action~ (default: ~#f~)
+
+    If mail matches this match configuration, then do this action. Valid values
+    include ~<opensmtpd-action-local-delivery-configuration>~ or
+    ~<opensmtpd-action-relay-configuration>~.
+
+  - ~options~ (default: ~#f~) ~<opensmtpd-option-configuration>~
+    The fieldname 'option' is a list of unique
+    ~<opensmtpd-option-configuration>~ records.
+
+    Each ~<opensmtpd-option-configuration>~ record's fieldname 'option' has some
+    mutually exclusive options: there can be one "for" and one "from" option.
+
+    |---------------------------+--------------------------------|
+    | for                       | from                           |
+    |---------------------------+--------------------------------|
+    | use one of the following: | only use one of the following: |
+    |---------------------------+--------------------------------|
+    | "for any"                 | "from any"                     |
+    | "for local"               | "from auth"                    |
+    | "for domain"              | "from local"                   |
+    | "for rcpt-to"             | "from mail-from"               |
+    |                           | "from socket"                  |
+    |                           | "from src"                     |
+    |---------------------------+--------------------------------|
+
+    The following matching options are supported and can all be negated via (not
+    #t). The options that support a table (anything surrounded with '<' and '>'
+    eg: <table>), also support specifying regex via (regex #t).
+
+    - =for any=
+
+        Specify that session may address any destination.
+
+    - =for local=
+
+        Specify that session may address any local domain.  This is the default,
+        and may be omitted.
+
+    - =for domain _domain_ | <domain>=
+
+        Specify that session may address the string or list table _domain_.
+
+    - =for rcpt-to _recipient_ | <recipient>=
+
+        Specify that session may address the string or list table _recipient_.
+
+    - =from any=
+
+        Specify that session may originate from any source.
+
+    - =from auth=
+
+        Specify that session may originate from any authenticated user, no matter
+        the source IP address.
+
+    - =from auth _user_ | <user>=
+
+        Specify that session may originate from authenticated _user_ or user list
+        user, no matter the source IP address.
+
+    - =from local=
+
+        Specify that session may only originate from a local IP address, or from
+        the local enqueuer.  This is the default, and may be omitted.
+
+    - =from mail-from _sender_ | <sender>=
+
+        Specify that session may originate from _sender_ or table _sender_, no
+        matter the source IP address.
+
+    - =from rdns=
+
+        Specify that session may only originate from an IP address that resolves
+        to a reverse DNS.
+
+    - =from rdns _hostname_ | <hostname>=
+
+        Specify that session may only originate from an IP address that resolves
+        to a reverse DNS matching string or list string _hostname_.
+
+    - =from socket=
+
+        Specify that session may only originate from the local enqueuer.
+
+    - =from src _address_ | <address>=
+
+        Specify that session may only originate from string or list table address
+        which can be a specific _address_ or a subnet expressed in CIDR-notation.
+
+    - =auth=
+
+      Matches transactions which have been authenticated.
+
+    - =auth _username_ | <username>=
+
+      Matches transactions which have been authenticated for user or user list
+      _username_.
+
+    - =helo _helo-name_ | <helo-name>=
+
+      Specify that session's HELO / EHLO should match the string or list table
+      _helo-name_.
+
+    - =mail-from _sender_ | <sender>=
+
+      Specify that transactions's MAIL FROM should match the string or list
+      table _sender_.
+
+    - =rcpt-to _recipient_ | <recipient>=
+
+      Specify that transaction's RCPT TO should match the string or list table
+      _recipient_.
+
+    - =tag tag=
+         Matches transactions tagged with the given _tag_.
+
+    - =tls=
+         Specify that transaction should take place in a TLS channel.
+
+    Here is a simple example:
+    #+BEGIN_SRC scheme
+    (opensmtpd-option-configuration
+     (not #t)
+     (regex #f)
+     (option "for domain")
+     (data (opensmtpd-table-configuration
+            (name "domain-table")
+            (data (list "gnu.org" "dismail.de")))))
+    #+END_SRC
+
+    The mail must NOT come from the domains =gnu.org= or =dismail.de=.
+
+  - Data Type: opensmtpd-option-configuration
+
+- Data Type: opensmtpd-action-local-delivery-configuration
+
+  This data type represents the configuration of an
+  ~<opensmtpd-action-local-delivery-configuration>~ record.
+
+  - ~name~ (default: ~#f~)
+
+    ~name~ is the string name of the relay action.
+
+  - ~method~ (default: ~"mbox"~)
+
+    The email delivery option.  Valid options are:
+
+    - ~"mbox"~
+
+      Deliver the message to the user's mbox with mail.local(8).
+
+    - ~"expand-only"~
+
+      Only accept the message if a delivery method was specified in an aliases
+      or _.forward file_.
+
+    - ~"forward-only"~
+
+      Only accept the message if the recipient results in a remote address after
+      the processing of aliases or forward file.
+
+    - ~<opensmtpd-lmtp-configuration>~
+
+      Deliver the message to an LMTP server at
+      ~<opensmtpd-lmtp-configuration>~'s fieldname ~destination~. The location
+      may be expressed as string host:port or as a UNIX socket. Optionally,
+      ~<opensmtpd-lmtp-configuration>~'s fieldname ~rcpt-to~ might be specified
+      to use the recipient email address (after expansion) instead of the local
+      user in the LMTP session as RCPT TO.
+
+    - ~<opensmtpd-maildir-configuration>~
+
+      Deliver the message to the maildir in
+      ~<opensmtpd-maildir-configuration>~'s fieldname ~pathname~ if specified,
+      or by default to =~/Maildir=.
+
+      The pathname may contain format specifiers that are expanded before use
+      (see the below section about Format Specifiers).
+
+      If ~<opensmtpd-maildir-configuration>~'s record fieldname ~junk~ is ~#t~,
+      then message will be moved to the ‘Junk’ folder if it contains a positive
+      ‘X-Spam’ header. This folder will be created under fieldname ~pathname~ if
+      it does not yet exist.
+
+    - ~<opensmtpd-mda-configuration>~
+
+      Delegate the delivery to the ~<opensmtpd-mda-configuration>~'s fieldname
+      ~command~ (type string) that receives the message on its standard input.
+
+      The ~command~ may contain format specifiers that are expanded before use
+      (see Format Specifiers).
+
+  - ~alias~ (default: ~#f~)
+
+    Use the mapping table for aliases expansion. ~alias~ is an
+    ~<opensmtpd-table-configuration>~.
+
+  - ~ttl~ (default: ~#f~)
+
+    ~ttl~ is a string specify how long a message may remain in the queue.  It's
+    format is =n{s|m|h|d}=.  eg: "4m" is four minutes.
+
+  - ~user~ (default: ~#f~ )
+
+    ~user~ is the string username for performing the delivery, to be looked up
+    with getpwnam(3).
+
+    This is used for virtual hosting where a single username is in charge of
+    handling delivery for all virtual users.
+
+    This option is not usable with the mbox delivery method.
+
+  - ~userbase~ (default: ~#f~)
+
+    ~userbase~ is an ~<opensmtpd-table-configuration>~ record for mapping user
+    lookups instead of the getpwnam(3) function.
+
+    The fieldnames ~user~ and ~userbase~ are mutually exclusive.
+
+  - ~virtual~ (default: ~#f~)
+
+    ~virtual~ is an ~<opensmtpd-table-configuration>~ record is used for virtual
+    expansion.
+    # TODO man 5 smtpd.conf says "The aliasing table format is described in
+    # table(5)." What is virtual expansion? I do NOT know how to use ~virtual~
+    # properly. What sort of <opensmtpd-table-configuration> do I need? does the
+    # below work?
+    # (opensmtpd-table (name "virtual") (data '(("joshua" . "jbranso@dismail.de"))))
+
+  # TODO fix this ~wrapper documentation~.  Should it accept an
+  # <opensmtpd-mda-configuration> ?  If so, then I need to write an <opensmtpd-mda-configuration>
+  # - ~wrapper~ (default: )
+
+  # TODO double check that these options are all correct
+
+- Data Type: opensmtpd-action-relay-configuration
+
+   This data type represents the configuration of an
+   ~<opensmtpd-action-relay-configuration>~ record.
+
+  - ~name~ (default: ~#f~)
+
+    ~name~ is the string name of the relay action.
+
+  - ~backup~ (default: ~#f~)
+
+    When ~#t~, operate as a backup mail exchanger delivering messages to any
+    mail exchanger with higher priority.
+
+  - ~backup-mx~ (default: ~#f~)
+
+    Operate as a backup mail exchanger delivering messages to any mail exchanger
+    with higher priority than mail exchanger identified as string name.
+
+  - ~helo~ (default: ~#f~)
+
+    Advertise string heloname as the hostname to other mail exchangers during
+    the HELO phase.
+
+  - ~helo-src~ (default: ~#f~ )
+
+     Use the mapping ~<openmstpd-table-configuration>~ to look up a hostname
+    matching the source address, to advertise during the HELO phase.
+
+  - ~domain~ (default: ~#f~)
+
+    Do not perform MX lookups but look up destination domain in an
+    ~<opensmtpd-table-configuration>~ and use matching relay url as relay host.
+
+  - ~host~ (default: ~#f~)
+
+    Do not perform MX lookups but relay messages to the relay host described by
+    the string relay-url. The format for relay-url is
+    =[proto://[label@]]host[:port]=. The following protocols are available:
+
+     |------------+----------------------------------------------------------------|
+     | smtp       | Normal SMTP session with opportunistic STARTTLS (the default). |
+     | smtp+tls   | Normal SMTP session with mandatory STARTTLS.                   |
+     | smtp+notls | Plain text SMTP session without TLS.                           |
+     | lmtp       | LMTP session.  port is required.                               |
+     | smtps      | SMTP session with forced TLS on connection, default port is    |
+     |            | 465.                                                           |
+     |------------+----------------------------------------------------------------|
+
+    Unless noted, port defaults to 25.
+
+    The label corresponds to an entry in a credentials table, as documented in
+    =table(5)=. It is used with the ="smtp+tls"= and ="smtps"= protocols for
+    authentication. Server certificates for those protocols are verified by
+    default.
+
+  - ~pki~ (default: ~#f~)
+
+    For secure connections, use the certificate associated with
+    ~<opensmtpd-pki-configuration>~ (declared in a pki directive) to prove the
+    client's identity to the remote mail server.
+
+  - ~srs~ (default: ~#f~)
+
+    If ~#t~, then when relaying a mail resulting from a forward, use the Sender
+    Rewriting Scheme to rewrite sender address.
+
+  - ~tls~ (default: ~#f~) boolean or string "no-verify"
+
+    When ~#t~, Require TLS to be used when relaying, using mandatory STARTTLS by
+    default. When used with a smarthost, the protocol must not be
+    ="smtp+notls://"=. When string ~"no-verify"~, then do not require a valid
+    certificate.
+
+  - ~auth~ (default: ~#f~) ~<opensmtpd-table-configuration>~
+
+    Use the alist ~<opensmtpd-table-configuration>~ for connecting to relay-url
+    using credentials. This option is usable only with fieldname ~host~ option.
+
+  - ~mail-from~ (default: ~#f~) string
+
+    Use the string _mailaddress_ as MAIL FROM address within the SMTP transaction.
+
+  - ~src~ (default: ~#f~) string | ~<opensmtpd-table-configuration>~
+
+    Use the string or ~<opensmtpd-table-configuration>~ sourceaddr for the
+    source IP address, which is useful on machines with multiple interfaces. If
+    the list contains more than one address, all of them are used in such a way
+    that traffic is routed as efficiently as possible.
+
+- Data Type: opensmtpd-filter-configuration
+
+   This data type represents the configuration of an
+   ~<opensmtpd-filter-configuration>~. This is the filter record one should use
+   if they want to use an external package to filter email eg: rspamd or
+   spamassassin.
+
+  - ~name~ (default: ~#f~)
+
+    The string name of the filter.
+
+  - ~proc~ (default: ~#f~)
+
+    # TODO let ~proc~ be a gexp
+    The string command or process name.  If ~proc-exec~ is ~#t~, ~proc~ is
+    treated as a command to execute.  Otherwise, it is a process name.
+
+  - ~proc-exec~ (default: ~#f~)
+
+- Data Type: opensmtpd-filter-phase-configuration
+
+   This data type represents the configuration of an
+   ~<opensmtpd-filter-phase-configuration>~.
+
+   In a regular workflow, smtpd(8) may accept or reject a message based only on
+   the content of envelopes. Its decisions are about the handling of the message,
+   not about the handling of an active session.
+
+   Filtering extends the decision making process by allowing smtpd(8) to stop at
+   each phase of an SMTP session, check that options are met, then decide if a
+   session is allowed to move forward.
+
+   With filtering via an ~<opensmtpd-filter-phase-configuration>~ record, a
+   session may be interrupted at any phase before an envelope is complete. A
+   message may also be rejected after being submitted, regardless of whether the
+   envelope was accepted or not.
+
+  - ~name~ (default: ~#f~)
+
+    The string name of the filter phase.
+
+  - ~phase-name~ (default: ~#f~)
+
+    The string name of the phase. Valid values are:
+
+   |-------------+-----------------------------------------------|
+   | "connect"   | upon connection, before a banner is displayed |
+   |-------------+-----------------------------------------------|
+   | "helo"      | after HELO command is submitted               |
+   |-------------+-----------------------------------------------|
+   | "ehlo"      | after EHLO command is submitted               |
+   |-------------+-----------------------------------------------|
+   | "mail-from" | after MAIL FROM command is submitted          |
+   |-------------+-----------------------------------------------|
+   | "rcpt-to"   | after RCPT TO command is submitted            |
+   |-------------+-----------------------------------------------|
+   | "data"      | after DATA command is submitted               |
+   |-------------+-----------------------------------------------|
+   | "commit"    | after message is fully is submitted           |
+   |-------------+-----------------------------------------------|
+
+  - ~options~ (default ~#f~)
+
+    A list of unique ~<opensmtpd-option-configuration>~ records.
+
+    At each phase, various options, specified by a list of
+    ~<opensmtpd-option-configuration>~, may be checked. The
+    ~<opensmtpd-option-configuration>~'s fieldname 'option' values of: "fcrdns",
+    "rdns", and "src" data are available in all phases, but other data must have
+    been already submitted before they are available. Options with a =<table>=
+    next to them require the ~<opensmtpd-option-configuration>~'s fieldname
+    ~data~ to be an ~<opensmtpd-table-configuration>~. There are the available
+    options:
+
+    |-------------------+----------------------------------------|
+    | fcrdns            | forward-confirmed reverse DNS is valid |
+    |-------------------+----------------------------------------|
+    | rdns              | session has a reverse DNS              |
+    |-------------------+----------------------------------------|
+    | rdns <table>      | session has a reverse DNS in table     |
+    |-------------------+----------------------------------------|
+    | src <table>       | source address is in table             |
+    |-------------------+----------------------------------------|
+    | helo <table>      | helo name is in table                  |
+    |-------------------+----------------------------------------|
+    | auth              | session is authenticated               |
+    |-------------------+----------------------------------------|
+    | auth <table>      | session username is in table           |
+    |-------------------+----------------------------------------|
+    | mail-from <table> | sender address is in table             |
+    |-------------------+----------------------------------------|
+    | rcpt-to <table>   | recipient address is in table          |
+    |-------------------+----------------------------------------|
+
+    These conditions may all be negated by setting
+    ~<opensmtpd-option-configuration>~'s fieldname ~not~ to ~#t~.
+
+    Any conditions that require a table may indicate that tables include regexs
+    setting ~<opensmtpd-option-configuration>~'s fieldname ~regex~ to ~#t~.
+
+  - ~decision~
+
+    A string decision to be taken. Some decisions require an ~message~ or
+    ~value~. Valid strings are:
+
+   |----------------------+------------------------------------------------|
+   | "bypass"             | the session or transaction bypasses filters    |
+   |----------------------+------------------------------------------------|
+   | "disconnect" message | the session is disconnected with message       |
+   |----------------------+------------------------------------------------|
+   | "junk"               | the session or transaction is junked, i.e., an |
+   |                      | ‘X-Spam: yes’ header is added to any messages  |
+   |----------------------+------------------------------------------------|
+   | "reject" message     | the command is rejected with message           |
+   |----------------------+------------------------------------------------|
+   | "rewrite" value      | the command parameter is rewritten with value  |
+   |----------------------+------------------------------------------------|
+
+    Decisions that involve a message require that the message be RFC valid,
+    meaning that they should either start with a 4xx or 5xx status code.
+    Descisions can be taken at any phase, though junking can only happen before
+    a message is committed.
+
+  - ~message~ (default ~#f~)
+
+    A string message beginning with a 4xx or 5xx status code.
+
+  - ~value~ (default: ~#f~)
+
+   A number value.  ~value~ and ~message~ are mutually exclusive.
+
+- Data Type: opensmtpd-option-configuration
+
+  This data type represents the configuration of an
+  ~<opensmtpd-option-configuration>~, which is used by
+  ~<opensmtpd-filter-phase-configuration>~ and ~<opensmtpd-match-configuration>~
+  to match various options for email.
+
+  - ~conditition~ (default ~#f~)
+
+    A string option to be taken. Some options require a string or an
+    ~<opensmtpd-table-configuration>~ via the fieldname data. When the option
+    record is used inside of an ~<opensmtpd-filter-phase-configuration>~, then
+    valid strings are:
+
+    At each phase, various options may be matched. The fcrdns, rdns, and src
+    data are available in all phases, but other data must have been already
+    submitted before they are available.
+
+    |---------------------+----------------------------------------|
+    | "fcrdns"            | forward-confirmed reverse DNS is valid |
+    | "rdns"              | session has a reverse DNS              |
+    | "rdns" <table>      | session has a reverse DNS in table     |
+    | "src" <table>       | source address is in table             |
+    | "helo" <table>      | helo name is in table                  |
+    | "auth"              | session is authenticated               |
+    | "auth" <table>      | session username is in table           |
+    | "mail-from" <table> | sender address is in table             |
+    | "rcpt-to" <table>   | recipient address is in table          |
+    |---------------------+----------------------------------------|
+
+    When ~<opensmtpd-option-configuration>~ is used inside of an
+    ~<opensmtpd-match-configuration>~, then valid strigs for fieldname ~option~
+    are: "for", "for any", "for local", "for domain", "for rcpt-to", "from any"
+    "from auth", "from local", "from mail-from", "from rdns", "from socket",
+    "from src", "auth", "helo", "mail-from", "rcpt-to", "tag", or "tls".
+
+  - ~data~ (default ~#f~) ~<opensmtpd-table-configuration>~
+
+    Some options require a table to be present. One would specify that table
+    here.
+  - ~regex~ (default: ~#f~) boolean
+
+    Any options using a table may indicate that tables hold regex by
+    prefixing the table name with the keyword regex.
+  - ~not~ (default: ~#f~) boolean
+
+    When ~#t~, this option record is negated.
+
+- Data Type: opensmtpd-table-configuration
+
+  This data type represents the configuration of an
+  ~<opensmtpd-table-configuration>~.
+
+  - ~name~ (default ~#f~)
+
+    ~name~ is the name of the ~<opensmtpd-table-configuration>~ record.
+
+  - ~data~ (default: ~#f~)
+
+    ~data~ expects a list of strings or an alist, which is a list of
+    cons cells.  eg: ~(data (list ("james" . "password")))~ OR
+    ~(data (list ("gnu.org" "fsf.org")))~.
+
+- Data Type: opensmtpd-pki-configuration
+
+  This data type represents the configuration of an
+  ~<opensmtpd-pki-configuration>~.
+
+  - ~domain~ (default ~#f~)
+
+    ~domain~ is the string name of the ~<opensmtpd-pki-configuration>~ record.
+
+  - ~cert~ (default: ~#f~)
+
+    ~cert~ (default: ~#f~)
+
+    ~cert~ is the string certificate filename to use for this pki.
+
+  - ~key~ (default: ~#f~)
+
+    ~key~ is the string certificate falename to use for this pki.
+
+  - ~dhe~ (default: ~"none"~)
+
+    Specify the DHE string parameter to use for DHE cipher suites with host
+    pkiname. Valid parameter values are "none", "legacy", or "auto". For "legacy", a
+    fixed key length of 1024 bits is used, whereas for "auto", the key length is
+    determined automatically. The default is "none", which disables DHE cipher
+    suites.
+
+- Data Type: opensmtpd-maildir-configuration
+
+  - ~pathname~ (default: ~"~/Maildir"~)
+
+    Deliver the message to the maildir if pathname if specified, or by default
+    to =~/Maildir=.
+
+    The pathname may contain format specifiers that are expanded before use
+    (see FORMAT SPECIFIERS).
+
+  - ~junk~ (default: ~#f~)
+
+    If the junk argument is ~#t~, then the message will be moved to the =‘Junk’=
+    folder if it contains a positive =‘X-Spam’= header. This folder will be
+    created under pathname if it does not yet exist.
+
+- Data Type: opensmtpd-mda-configuration
+  # Do we need a dataypte for mda configuration?
+  # this could just be a gexp in the fieldname opensmtpd-configuration-mda
+
+  - ~name~
+
+    The string name for this MDA command.
+
+  - ~command~
+
+    Delegate the delivery to a command that receives the message on its standard
+    input.
+
+    The command may contain format specifiers that are expanded before use (see
+    FORMAT SPECIFIERS).
+
+- Data Type: opensmtpd-queue-configuration
+
+  - ~compression~ (default ~#f~)
+
+    Store queue files in a compressed format. This may be useful to save disk
+    space.
+  - ~encryption~ (default ~#f~)
+
+    Encrypt queue files with EVP_aes_256_gcm(3). If no key is specified, it is
+    read with getpass(3). If the string stdin or a single dash (‘-’) is given
+    instead of a key, the key is read from the standard input.
+  - ~ttl-delay~ (default ~#f~)
+
+    Set the default expiration time for temporarily undeliverable messages,
+    given as a positive decimal integer followed by a unit s, m, h, or d. The
+    default is four days ("4d").
+
+- Data Type: opensmtpd-smtp-configuration
+
+  Data type representing an ~<opensmtpd-smtp-configuration>~ record.
+
+  - ~ciphers~ (default: ~#f~)
+
+    Set the control string for SSL_CTX_set_cipher_list(3).  The default is
+             "HIGH:!aNULL:!MD5".
+  - ~limit-max-mails~ (default: ~100~)
+
+    Limit the number of messages to count for each sessio
+  - ~limit-max-rcpt~ (default: ~1000~)
+
+    Limit the number of recipients to count for each transaction.
+  - ~max-message-size~ (default: ~35M~)
+
+    Reject messages larger than size, given as a positive number of bytes or as
+    a string to be parsed with scan_scaled(3).
+  - ~sub-addr-delim character~ (default: ~+~)
+
+    When resolving the local part of a local email address, ignore the ASCII
+    character and all characters following it. This is helpful for email
+    filters. ="admin+bills@gnu.org"= is the same email address as
+    ="admin@gnu.org"=. BUT an email filter can filter emails addressed to first
+    email address into a 'Bills' email folder.
+
+- Data Type: opensmtpd-srs-configuration
+
+  - ~key~ (default: ~#f~)
+
+     Set the secret key to use for SRS, the Sender Rewriting Scheme.
+
+  - ~backup-key~ (default: ~#f~)
+
+    Set a backup secret key to use as a fallback for SRS. This can be used to
+    implement SRS key rotation.
+  - ~ttl-delay~ (default: ~"4d"~)
+
+    Set the time-to-live delay for SRS envelopes. After this delay, a bounce
+    reply to the SRS address will be discarded to limit risks of forged
+    addresses.
+
+- Format Specifiers
+
+  Some configuration records support expansion of their parameters at
+  runtime. Such records (for example
+  ~<opensmtpd-maildir-configuration>~, ~<opensmtpd-mda-configuration>~) may use
+  format specifiers which are expanded before delivery or relaying. The
+  following formats are currently supported:
+
+    |---------------------+-------------------------------------------------------|
+    | =%{sender}=         | sender email address, may be empty string             |
+    | =%{sender.user}=    | user part of the sender email address, may be empty   |
+    | =%{sender.domain}=  | domain part of the sender email address, may be empty |
+    | =%{rcpt}=           | recipient email address                               |
+    | =%{rcpt.user}=      | user part of the recipient email address              |
+    | =%{rcpt.domain}=    | domain part of the recipient email address            |
+    | =%{dest}=           | recipient email address after expansion               |
+    | =%{dest.user}=      | user part after expansion                             |
+    | =%{dest.domain}=    | domain part after expansion                           |
+    | =%{user.username}=  | local user                                            |
+    | =%{user.directory}= | home directory of the local user                      |
+    | =%{mbox.from}=      | name used in mbox From separator lines                |
+    | =%{mda}=            | mda command, only available for mda wrappers          |
+    |---------------------+-------------------------------------------------------|
+
+  Expansion formats also support partial expansion using the optional bracket notations
+  with substring offset.  For example, with recipient domain =“example.org”=:
+
+    |------------------------+----------------------|
+    | =%{rcpt.domain[0]}=    | expands to “e”       |
+    | =%{rcpt.domain[1]}=    | expands to “x”       |
+    | =%{rcpt.domain[8:]}=   | expands to “org”     |
+    | =%{rcpt.domain[-3:]}=  | expands to “org”     |
+    | =%{rcpt.domain[0:6]}=  | expands to “example” |
+    | =%{rcpt.domain[0:-4]}= | expands to “example” |
+    |------------------------+----------------------|
+
+  In addition, modifiers may be applied to the token.  For example, with recipient
+  =“User+Tag@Example.org”=:
+
+    |--------------------------+-----------------------------------|
+    | =%{rcpt:lowercase}=      | expands to “user+tag@example.org” |
+    | =%{rcpt:uppercase}=      | expands to “USER+TAG@EXAMPLE.ORG” |
+    | =%{rcpt:strip}=          | expands to “User@Example.org”     |
+    | =%{rcpt:lowercasestrip}= | expands to “user@example.org”     |
+    |--------------------------+-----------------------------------|
+
+  For security concerns, expanded values are sanitized and potentially dangerous
+  characters are replaced with ‘:’. In situations where they are desirable, the
+  “raw” modifier may be applied. For example, with recipient
+  =“user+t?g@example.org”=:
+
+    |---------------+-----------------------------------|
+    | =%{rcpt}=     | expands to “user+t:g@example.org” |
+    | =%{rcpt:raw}= | expands to “user+t?g@example.org” |
+    |---------------+-----------------------------------|
+*** some example ~<opensmtpd-configurations>~ that are probably out of date
+
+      #+BEGIN_SRC scheme
+
+;;this works! (opensmtpd-configuration->mixed-text-file (opensmtpd-configuration (smtp (opensmtpd-smtp-configuration (limit-max-rcpt 10)))))
+
+;; (tables (list
+;;          (opensmtpd-table-configuration
+;;           (name "aliases")
+;;           (data
+;;            (list
+;;             (cons "webmaster" "root")
+;;             (cons "postmaster" "root")
+;;             (cons "abuse" "root"))))
+;;
+;;          (opensmtpd-table-configuration
+;;           (name "vdoms")
+;;           (data (list "gnucode.me"
+;;                         "gnu-hurd.com")))
+;;          (opensmtpd-table-configuration
+;;           (name (opensmtpd-table-configuration
+;;                   (name "virtual")
+;;                   (data (list "root" "postmaster@gnu.org"))))
+;;           (data (list (cons "joshua@gnucode.me" "joshua")
+;;                         (cons "jbranso@gnucode.me" "joshua")
+;;                         (cons "postmaster@gnucode.me" "joshua"))))))
+
+            ;; (filter-chains
+            ;;  (list
+            ;;   (opensmtpd-filter-chain
+            ;;    (name "dropDumbEmails")
+            ;;    (filter-names (list "nofcrdnsDisconnect"
+            ;;                        "nordnsDisconnect")))))
+            ;; (filter-phases
+            ;;  (list (opensmtpd-filter-phase-configuration
+            ;;         (name "nofcrdnsDisconnect")
+            ;;         (phase-name "connect")
+            ;;         (options (list "!fcrdns"))
+            ;;         (decision "disconnect")
+            ;;         (message "You have not set up forward confirmed DNS."))
+            ;;        (opensmtpd-filter-phase-configuration
+            ;;         (name "nordnsDisconnect")
+            ;;         (phase-name "connect")
+            ;;         (options (list "!rdns"))
+            ;;
+            ;;         (decision "reject")
+            ;;         (message "You have not set up reverse DNS."))))
+            ;;
+(define example-opensmtpd-config-smaller
+  (opensmtpd-configuration
+   (listen-ons
+    (list
+     ;; this forum help suggests that I listen on 0.0.0.0 and NOT eth0
+     ;; https://serverfault.com/questions/726795/opensmtpd-wont-work-at-reboot
+     ;; this listens for email from the outside world
+     ;; this lets local users logged into the system via ssh send email
+     (opensmtpd-listen-on-configuration
+      (interface "wlp2s0")
+      (port 465))))
+   (matches (list
+             (opensmtpd-match-configuration
+              (name "maildir")
+              (action (opensmtpd-action-local-delivery-configuration
+                       (method (opensmtpd-maildir-configuration
+                                (pathname "/home/%{rcpt.user}/Maildir")
+                                (junk #t)))
+                       (virtual (opensmtpd-table-configuration
+                                 (name "virtual")
+                                 (data (list "root" "james@gnu.org"))))))
+              (for (opensmtpd-option-configuration
+                    (option "for local"))))))))
+
+(define example-opensmtpd-config-small
+  (let ([interface "wlp2s0"]
+        [creds (opensmtpd-table-configuration
+                (name "creds")
+                (data
+                 (list
+                  (cons "joshua"
+                        "$6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86."))))]
+        [receive-action (opensmtpd-action-local-delivery-configuration
+                         (name "receive")
+                         (method (opensmtpd-maildir-configuration
+                                  (pathname "/home/%{rcpt.user}/Maildir")
+                                  (junk #t)))
+                         (virtual (opensmtpd-table-configuration
+                                   (name "virtual")
+                                   (data (list "root" "james@gnu.org")))))]
+        [smtp.gnucode.me (opensmtpd-pki-configuration
+                          (domain "smtp.gnucode.me")
+                          (cert "opensmtpd.scm")
+                          (key "opensmtpd.scm"))])
+    (opensmtpd-configuration
+     (listen-ons
+      (list
+       ;; this forum help suggests that I listen on 0.0.0.0 and NOT eth0
+       ;; https://serverfault.com/questions/726795/opensmtpd-wont-work-at-reboot
+       ;; this listens for email from the outside world
+       (opensmtpd-listen-on-configuration
+        (interface interface)
+        (port 25)
+        (secure-connection "tls")
+        (pki smtp.gnucode.me))
+       ;; this lets local users logged into the system via ssh send email
+       (opensmtpd-listen-on-configuration
+        (interface interface)
+        (port 465)
+        (secure-connection "smtps")
+        (pki smtp.gnucode.me)
+        (auth creds))))
+     (matches (list
+               (opensmtpd-match-configuration
+                (action receive-action)
+                (for (opensmtpd-option-configuration
+                      (option "for local")))))))))
+
+(define example-opensmtpd-config
+  (let ([interface "lo"]
+        [creds (opensmtpd-table-configuration
+                (name "creds")
+                (data
+                 (list
+                  (cons "joshua"
+                        "$6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86."))))]
+        [receive-action (opensmtpd-action-local-delivery-configuration
+                         (name "receive")
+                         (method (opensmtpd-maildir-configuration
+                                  (pathname "/home/%{rcpt.user}/Maildir")
+                                  (junk #t)))
+                         (virtual (opensmtpd-table-configuration
+                                   (name "virtual")
+                                   (data (list "josh" "jbranso@dismail.de")))))]
+        [smtp.gnucode.me (opensmtpd-pki-configuration
+                          (domain "smtp.gnucode.me")
+                          (cert "opensmtpd.scm")
+                          (key "opensmtpd.scm"))])
+    (opensmtpd-configuration
+     ;; (mta-max-deferred 50)
+     ;; (queue
+     ;;  (opensmtpd-queue-configuration
+     ;;   (compression #t)))
+     ;; (smtp
+     ;;  (opensmtpd-smtp-configuration
+     ;;   (max-message-size "10M")))
+     ;; (srs
+     ;;  (opensmtpd-srs-configuration
+     ;;   (ttl-delay "5d")))
+     (listen-ons
+      (list
+       ;; this forum help suggests that I listen on 0.0.0.0 and NOT eth0
+       ;; https://serverfault.com/questions/726795/opensmtpd-wont-work-at-reboot
+       ;; this listens for email from the outside world
+       (opensmtpd-listen-on-configuration
+        (interface interface)
+        (port 25)
+        (secure-connection "tls")
+        (pki smtp.gnucode.me))
+       ;; this lets local users logged into the system via ssh send email
+       (opensmtpd-listen-on-configuration
+        (interface "lo")
+        (port 25)
+        (secure-connection "tls")
+        (pki smtp.gnucode.me))
+       (opensmtpd-listen-on-configuration
+        (interface interface)
+        (port 465)
+        (secure-connection "smtps")
+        (pki smtp.gnucode.me)
+        (auth creds)
+        ;;(filter )
+        )
+       (opensmtpd-listen-on-configuration
+        (interface interface)
+        (port 587)
+        (secure-connection "tls-require")
+        (pki smtp.gnucode.me)
+        (auth creds))))
+     (matches (list
+               (opensmtpd-match-configuration
+                (action (opensmtpd-action-relay-configuration
+                         (name "send")))
+                (for (opensmtpd-option-configuration
+                      (option "for any")))
+                (from (opensmtpd-option-configuration
+                       (option "from any")))
+                (auth (opensmtpd-option-configuration
+                       (option "auth"))))
+               (opensmtpd-match-configuration
+                (action receive-action)
+                (from (opensmtpd-option-configuration
+                       (option "from any")))
+                (for (opensmtpd-option-configuration
+                      (option "for domain")
+                      (value (list "gnucode.me" "gnu-hurd.com")))))
+               (opensmtpd-match-configuration
+                (action receive-action)
+                (for (opensmtpd-option-configuration
+                      (option "for local")))))))))
+      #+END_SRC
+
+*** some example smtpd.conf configs
+*** serving multiple domains with one pki
+
+source:  https://www.reddit.com/r/openbsd/comments/n41wkz/how_to_host_different_domains_for_an_email_server/
+#+BEGIN_EXAMPLE
+​pki mail.primary.domain cert​pki mail.primary.domain cert "/etc/ssl/mail.primary.domain.fullchain.pem"
+
+pki mail.primary.domain key "/etc/ssl/private/mail.primary.domain.key"
+
+
+filter check_dyndns phase connect match rdns regex { '.*\.dyn\..*', '.*\.dsl\..*' } \
+
+disconnect "550 no residential connections"
+
+
+filter check_rdns phase connect match !rdns \
+
+disconnect "550 no rDNS is so 80s"
+
+
+filter check_fcrdns phase connect match !fcrdns \
+
+disconnect "550 no FCrDNS is so 80s"
+
+
+filter senderscore \
+
+proc-exec "filter-senderscore -blockBelow 10 -junkBelow 70 -slowFactor 5000"
+
+
+filter rspamd proc-exec "filter-rspamd"
+
+
+table usermap file:/etc/mail/usermap
+
+table credentials file:/etc/mail/credentials
+
+table domains { primary.domain, second.domain }
+
+
+listen on all tls pki mail.primary.domain \
+
+filter { check_dyndns, check_rdns, check_fcrdns, senderscore, rspamd }
+
+
+listen on egress port 465 smtps pki mail.primary.domain \
+
+auth ~<credentials>~ filter rspamd
+