Your question
Hi,
Stalwart version 0.16.13 in a VM.
I have a mailing list where I want to restrict the access, meaning just a list of users to be able to send to this mailing list. How can this be done?
Thanks
I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.
on
You can enforce this with a Sieve script run at the RCPT stage that checks the envelope recipient (the list’s address) and rejects the message unless the envelope sender is on an approved list.
For example, define an allow-list (a static list named list-senders containing the addresses allowed to post) and a Sieve script like:
require ["variables", "envelope", "reject"];
if envelope :is "to" "[email protected]" {
if not key_exists("list-senders", "${envelope.from}") {
reject "550 5.7.1 You are not authorised to post to this list.";
}
}
Then set script on the MtaStageRcpt singleton (Settings › MTA › Session › RCPT TO Stage in the WebUI) to run this script, e.g. only when the recipient matches the list address:
{
"script": {
"match": [{"if": "rcpt == '[email protected]'", "then": "'list_sender_check'"}],
"else": "false"
}
}
This runs at the RCPT stage, before the message body is even accepted, so unauthorized senders are rejected outright rather than having their message accepted into the list. The list-senders lookup can be a static in-memory list, a SQL/LDAP-backed one, or any configured Store lookup, depending on how you want to manage the approved sender roster.
See the RCPT stage docs for the full mechanics: RCPT stage | Stalwart
This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.
Hi,
I did a static in-memory list, a system sieve script and a script in MtaStageRcpt but something is wrong. I’m guessing I did something wrong in the MtaStageRcpt because I don’t see in the TRACE log anythin only:
2026-07-24T08:49:53Z TRACE Expression evaluation result (eval.result) id = “MtaStageRcpt with id singleton”, key = “script”, result = “Integer(0)”
2026-07-24T08:49:53Z TRACE Expression evaluation result (eval.result) id = “MtaStageRcpt with id singleton”, key = “script”, result = “”
2026-07-24T08:49:53Z TRACE Expression evaluation result (eval.result) id = “MtaStageRcpt with id singleton”, key = “rewrite”, result = “Integer(0)”
2026-07-24T08:49:53Z TRACE Expression evaluation result (eval.result) id = “MtaStageRcpt with id singleton”, key = “rewrite”, result = “”
2026-07-24T08:49:53Z DEBUG Cache hit (store.cache-hit) key = “example.org”, collection = “domainName”
2026-07-24T08:49:53Z DEBUG Cache hit (store.cache-hit) key = 1, collection = “domainId”
2026-07-24T08:49:53Z DEBUG Cache hit (store.cache-hit) key = “mylist”, domain = 1, collection = “email”
2026-07-24T08:49:53Z DEBUG Cache hit (store.cache-hit) key = 4, collection = “account”
2026-07-24T08:49:53Z TRACE Expression evaluation result (eval.result) id = “MtaInboundThrottle with id ipvwtgdqabqb”, key = “match”, result = “Integer(1)”
2026-07-24T08:49:53Z TRACE Data store iteration operation (store.data-iterate) elapsed = 1ms
2026-07-24T08:49:53Z DEBUG Task acquired from queue (task-manager.task-acquired) total = 2, details = 2
2026-07-24T08:49:53Z TRACE Write batch operation (store.data-write) elapsed = 0ms, total = 2
2026-07-24T08:49:53Z INFO SMTP RCPT TO command (smtp.rcpt-to) to = “[email protected]”
2026-07-24T08:49:53Z TRACE Raw SMTP output sent (smtp.raw-output) size = 14, contents = “250 2.1.5 OK\r\n”
The MtaRcptStahe script is:
Run Script:
Expression
Conditions:
Condition Result
0 rcpt == ‘[email protected]’ ‘all_list_sender_check’
Default Value: false
System sieve script is:
System Script
Name: all_list_sender_check
Description:
Contents: require [“variables”, “envelope”, “reject”];
if envelope :is “to” “[email protected]” {
if not key_exists(“all-list-senders”, “${envelope.from}”) {
reject “550 5.7.1 You are not authorised to post to this list.”;
}
}
Active: Yes
And the list “all-list-sender” is:
Lookup Key
Namespace: all-list-senders
Key: [email protected]
Glob Pattern: No
Thanks for help
There’s no built-in “who may post” setting on a mailing list, so doing it with an MTA-stage script is the right approach. Your problem is in the MtaStageRcpt run-script mapping, not the Sieve script: the trace shows the “script” expression evaluating to Integer(0) and then empty, which means your condition didn’t match and it fell through to the default (false), so all_list_sender_check was never invoked and RCPT went straight to 250.
So the expression rcpt == ‘[email protected]’ isn’t matching what rcpt actually holds at that stage. Turn on trace and check the real value of rcpt for that delivery (case, exact address, any trailing detail), and confirm the run-script expression returns the script name as a string when it matches rather than a boolean. Once the mapping returns ‘all_list_sender_check’, your Sieve reject will run. If you paste the exact run-script condition as configured (the variable and the returned value) and one trace line showing rcpt, I can tell you what to change. One thing to decide up front: your script rejects on envelope.from at RCPT time, which is the MAIL FROM envelope sender; if you actually want to gate on the header From, that has to happen at the DATA stage instead.