I've used the next approach:
MTA is the exim that pass the copy of message to the spamassassin from the DATA ACL. SA returns the score and do not perform any message modifications or routing. If calculated score is above the threshold, exim add some special header (X-Spam-Detected: YES) to the message.
Then message is routed for local delivery via dovecot's deliver. Dovecot have pidgeonhole plugin installed that is the sieve engine implementation. When message satisfy some conditions (f.e. header "X-Spam-Detected" exists) pidgeonhole store the message in the inbox' subfolder:
if exists "X-Spam-Detected"
{
fileinto "Junk";
stop;
}
If message is detected wrong (false-positive or false-negative) user can move the message to the right place. Dovecot have another plugin called antispam that track message movements. When message is moved TO the "Spam" subfolder, automatically sa-learn --spam is launched for that message. When message is moved FROM the "Spam" subfolder, sa-learn --ham is launched.
Old messages can be removed automatically with doveadm utility:
doveadm expunge -A mailbox Junk savedbefore 31d