Issue Description
I write a MTA Hook server to change subject when sender is [email protected], but subject doesn’t changed.
Expected Behavior
When I send from [email protected] to [email protected]
MTA Hook receive a request:
{
"context": {...},
"envelope": {
"from": { "address": "[email protected]" },
"to": [{ "address": "[email protected]" }]
},
"message": {
"headers": [
["Message-ID", " <[email protected]>\r\n"],
["Date", " Sun, 21 Jun 2026 12:47:33 +0800\r\n"],
["MIME-Version", " 1.0\r\n"],
["User-Agent", " Mozilla Thunderbird\r\n"],
["Content-Language", " en-US\r\n"],
["To", " [email protected]\r\n"],
["From", " test <[email protected]>\r\n"],
["Subject", " Test\r\n"],
["Content-Type", " text/plain; charset=UTF-8; format=flowed\r\n"],
["Content-Transfer-Encoding", " 7bit\r\n"]
],
"contents": "test\r\n",
"size": 335
}
}
and this is the response:
{
"action": "accept",
"response": {
"status": 250,
"enhancedStatus": "2.0.0",
"message": "Message accepted",
"disconnect": false
},
"modifications": [
{
"type": "changeHeader",
"index": 7,
"name": "Subject",
"value": "[TEST] Test\r\n"
}
]
}
admin should receive a mail with [TEST] Test
Actual Behavior
Admin receive a mail with original subject Test
Reproduction Steps
- Send a mail from
[email protected]with Test - Lookup
[email protected]’s mailbox - Found a mail with subject “Test” instead of “[TEST] Test”
Stalwart Version
v0.16.x
Installation Method
Docker
Database Backend
RocksDB
Blob Storage
RocksDB
Search Engine
Internal
Directory Backend
Internal
Additional Context
MTA hook server code snippet
async fn filter(Json(req): Json<MTAHookRequest>) -> Json<MTAHookResponse> {
let mut mods: Vec<Modification> = vec![];
let headers = &req.message.headers;
let subject_index = headers
.iter()
.position(|x| x.0 == "Subject")
.expect("subject not found");
let subject = &headers[subject_index].1;
let from = req.envelope.from.address;
let from_user = from.split_once("@").expect("invalid mail").0;
if from_user == "test" {
mods.push(Modification::ChangeHeader {
index: subject_index,
name: "Subject".to_owned(),
value: format!("[TEST]{subject}"),
});
}
let resp = MTAHookResponse {
action: Action::Accept,
response: Some(ResponseData {
status: 250,
enhanced_status: "2.0.0".to_owned(),
message: "Message accepted".to_owned(),
disconnect: false,
}),
modifications: mods,
};
Json(resp)
}
I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.
on
I have searched this support forum (open and closed topics) and confirm this is not a duplicate.
on
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