From e1600bf56eea5afba89600239be87914875e18ff Mon Sep 17 00:00:00 2001 From: Wolf1098 Date: Thu, 4 Jun 2026 03:07:26 -0700 Subject: [PATCH 01/12] Update DKIM_SELECTOR to read from environment variable Read DKIM_SELECTOR from environment variable with default value 'dkim' and encode it for byte consumption. --- app/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/config.py b/app/config.py index dc7e3090f..2432a2f60 100644 --- a/app/config.py +++ b/app/config.py @@ -205,7 +205,8 @@ def get_env_csv(env_var: str, default: Optional[str]) -> list[str]: # due to a typo, both UNSUBSCRIBER and OLD_UNSUBSCRIBER are supported OLD_UNSUBSCRIBER = os.environ.get("OLD_UNSUBSCRIBER") -DKIM_SELECTOR = b"dkim" +#change to reading DKIM_SELECTOR from .env, passing through encode as is consumed in bytes not string, using the default value of "dkim". +DKIM_SELECTOR = os.enviroment.get("DKIM_SELECTOR", "dkim").encode DKIM_PRIVATE_KEY = None if "DKIM_PRIVATE_KEY_PATH" in os.environ: From 3a13e5f04bb44c108bbd43ae36c8699ac5e82de0 Mon Sep 17 00:00:00 2001 From: Wolf1098 Date: Thu, 4 Jun 2026 03:17:35 -0700 Subject: [PATCH 02/12] Update DKIM comments in example.env Clarified DKIM private key and selector comments in example.env --- example.env | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/example.env b/example.env index b7a6a5b3b..2c699c7de 100644 --- a/example.env +++ b/example.env @@ -68,8 +68,11 @@ EMAIL_SERVERS_WITH_PRIORITY=[(10, "email.hostname.")] # By default, emails are sent using the the same Postfix server that receives emails # POSTFIX_SERVER=my-postfix.com -# the DKIM private key used to compute DKIM-Signature +# the DKIM private key and selector used to compute DKIM-Signature (the selector is the DNS bit before the period in the cname or txt record(ie. simplelogin._domainkey.[example.com] rather than dkim._domainkey.[example.com]) # DKIM_PRIVATE_KEY_PATH=local_data/dkim.key +# DKIM_SELECTOR=simplelogin + + # DB Connection DB_URI=postgresql://myuser:mypassword@localhost:5432/simplelogin From 581f0b0dac0b5fc0ad6ab23c92eb67dc17001045 Mon Sep 17 00:00:00 2001 From: Wolf1098 Date: Sat, 6 Jun 2026 10:24:53 -0700 Subject: [PATCH 03/12] Refactor DKIM configuration handling Updated DKIM_SELECTOR and DKIM_SELECTORS_LIST retrieval from environment variables, with defaults for dehard-coding. --- app/config.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/config.py b/app/config.py index 2432a2f60..8697f72f9 100644 --- a/app/config.py +++ b/app/config.py @@ -205,8 +205,16 @@ def get_env_csv(env_var: str, default: Optional[str]) -> list[str]: # due to a typo, both UNSUBSCRIBER and OLD_UNSUBSCRIBER are supported OLD_UNSUBSCRIBER = os.environ.get("OLD_UNSUBSCRIBER") -#change to reading DKIM_SELECTOR from .env, passing through encode as is consumed in bytes not string, using the default value of "dkim". -DKIM_SELECTOR = os.enviroment.get("DKIM_SELECTOR", "dkim").encode +# read DKIM_SELECTOR and the DKIM_SELECTORS_LIST from .env, +# passing SELECTORS_LIST as str, with defauls matching hardcoded values. in preperation of dehard-coding +#passing _SELECTOR through encode as is consumed in bytes not string, using the default value of "dkim". +# +if DKIM_SELECTOR in os.environ: + DKIM_SELECTOR = os.enviroment.get("DKIM_SELECTOR", "dkim").encode + DKIM_SELECTORS_LIST = os.environ.get("DKIM_SELECTORS_LIST", DKIM_SELECTOR) +else: + DKIM_SELECTORS_LIST = os.environ.get("DKIM_SELECTORS_LIST", "dkim, dkim02, dkim03") + DKIM_PRIVATE_KEY = None if "DKIM_PRIVATE_KEY_PATH" in os.environ: From e85674f405143004e00db6320b8f96eaa6673541 Mon Sep 17 00:00:00 2001 From: Wolf1098 Date: Sat, 6 Jun 2026 10:36:20 -0700 Subject: [PATCH 04/12] Fix DKIM_SELECTOR environment variable handling --- app/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config.py b/app/config.py index 8697f72f9..45e727ab1 100644 --- a/app/config.py +++ b/app/config.py @@ -209,8 +209,8 @@ def get_env_csv(env_var: str, default: Optional[str]) -> list[str]: # passing SELECTORS_LIST as str, with defauls matching hardcoded values. in preperation of dehard-coding #passing _SELECTOR through encode as is consumed in bytes not string, using the default value of "dkim". # -if DKIM_SELECTOR in os.environ: - DKIM_SELECTOR = os.enviroment.get("DKIM_SELECTOR", "dkim").encode +if DKIM_SELECTOR in os.environ or DKIM_SELECTORS_LIST in os.environ: + DKIM_SELECTOR = os.environ.get("DKIM_SELECTOR", "dkim").encode() DKIM_SELECTORS_LIST = os.environ.get("DKIM_SELECTORS_LIST", DKIM_SELECTOR) else: DKIM_SELECTORS_LIST = os.environ.get("DKIM_SELECTORS_LIST", "dkim, dkim02, dkim03") From 6f7b3adab219f6b4e892a21991407190df977a79 Mon Sep 17 00:00:00 2001 From: Wolf1098 Date: Sat, 6 Jun 2026 10:53:49 -0700 Subject: [PATCH 05/12] Correct DKIM_SELECTOR environment variable check quotes needed here --- app/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config.py b/app/config.py index 45e727ab1..281ff3cdb 100644 --- a/app/config.py +++ b/app/config.py @@ -209,7 +209,7 @@ def get_env_csv(env_var: str, default: Optional[str]) -> list[str]: # passing SELECTORS_LIST as str, with defauls matching hardcoded values. in preperation of dehard-coding #passing _SELECTOR through encode as is consumed in bytes not string, using the default value of "dkim". # -if DKIM_SELECTOR in os.environ or DKIM_SELECTORS_LIST in os.environ: +if "DKIM_SELECTOR" in os.environ or "DKIM_SELECTORS_LIST" in os.environ: DKIM_SELECTOR = os.environ.get("DKIM_SELECTOR", "dkim").encode() DKIM_SELECTORS_LIST = os.environ.get("DKIM_SELECTORS_LIST", DKIM_SELECTOR) else: From 36b5c380e014b33f84ac1f0b0cf3797fcf95bca1 Mon Sep 17 00:00:00 2001 From: "Terrence Y.H." Date: Sun, 7 Jun 2026 16:44:13 -0700 Subject: [PATCH 06/12] modified: app/config.py - refined dkim_selector and dkim_valid_selectors_list loading logic, - moved encode logic from dkim_selector to email_utils.py modified: app/custom_domain_validation.py - use config.DKIM_VALID_SELECTORS_LIST instead of hardcoded selector list modified: app/email_utils.py - encode config.DKIM_SELECTOR when signing email with dkim constructor for method already encodes other inputs --- app/config.py | 25 +++++++++++++++++-------- app/custom_domain_validation.py | 7 ++++++- app/email_utils.py | 2 +- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/app/config.py b/app/config.py index 281ff3cdb..b8cca772f 100644 --- a/app/config.py +++ b/app/config.py @@ -205,15 +205,23 @@ def get_env_csv(env_var: str, default: Optional[str]) -> list[str]: # due to a typo, both UNSUBSCRIBER and OLD_UNSUBSCRIBER are supported OLD_UNSUBSCRIBER = os.environ.get("OLD_UNSUBSCRIBER") -# read DKIM_SELECTOR and the DKIM_SELECTORS_LIST from .env, -# passing SELECTORS_LIST as str, with defauls matching hardcoded values. in preperation of dehard-coding -#passing _SELECTOR through encode as is consumed in bytes not string, using the default value of "dkim". -# -if "DKIM_SELECTOR" in os.environ or "DKIM_SELECTORS_LIST" in os.environ: - DKIM_SELECTOR = os.environ.get("DKIM_SELECTOR", "dkim").encode() - DKIM_SELECTORS_LIST = os.environ.get("DKIM_SELECTORS_LIST", DKIM_SELECTOR) +# Load DKIM_SELECTOR or use "dkim" as default +DKIM_SELECTOR = os.environ.get("DKIM_SELECTOR", "dkim") +if "DKIM_SELECTOR" in os.environ and "DKIM_VALID_SELECTORS_LIST" in os.environ: + # read DKIM_SELECTOR and the DKIM_VALID_SELECTORS_LIST from .env, if os.eniron.get fails, failback to the hardcoded values for to ensure SL production stablity. + DKIM_VALID_SELECTORS_LIST = os.environ.get("DKIM_VALID_SELECTORS_LIST", DKIM_SELECTOR) + if DKIM_SELECTOR not in [selector.strip() for selector in DKIM_VALID_SELECTORS_LIST.split(",")]: + raise RuntimeError( + "DKIM_SELECTOR must be included in DKIM_VALID_SELECTORS_LIST if both are defined" + ) +elif "DKIM_SELECTOR" in os.environ and "DKIM_VALID_SELECTORS_LIST" not in os.environ: + # else, for single selector compatibility, if DKIM_VALID_SELECTORS_LIST is not defined, + # AND DKIM_SELECTOR is defined, + # in simplelogin.env; USE: the value of DKIM_SELECTOR as the only valid selector + DKIM_VALID_SELECTORS_LIST = DKIM_SELECTOR else: - DKIM_SELECTORS_LIST = os.environ.get("DKIM_SELECTORS_LIST", "dkim, dkim02, dkim03") + # for backward compatibility, if neither DKIM_SELECTOR nor DKIM_VALID_SELECTORS_LIST is defined, use the hardcoded values + DKIM_VALID_SELECTORS_LIST = "dkim,dkim02,dkim03" DKIM_PRIVATE_KEY = None @@ -222,6 +230,7 @@ def get_env_csv(env_var: str, default: Optional[str]) -> list[str]: with open(DKIM_PRIVATE_KEY_PATH) as f: DKIM_PRIVATE_KEY = f.read() + # Database DB_URI = os.environ["DB_URI"] DB_CONN_NAME = os.environ.get("DB_CONN_NAME", "webapp") diff --git a/app/custom_domain_validation.py b/app/custom_domain_validation.py index fc3ec153c..e9c1d59d9 100644 --- a/app/custom_domain_validation.py +++ b/app/custom_domain_validation.py @@ -141,7 +141,12 @@ def get_dkim_records( dkim_domains.insert(0, partner_domain) output = {} - for key in ("dkim", "dkim02", "dkim03"): + dkim_selectors = [ + s.strip() + for s in config.DKIM_VALID_SELECTORS_LIST.split(",") + if s.strip() + ] + for key in dkim_selectors: records = [ f"{key}._domainkey.{dkim_domain}" for dkim_domain in dkim_domains ] diff --git a/app/email_utils.py b/app/email_utils.py index 796a7b567..f63f3b5ab 100644 --- a/app/email_utils.py +++ b/app/email_utils.py @@ -551,7 +551,7 @@ def add_dkim_signature_with_header( if config.DKIM_PRIVATE_KEY: sig = dkim.sign( message_to_bytes(msg), - config.DKIM_SELECTOR, + config.DKIM_SELECTOR.encode(), email_domain.encode(), config.DKIM_PRIVATE_KEY.encode(), include_headers=dkim_headers, From d9e42d374e4aec43f07a333f346125a02cf9abb7 Mon Sep 17 00:00:00 2001 From: "Terrence Y.H." Date: Sun, 7 Jun 2026 18:47:51 -0700 Subject: [PATCH 07/12] add DKIM_* info to example.env --- example.env | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/example.env b/example.env index 2c699c7de..fdfd01e79 100644 --- a/example.env +++ b/example.env @@ -68,9 +68,15 @@ EMAIL_SERVERS_WITH_PRIORITY=[(10, "email.hostname.")] # By default, emails are sent using the the same Postfix server that receives emails # POSTFIX_SERVER=my-postfix.com -# the DKIM private key and selector used to compute DKIM-Signature (the selector is the DNS bit before the period in the cname or txt record(ie. simplelogin._domainkey.[example.com] rather than dkim._domainkey.[example.com]) +# DKIM private key and selector used to compute DKIM-Signature (the selector is - +# the DNS bit before the period in the cname or txt record for the root domain (EMAIL_DOMAIN) +# (ie. simplelogin._domainkey.[sl.lan] rather than dkim._domainkey.[sl.lan]) +# The VALID DKIM selectors list is used to specify csv list of valid DKIM selectors +# preferablly these should be online # DKIM_PRIVATE_KEY_PATH=local_data/dkim.key -# DKIM_SELECTOR=simplelogin +# DKIM_SELECTOR=OnlineMX02 +# DKIM_VALID_SELECTORS_LIST=OnlineMX,OnlineMX02,OnlineMX03 + From 5eb97d0a046bc8d283929f3f473c8de6a5c3cf34 Mon Sep 17 00:00:00 2001 From: "Terrence Y.H." Date: Sun, 7 Jun 2026 19:07:01 -0700 Subject: [PATCH 08/12] Clarify example.env --- example.env | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/example.env b/example.env index fdfd01e79..879dc720a 100644 --- a/example.env +++ b/example.env @@ -71,11 +71,13 @@ EMAIL_SERVERS_WITH_PRIORITY=[(10, "email.hostname.")] # DKIM private key and selector used to compute DKIM-Signature (the selector is - # the DNS bit before the period in the cname or txt record for the root domain (EMAIL_DOMAIN) # (ie. simplelogin._domainkey.[sl.lan] rather than dkim._domainkey.[sl.lan]) -# The VALID DKIM selectors list is used to specify csv list of valid DKIM selectors -# preferablly these should be online # DKIM_PRIVATE_KEY_PATH=local_data/dkim.key -# DKIM_SELECTOR=OnlineMX02 -# DKIM_VALID_SELECTORS_LIST=OnlineMX,OnlineMX02,OnlineMX03 +# DKIM_SELECTOR=DKIM02 + +# The VALID DKIM selectors list is used to specify csv list of valid DKIM selectors used by +# other sl-email containers serving the same domain +# (setting a unique DKIM_SELECTOR above for each sl-email instances) +# DKIM_VALID_SELECTORS_LIST=DKIM,DKIM02,DKIM03 From 4ff0b9641c7eae672d96630a057393115e03295f Mon Sep 17 00:00:00 2001 From: Wolf1098 Date: Sun, 7 Jun 2026 19:24:00 -0700 Subject: [PATCH 09/12] Update DKIM configuration comments in example.env --- example.env | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/example.env b/example.env index 879dc720a..65448559a 100644 --- a/example.env +++ b/example.env @@ -70,14 +70,15 @@ EMAIL_SERVERS_WITH_PRIORITY=[(10, "email.hostname.")] # DKIM private key and selector used to compute DKIM-Signature (the selector is - # the DNS bit before the period in the cname or txt record for the root domain (EMAIL_DOMAIN) -# (ie. simplelogin._domainkey.[sl.lan] rather than dkim._domainkey.[sl.lan]) +# for example simplelogin._domainkey.[sl.lan] rather than dkim._domainkey.[sl.lan]) # DKIM_PRIVATE_KEY_PATH=local_data/dkim.key -# DKIM_SELECTOR=DKIM02 +# DKIM_SELECTOR=simplelogin # The VALID DKIM selectors list is used to specify csv list of valid DKIM selectors used by -# other sl-email containers serving the same domain -# (setting a unique DKIM_SELECTOR above for each sl-email instances) -# DKIM_VALID_SELECTORS_LIST=DKIM,DKIM02,DKIM03 +# other sl-email containers serving the same EMAIL_DOMAIN (setting a unique DKIM_SELECTOR above for each sl-email instances) +# DOES NOT ADD the default selector 'dkim' by default to the list IF, above, DKIM_SELECTOR has been set +# if your running WITHOUT setting DKIM_SELECTOR or DKIM_SELECTOR=dkim, DO specify `dkim` selector in the list. +# DKIM_VALID_SELECTORS_LIST=dkim,simplelogin,simplelogin2 From 9fffabf6b34d805ddbf43d2a13a5280633e2c2d4 Mon Sep 17 00:00:00 2001 From: "Terrence Y.H." Date: Sun, 7 Jun 2026 19:49:31 -0700 Subject: [PATCH 10/12] Add missing elif branch --- app/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/config.py b/app/config.py index 0fc1d1e3a..afc644cdd 100644 --- a/app/config.py +++ b/app/config.py @@ -219,6 +219,8 @@ def get_env_csv(env_var: str, default: Optional[str]) -> list[str]: # AND DKIM_SELECTOR is defined, # in simplelogin.env; USE: the value of DKIM_SELECTOR as the only valid selector DKIM_VALID_SELECTORS_LIST = DKIM_SELECTOR +elif "DKIM_SELECTOR" not in os.environ and "DKIM_VALID_SELECTORS_LIST" in os.environ: + DKIM_VALID_SELECTORS_LIST = os.environ.get("DKIM_VALID_SELECTORS_LIST") else: # for backward compatibility, if neither DKIM_SELECTOR nor DKIM_VALID_SELECTORS_LIST is defined, use the hardcoded values DKIM_VALID_SELECTORS_LIST = "dkim,dkim02,dkim03" From ca3b48cc2d828b575221ccf84cea106e9e772ec9 Mon Sep 17 00:00:00 2001 From: "Terrence Y.H." Date: Sun, 7 Jun 2026 20:23:33 -0700 Subject: [PATCH 11/12] Handle the exceptions --- app/config.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/config.py b/app/config.py index afc644cdd..70e85414e 100644 --- a/app/config.py +++ b/app/config.py @@ -212,7 +212,7 @@ def get_env_csv(env_var: str, default: Optional[str]) -> list[str]: DKIM_VALID_SELECTORS_LIST = os.environ.get("DKIM_VALID_SELECTORS_LIST", DKIM_SELECTOR) if DKIM_SELECTOR not in [selector.strip() for selector in DKIM_VALID_SELECTORS_LIST.split(",")]: raise RuntimeError( - "DKIM_SELECTOR must be included in DKIM_VALID_SELECTORS_LIST if both are defined" + f'"{DKIM_SELECTOR}" must be included in DKIM_VALID_SELECTORS_LIST if both are defined' ) elif "DKIM_SELECTOR" in os.environ and "DKIM_VALID_SELECTORS_LIST" not in os.environ: # else, for single selector compatibility, if DKIM_VALID_SELECTORS_LIST is not defined, @@ -221,9 +221,17 @@ def get_env_csv(env_var: str, default: Optional[str]) -> list[str]: DKIM_VALID_SELECTORS_LIST = DKIM_SELECTOR elif "DKIM_SELECTOR" not in os.environ and "DKIM_VALID_SELECTORS_LIST" in os.environ: DKIM_VALID_SELECTORS_LIST = os.environ.get("DKIM_VALID_SELECTORS_LIST") + if str("dkim") not in [selector.strip() for selector in DKIM_VALID_SELECTORS_LIST.split(",")]: + raise RuntimeError( + '"dkim", must be included in DKIM_VALID_SELECTORS_LIST if DKIM_SELECTOR is left to defaults' + ) else: # for backward compatibility, if neither DKIM_SELECTOR nor DKIM_VALID_SELECTORS_LIST is defined, use the hardcoded values DKIM_VALID_SELECTORS_LIST = "dkim,dkim02,dkim03" + print("WARNING: DKIM_SELECTOR and DKIM_VALID_SELECTORS_LIST are not defined in .env, using default values. " + "For better security, please define them in your .env file." + ) + DKIM_PRIVATE_KEY = None From 82ae0be5833b2ed46a59badc394f79aab6bcb44a Mon Sep 17 00:00:00 2001 From: "Terrence Y.H." Date: Sun, 7 Jun 2026 21:33:09 -0700 Subject: [PATCH 12/12] Use RuntimeWarning instead --- app/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config.py b/app/config.py index 70e85414e..8d75f3864 100644 --- a/app/config.py +++ b/app/config.py @@ -228,7 +228,7 @@ def get_env_csv(env_var: str, default: Optional[str]) -> list[str]: else: # for backward compatibility, if neither DKIM_SELECTOR nor DKIM_VALID_SELECTORS_LIST is defined, use the hardcoded values DKIM_VALID_SELECTORS_LIST = "dkim,dkim02,dkim03" - print("WARNING: DKIM_SELECTOR and DKIM_VALID_SELECTORS_LIST are not defined in .env, using default values. " + raise RuntimeWarning("WARNING: DKIM_SELECTOR and DKIM_VALID_SELECTORS_LIST are not defined in .env, using default values. " "For better security, please define them in your .env file." )