<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ScottDotDot </title>
	<atom:link href="http://s.co.tt/tag/postfix/feed/" rel="self" type="application/rss+xml" />
	<link>http://s.co.tt</link>
	<description>Babblings of a computer curmudgeon.</description>
	<lastBuildDate>Mon, 26 Jan 2026 16:08:52 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=4.1</generator>
	<item>
		<title>Redundant email servers with soft-fail (450) vs. hard-fail (550)</title>
		<link>http://s.co.tt/2014/02/10/redundant-email-servers-with-soft-fail-450-vs-hard-fail-550/</link>
		<comments>http://s.co.tt/2014/02/10/redundant-email-servers-with-soft-fail-450-vs-hard-fail-550/#comments</comments>
		<pubDate>Mon, 10 Feb 2014 23:34:32 +0000</pubDate>
		<dc:creator><![CDATA[Scott]]></dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[email]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[postfix]]></category>

		<guid isPermaLink="false">http://s.co.tt/blog/?p=529</guid>
		<description><![CDATA[I manage a fairly large number of incoming mail exchangers, which are numerous both to handle large message volumes as well as to provide redundancy. In most cases, these mail servers are Postfix with MySQL providing virtual alias maps, transport maps, relay domains, and virtual alias domains. Unfortunately the Postfix+MySQL implementation isn&#8217;t always 100% great. On very rare occasions the Postfix instance may fail to communicate with the MySQL server, for any number of reasons. From the perspective of the sender&#8217;s MX, this usually results in a 550 status code (often given as &#8220;Relay access denied&#8221;). This is a hard-fail, in that it tells the upstream MX that the recipient they&#8217;re trying to reach is permanently unavailable. The upstream MX … <a class="continue-reading-link" href="http://s.co.tt/2014/02/10/redundant-email-servers-with-soft-fail-450-vs-hard-fail-550/"> Continue reading</a>]]></description>
				<content:encoded><![CDATA[<p>I manage a fairly large number of incoming mail exchangers, which are numerous both to handle large message volumes as well as to provide redundancy.</p>
<p>In most cases, these mail servers are Postfix with MySQL providing virtual alias maps, transport maps, relay domains, and virtual alias domains.  Unfortunately the Postfix+MySQL implementation isn&#8217;t always 100% great.  On very rare occasions the Postfix instance may fail to communicate with the MySQL server, for any number of reasons.</p>
<p>From the perspective of the sender&#8217;s MX, this usually results in a 550 status code (often given as &#8220;Relay access denied&#8221;).  This is a hard-fail, in that it tells the upstream MX that the recipient they&#8217;re trying to reach is permanently unavailable.  The upstream MX then gives up on delivery and sends a bounce notification to the sender.  This behavior is highly undesirable for the purposes of redundancy, because the message will never be retried at another one of my MX servers (which is probably working just fine)!</p>
<p>The solution is to tell Postfix to soft-fail in the case of any undeliverable mail.  For example, I have the following settings in my main.cf:</p>
<p><code>access_map_reject_code = 450<br />
maps_rbl_reject_code = 450<br />
reject_code = 450<br />
relay_domains_reject_code = 450<br />
unknown_local_recipient_reject_code = 450<br />
unknown_relay_recipient_reject_code = 450<br />
unknown_virtual_alias_reject_code = 450<br />
unknown_virtual_mailbox_reject_code = 450</code></p>
<p>A 4xx code is generally interpreted by mail exchangers as a temporary condition, encouraging them to retry delivery at a later time.  Most mail exchangers are smart enough to then try other MX records in DNS for delivery.</p>
<p>However, there are downsides to a soft failure (4xx code).  </p>
<p>First, you&#8217;ll see a whole lot of retries for messages that have truly invalid recipients.  In fact, with a 99.999% MX uptime, the vast majority of retries will be for spam messages, mis-typed addresses, spammer bounces, etc.  This will result in a lot of extra traffic and a lot of extra load on mail servers.  Of course, it will also result in lots of deliveries of valid messages in the case of an outage on one MX!</p>
<p>Second, in the case of a mis-typed address (or other case where the mail is really not deliverable) it could take over a week for the sender to get a bounce back (undeliverable notification).  In the mean time they would probably assume that their message went through, and that the recipient was just ignoring them!</p>
<p><strong>So what&#8217;s the solution?</strong></p>
<p>First, let&#8217;s look at what a properly-configured (read: non-spammer) mail exchanger will do when trying to deliver a message:</p>
<ol>
<li>Obtain from DNS the MX records for the destination domain.</li>
<li>Attempt delivery to the MX with the lowest number in its priority field.</li>
<li>If delivery doesn&#8217;t succeed (generally because the server could not be contacted or the server gave a soft failure) then:
<ul>
<li>Go back to step #2 using the next-highest priority number, or cycle back to the lowest number if there is no higher number.</li>
</ul>
</li>
</ol>
<p>That&#8217;s a simplification, but the idea is that delivery will be tried to each successive MX server in a loop until delivery succeeds or a hard-fail status is received.</p>
<p><strong>The best solution that I&#8217;ve found is to set up the last MX server (the one with the highest priority number in DNS) to hard-fail with 5xx status codes, while the others are set to soft-fail with 4xx status codes.</strong></p>
<p>Spammers will generally attempt delivery to any arbitrary MX server and will not obey the priority order in DNS.  So there will be some unnecessary retries.  But this will cut down on traffic from accidental delivery attempts (e.g. mis-typed addresses), as well as from bounce notifications from valid MX servers (e.g. if a spammer is using one of your users&#8217; email addresses to send mail).</p>
<p>Valid senders with invalid recipients will receive a timely bounce back, though it may take a few minutes depending upon the timeouts in the upstream MX.</p>
<p>Most importantly, delivery of valid and desirable messages will be retried across MX servers until a functional one is found.</p>
<p><strong>Actually, my setup is a bit different:</strong>  I host incoming mail services for many domains, and want to keep traffic flowing equally across all of my mail exchangers.  As such, each domain is assigned three of my MX servers <strong>in random order</strong>.  That means that there is no MX server that will always have the highest priority number in DNS, and so all of those servers are set up to soft-fail.</p>
<p>I think you can see where I&#8217;m going:  I created another fall-back server which is assigned to users&#8217; domains as a fourth MX record with the highest priority number.  This server is set up identically to all the other MX servers, except that it will reply with a 5xx status codes in cases where the other servers would give 4xx codes.  For almost all valid (non-spam) email, this server will be a last resort.</p>
<p>I say &#8220;almost all&#8221; valid email because there is some MX software out there that will try DNS MX records in an arbitrary order.  However this behavior is not that common, and the chances that delivery would be first attempted to the hard-fail server while that server was down is slim:  It would effect a very low number of messages, and so is an acceptable risk.</p>
<p>If you&#8217;ve been working with mail servers and SMTP for any length of time, then you know that mail delivery is never as simple as it seems.  There are trade-offs and &#8220;gotchas&#8221; galore when implementing and administering a mail infrastructure.  To my mind, the #1 goal of any admin should be ensuring successful mail delivery.  Every trade-off should be in furtherance of that result.</p>
<p>It&#8217;s like the old saying:  Better that 10 guilty persons escape than one innocent suffer.</p>
<p><strong>Better that 10 spam messages get delivered than one important message bounce.</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://s.co.tt/2014/02/10/redundant-email-servers-with-soft-fail-450-vs-hard-fail-550/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Using Postfix with MySQL Stored Procedures? (Just Use Functions)</title>
		<link>http://s.co.tt/2012/01/12/using-postfix-with-mysql-stored-procedures-or-use-functions/</link>
		<comments>http://s.co.tt/2012/01/12/using-postfix-with-mysql-stored-procedures-or-use-functions/#comments</comments>
		<pubDate>Thu, 12 Jan 2012 22:43:11 +0000</pubDate>
		<dc:creator><![CDATA[Scott]]></dc:creator>
				<category><![CDATA[Computers]]></category>
		<category><![CDATA[computer]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[postfix]]></category>

		<guid isPermaLink="false">http://s.co.tt/blog/?p=332</guid>
		<description><![CDATA[If you&#8217;re like me and want to use Postfix with a MySQL backend, you may have also wanted to be able to call stored procedures from Postfix. Unfortunately, MySQL stored procs return multiple resultsets while Postfix&#8217;s call to the MySQL C API can only process a single resultset.  I banged my head against the wall for a while until I realized that a function called from a SELECT statement would return a Postfix-friendly single resultset. For example, this would be a perfectly acceptable virtual alias map file: user = mailreader password = somepassword dbname = mail_config query = SELECT retval FROM (SELECT fnPostfixVirtualAliasMapGet('%s') AS retval) t WHERE t.retval IS NOT NULL; hosts = 127.0.0.1 This is useful if, for example, … <a class="continue-reading-link" href="http://s.co.tt/2012/01/12/using-postfix-with-mysql-stored-procedures-or-use-functions/"> Continue reading</a>]]></description>
				<content:encoded><![CDATA[<p>If you&#8217;re like me and want to use Postfix with a MySQL backend, you may have also wanted to be able to call stored procedures from Postfix.</p>
<p>Unfortunately, MySQL stored procs return multiple resultsets while Postfix&#8217;s call to the MySQL C API can only process a single resultset.  I banged my head against the wall for a while until I realized that a function called from a SELECT statement would return a Postfix-friendly single resultset.</p>
<p>For example, this would be a perfectly acceptable virtual alias map file:</p>
<pre>user = mailreader
password = somepassword
dbname = mail_config
query = SELECT retval FROM (SELECT fnPostfixVirtualAliasMapGet('%s') AS retval) t
           WHERE t.retval IS NOT NULL;
hosts = 127.0.0.1</pre>
<p>This is useful if, for example, you wanted to track your customers&#8217; usage in realtime.  Then your function might look like this:</p>
<pre>FUNCTION `fnPostfixVirtualAliasMapGet`(
        p_alias VARCHAR(128)
        ) RETURNS varchar(128) CHARSET latin1
BEGIN
        DECLARE v_destination VARCHAR(128) DEFAULT NULL;
        DECLARE v_zone INTEGER DEFAULT NULL;

        DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET @garbage = 1;

        SELECT destination, zone
        INTO v_destination, v_zone
        FROM postfix_virtual_alias_maps
        WHERE alias = p_alias;

        IF v_zone IS NOT NULL THEN

                INSERT INTO usage_data.mx (
                        zone, created_on
                ) VALUES (
                        v_zone, NOW()
                );

        END IF;

        RETURN v_destination;
END</pre>
<p>Of course, your implementation could vary.</p>
<p>There is one big gotcha.  Postfix requires either no resultset (meaning no match found), or a resultset (meaning a match was found).  If you were to use the simpler form of the example&#8230;</p>
<pre>query = SELECT fnPostfixVirtualAliasMapGet('%s');</pre>
<p>&#8230;Postfix would always think that a match was found for that lookup because the function would return NULL.  That&#8217;s why the query is more complex, and the function&#8217;s result is used as a derived table.</p>
<p>Also notice this line in the function:</p>
<pre>DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET @garbage = 1;</pre>
<p>The purpose of the continue handler is to ignore<em> SQLSTATE &#8216;02000&#8217;</em>, which is <em>ER_SP_FETCH_NO_DATA</em>, or in more straightforward terms is the warning &#8220;<em>No data &#8211; zero rows fetched, selected, or processed</em>&#8220;.  This warning is generated when the <em>SELECT &#8230; INTO [variables]</em> returns no data, and the variables are left unchanged.  I experienced problems with Postfix when this warning was generated (and it&#8217;s just going to fill up logs anyway).</p>
<p>The <em>SET @garbage = 1;</em> is just a throwaway call to give the continue handler something to do.  I don&#8217;t know if it&#8217;s needed, but there you have it.</p>
<p>That&#8217;s about it.  Enjoy!</p>
]]></content:encoded>
			<wfw:commentRss>http://s.co.tt/2012/01/12/using-postfix-with-mysql-stored-procedures-or-use-functions/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
