I would like to pass incoming mail using virtual_transport to a transport spamassassin (main.cf: virtual_transport=spamassassin) and then reinject the scanned mail into postfix using sendmail.

The transport looks like this:

spamassassin unix   -    n     n   -   -    pipe
    flags=hu user=vmail:mail argv=/usr/bin/spamc -u ${recipient} -e /usr/sbin/sendmail -f ${sender} ${recipient}

This should be possible acording to the postfix documentation:

High volume sites should avoid using "simple" content filters that re-inject scanned mail via Postfix sendmail(1) and postdrop(1).

With my current sendmail call /usr/sbin/sendmail -f ${sender} ${recipient}, the mail loops by getting passed back into the queue and then back to spamassassin and so on.

From my understanding of the postfix documentation, mail delivered using postfix sendmail is dropped in the maildrop directory and then picked up using pickup. I tried to append a -o virtual_transport=dovecot to the pickup transport to avoid the queue, but this didn't help. (Those options can only get appended to smtp transports I suppose).

How can I reinject mail scanned with spamassassin back into the queue for final delivery without causing a loop?

1 Answer 1


How can I reinject mail scanned with spamassassin back into the queue for final delivery without causing a loop?

NO You can't


I understand the reason you use virtual_transport to pipe email into Spam Assassin. You only want to scan the email intended to your virtual_mailbox_domain. Unfortunately it doesn't follow the recommended setup for simple content filter that using content_filter parameter instead virtual_transport.

Why I can't pass virtual_transport to pickup or smtpd?

Generally, here the email journey across postfix code

Input  --> cleanup --> qmgr --> Output 
*smtpd                          *local     
*pickup                         *virtual
*qmqpd                          *smtp

In cleanup daemon, postfix will 'route' your email, whether they are belong to virtual, local, smtp or other transports. One exception is when non-blank content_filter parameter was passed into input service (smtpd, pickup, qmqpd). When this happened, postfix will override the message route and send the email into content_filter instead.

Another note, by default all postfix service was get parameter value from main.cf unless you override it by using -o parameter=value on master.cf. But not every parameter in man 5 postconf used by a service. For example, content_filter only used by pickup, smtpd and qmqpd so you can put -o content_filter=amavis:[ on them. Another example, it is not sensible to put -o smtp_bind_address in smtpd as this parameter only used in smtp service.

The same argument can be applied to your question, "why I can pass -o virtual_transport in pickup?"

So here are the several alternatives for your setup

  • Use content_filter as explained in postfix documentation. The downside is you also scan the email that relayed outside your domain.

  • Use content_filter via ACCESS table and FILTER action. With this way, you can selectively scan the incoming email. Unfortunately you must enlist your virtual domain again in that table.. You can avoid it by carefully placing ACCESS table after permit_mynetworks, permit_sasl_authenticated. With this way, outgoing email will be already permitted and you only scan incoming email. (Credit to OP comment :) )

  • Using Postfix Multiple Instance. With this way, you can keep the virtual_transport parameter and spamc will inject the email to your second instance. And then you can define different virtual_transport in different instance. But it's a bit overkill if you want to keep your setup simple.

  • 1
    Hi, thanks for the answer! I got a question about the simple filter setup: How does this not end in a loop without content_filter= in the pickup transport?
    – Zulakis
    May 3, 2015 at 17:14
  • 1
    Because you don't specify content_filter in main.cf, then content_filter was blank. You only specify content_filter in smtpd, not pickup
    – masegaloeh
    May 3, 2015 at 21:34
  • 1
    Ah, right. Shouldn't content_filter via ACCESS table and FILTER action work even without another complete list of the virtual domains using the following setup: smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_sender_access regexp:$config_directory/apply_content_filter, permit and a apply_content_filter with content /.*/ FILTER spamassassin:dummy? Or are there any caveats/downsides to this?
    – Zulakis
    May 5, 2015 at 10:41
  • Yes. You are right. Outgoing email will be permitted by permit_mynetworks, permit_sasl_authenticated, so you are only filter virtual domain.
    – masegaloeh
    May 5, 2015 at 10:44
  • 1
    See my updated answer :)
    – masegaloeh
    May 6, 2015 at 5:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.