{"id":1597,"date":"2026-11-04T02:34:04","date_gmt":"2026-11-04T02:34:04","guid":{"rendered":"https:\/\/virtualserversvps.com\/blog\/?p=1597"},"modified":"2026-11-04T02:34:04","modified_gmt":"2026-11-04T02:34:04","slug":"postfix-smtp-port-25-blocked-vps","status":"publish","type":"post","link":"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/","title":{"rendered":"When Port 25 Is Blocked: Configuring Postfix to Relay Through a Smarthost"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n\n<p class=\"wp-block-paragraph\">Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n\n<p class=\"wp-block-paragraph\">Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n\n<p class=\"wp-block-paragraph\">Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">relayhost syntax and the port that matters<\/h2>\n<!-- wp:paragraph -->\n<p>Set <code>relayhost<\/code> with an explicit port. Without one Postfix assumes 25 and you end up right back in the black hole. Bracket the hostname so Postfix does not attempt MX lookups \u2014 <code>[smtp.relay.example.net]:587<\/code> means &#8220;this exact host, this exact port&#8221;. Bracket syntax is the difference between a working relay and a DNS recursion loop on a host with no outbound resolver path.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For submission on 587 you also need <code>smtp_use_tls = yes<\/code> and STARTTLS enabled on the relay. If the provider offers 465 instead, use <code>smtp_tls_wrappermode = yes<\/code> with port 465; that is not STARTTLS, it is TLS from the first byte, and mixing the two settings produces a handshake that never completes. Ask the provider which one they support rather than guessing.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>postconf -e 'relayhost = [smtp.relay.example.net]:587'\npostconf -e 'smtp_use_tls = yes'\npostconf -e 'smtp_tls_security_level = encrypt'\npostconf -e 'smtp_tls_CAfile = \/etc\/ssl\/certs\/ca-certificates.crt'\npostconf -e 'smtp_sasl_auth_enable = yes'\npostconf -e 'smtp_sasl_password_maps = hash:\/etc\/postfix\/sasl_passwd'\npostconf -e 'smtp_sasl_security_options = noanonymous'\npostconf -e 'smtp_sasl_tls_security_options = noanonymous'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">relayhost syntax and the port that matters<\/h2>\n<!-- wp:paragraph -->\n<p>Set <code>relayhost<\/code> with an explicit port. Without one Postfix assumes 25 and you end up right back in the black hole. Bracket the hostname so Postfix does not attempt MX lookups \u2014 <code>[smtp.relay.example.net]:587<\/code> means &#8220;this exact host, this exact port&#8221;. Bracket syntax is the difference between a working relay and a DNS recursion loop on a host with no outbound resolver path.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For submission on 587 you also need <code>smtp_use_tls = yes<\/code> and STARTTLS enabled on the relay. If the provider offers 465 instead, use <code>smtp_tls_wrappermode = yes<\/code> with port 465; that is not STARTTLS, it is TLS from the first byte, and mixing the two settings produces a handshake that never completes. Ask the provider which one they support rather than guessing.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>postconf -e 'relayhost = [smtp.relay.example.net]:587'\npostconf -e 'smtp_use_tls = yes'\npostconf -e 'smtp_tls_security_level = encrypt'\npostconf -e 'smtp_tls_CAfile = \/etc\/ssl\/certs\/ca-certificates.crt'\npostconf -e 'smtp_sasl_auth_enable = yes'\npostconf -e 'smtp_sasl_password_maps = hash:\/etc\/postfix\/sasl_passwd'\npostconf -e 'smtp_sasl_security_options = noanonymous'\npostconf -e 'smtp_sasl_tls_security_options = noanonymous'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">relayhost syntax and the port that matters<\/h2>\n<!-- wp:paragraph -->\n<p>Set <code>relayhost<\/code> with an explicit port. Without one Postfix assumes 25 and you end up right back in the black hole. Bracket the hostname so Postfix does not attempt MX lookups \u2014 <code>[smtp.relay.example.net]:587<\/code> means &#8220;this exact host, this exact port&#8221;. Bracket syntax is the difference between a working relay and a DNS recursion loop on a host with no outbound resolver path.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For submission on 587 you also need <code>smtp_use_tls = yes<\/code> and STARTTLS enabled on the relay. If the provider offers 465 instead, use <code>smtp_tls_wrappermode = yes<\/code> with port 465; that is not STARTTLS, it is TLS from the first byte, and mixing the two settings produces a handshake that never completes. Ask the provider which one they support rather than guessing.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>postconf -e 'relayhost = [smtp.relay.example.net]:587'\npostconf -e 'smtp_use_tls = yes'\npostconf -e 'smtp_tls_security_level = encrypt'\npostconf -e 'smtp_tls_CAfile = \/etc\/ssl\/certs\/ca-certificates.crt'\npostconf -e 'smtp_sasl_auth_enable = yes'\npostconf -e 'smtp_sasl_password_maps = hash:\/etc\/postfix\/sasl_passwd'\npostconf -e 'smtp_sasl_security_options = noanonymous'\npostconf -e 'smtp_sasl_tls_security_options = noanonymous'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">relayhost syntax and the port that matters<\/h2>\n<!-- wp:paragraph -->\n<p>Set <code>relayhost<\/code> with an explicit port. Without one Postfix assumes 25 and you end up right back in the black hole. Bracket the hostname so Postfix does not attempt MX lookups \u2014 <code>[smtp.relay.example.net]:587<\/code> means &#8220;this exact host, this exact port&#8221;. Bracket syntax is the difference between a working relay and a DNS recursion loop on a host with no outbound resolver path.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For submission on 587 you also need <code>smtp_use_tls = yes<\/code> and STARTTLS enabled on the relay. If the provider offers 465 instead, use <code>smtp_tls_wrappermode = yes<\/code> with port 465; that is not STARTTLS, it is TLS from the first byte, and mixing the two settings produces a handshake that never completes. Ask the provider which one they support rather than guessing.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>postconf -e 'relayhost = [smtp.relay.example.net]:587'\npostconf -e 'smtp_use_tls = yes'\npostconf -e 'smtp_tls_security_level = encrypt'\npostconf -e 'smtp_tls_CAfile = \/etc\/ssl\/certs\/ca-certificates.crt'\npostconf -e 'smtp_sasl_auth_enable = yes'\npostconf -e 'smtp_sasl_password_maps = hash:\/etc\/postfix\/sasl_passwd'\npostconf -e 'smtp_sasl_security_options = noanonymous'\npostconf -e 'smtp_sasl_tls_security_options = noanonymous'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">relayhost syntax and the port that matters<\/h2>\n<!-- wp:paragraph -->\n<p>Set <code>relayhost<\/code> with an explicit port. Without one Postfix assumes 25 and you end up right back in the black hole. Bracket the hostname so Postfix does not attempt MX lookups \u2014 <code>[smtp.relay.example.net]:587<\/code> means &#8220;this exact host, this exact port&#8221;. Bracket syntax is the difference between a working relay and a DNS recursion loop on a host with no outbound resolver path.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For submission on 587 you also need <code>smtp_use_tls = yes<\/code> and STARTTLS enabled on the relay. If the provider offers 465 instead, use <code>smtp_tls_wrappermode = yes<\/code> with port 465; that is not STARTTLS, it is TLS from the first byte, and mixing the two settings produces a handshake that never completes. Ask the provider which one they support rather than guessing.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>postconf -e 'relayhost = [smtp.relay.example.net]:587'\npostconf -e 'smtp_use_tls = yes'\npostconf -e 'smtp_tls_security_level = encrypt'\npostconf -e 'smtp_tls_CAfile = \/etc\/ssl\/certs\/ca-certificates.crt'\npostconf -e 'smtp_sasl_auth_enable = yes'\npostconf -e 'smtp_sasl_password_maps = hash:\/etc\/postfix\/sasl_passwd'\npostconf -e 'smtp_sasl_security_options = noanonymous'\npostconf -e 'smtp_sasl_tls_security_options = noanonymous'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">relayhost syntax and the port that matters<\/h2>\n<!-- wp:paragraph -->\n<p>Set <code>relayhost<\/code> with an explicit port. Without one Postfix assumes 25 and you end up right back in the black hole. Bracket the hostname so Postfix does not attempt MX lookups \u2014 <code>[smtp.relay.example.net]:587<\/code> means &#8220;this exact host, this exact port&#8221;. Bracket syntax is the difference between a working relay and a DNS recursion loop on a host with no outbound resolver path.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For submission on 587 you also need <code>smtp_use_tls = yes<\/code> and STARTTLS enabled on the relay. If the provider offers 465 instead, use <code>smtp_tls_wrappermode = yes<\/code> with port 465; that is not STARTTLS, it is TLS from the first byte, and mixing the two settings produces a handshake that never completes. Ask the provider which one they support rather than guessing.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>postconf -e 'relayhost = [smtp.relay.example.net]:587'\npostconf -e 'smtp_use_tls = yes'\npostconf -e 'smtp_tls_security_level = encrypt'\npostconf -e 'smtp_tls_CAfile = \/etc\/ssl\/certs\/ca-certificates.crt'\npostconf -e 'smtp_sasl_auth_enable = yes'\npostconf -e 'smtp_sasl_password_maps = hash:\/etc\/postfix\/sasl_passwd'\npostconf -e 'smtp_sasl_security_options = noanonymous'\npostconf -e 'smtp_sasl_tls_security_options = noanonymous'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">relayhost syntax and the port that matters<\/h2>\n<!-- wp:paragraph -->\n<p>Set <code>relayhost<\/code> with an explicit port. Without one Postfix assumes 25 and you end up right back in the black hole. Bracket the hostname so Postfix does not attempt MX lookups \u2014 <code>[smtp.relay.example.net]:587<\/code> means &#8220;this exact host, this exact port&#8221;. Bracket syntax is the difference between a working relay and a DNS recursion loop on a host with no outbound resolver path.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For submission on 587 you also need <code>smtp_use_tls = yes<\/code> and STARTTLS enabled on the relay. If the provider offers 465 instead, use <code>smtp_tls_wrappermode = yes<\/code> with port 465; that is not STARTTLS, it is TLS from the first byte, and mixing the two settings produces a handshake that never completes. Ask the provider which one they support rather than guessing.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>postconf -e 'relayhost = [smtp.relay.example.net]:587'\npostconf -e 'smtp_use_tls = yes'\npostconf -e 'smtp_tls_security_level = encrypt'\npostconf -e 'smtp_tls_CAfile = \/etc\/ssl\/certs\/ca-certificates.crt'\npostconf -e 'smtp_sasl_auth_enable = yes'\npostconf -e 'smtp_sasl_password_maps = hash:\/etc\/postfix\/sasl_passwd'\npostconf -e 'smtp_sasl_security_options = noanonymous'\npostconf -e 'smtp_sasl_tls_security_options = noanonymous'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">relayhost syntax and the port that matters<\/h2>\n<!-- wp:paragraph -->\n<p>Set <code>relayhost<\/code> with an explicit port. Without one Postfix assumes 25 and you end up right back in the black hole. Bracket the hostname so Postfix does not attempt MX lookups \u2014 <code>[smtp.relay.example.net]:587<\/code> means &#8220;this exact host, this exact port&#8221;. Bracket syntax is the difference between a working relay and a DNS recursion loop on a host with no outbound resolver path.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For submission on 587 you also need <code>smtp_use_tls = yes<\/code> and STARTTLS enabled on the relay. If the provider offers 465 instead, use <code>smtp_tls_wrappermode = yes<\/code> with port 465; that is not STARTTLS, it is TLS from the first byte, and mixing the two settings produces a handshake that never completes. Ask the provider which one they support rather than guessing.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>postconf -e 'relayhost = [smtp.relay.example.net]:587'\npostconf -e 'smtp_use_tls = yes'\npostconf -e 'smtp_tls_security_level = encrypt'\npostconf -e 'smtp_tls_CAfile = \/etc\/ssl\/certs\/ca-certificates.crt'\npostconf -e 'smtp_sasl_auth_enable = yes'\npostconf -e 'smtp_sasl_password_maps = hash:\/etc\/postfix\/sasl_passwd'\npostconf -e 'smtp_sasl_security_options = noanonymous'\npostconf -e 'smtp_sasl_tls_security_options = noanonymous'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">relayhost syntax and the port that matters<\/h2>\n<!-- wp:paragraph -->\n<p>Set <code>relayhost<\/code> with an explicit port. Without one Postfix assumes 25 and you end up right back in the black hole. Bracket the hostname so Postfix does not attempt MX lookups \u2014 <code>[smtp.relay.example.net]:587<\/code> means &#8220;this exact host, this exact port&#8221;. Bracket syntax is the difference between a working relay and a DNS recursion loop on a host with no outbound resolver path.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For submission on 587 you also need <code>smtp_use_tls = yes<\/code> and STARTTLS enabled on the relay. If the provider offers 465 instead, use <code>smtp_tls_wrappermode = yes<\/code> with port 465; that is not STARTTLS, it is TLS from the first byte, and mixing the two settings produces a handshake that never completes. Ask the provider which one they support rather than guessing.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>postconf -e 'relayhost = [smtp.relay.example.net]:587'\npostconf -e 'smtp_use_tls = yes'\npostconf -e 'smtp_tls_security_level = encrypt'\npostconf -e 'smtp_tls_CAfile = \/etc\/ssl\/certs\/ca-certificates.crt'\npostconf -e 'smtp_sasl_auth_enable = yes'\npostconf -e 'smtp_sasl_password_maps = hash:\/etc\/postfix\/sasl_passwd'\npostconf -e 'smtp_sasl_security_options = noanonymous'\npostconf -e 'smtp_sasl_tls_security_options = noanonymous'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">relayhost syntax and the port that matters<\/h2>\n<!-- wp:paragraph -->\n<p>Set <code>relayhost<\/code> with an explicit port. Without one Postfix assumes 25 and you end up right back in the black hole. Bracket the hostname so Postfix does not attempt MX lookups \u2014 <code>[smtp.relay.example.net]:587<\/code> means &#8220;this exact host, this exact port&#8221;. Bracket syntax is the difference between a working relay and a DNS recursion loop on a host with no outbound resolver path.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For submission on 587 you also need <code>smtp_use_tls = yes<\/code> and STARTTLS enabled on the relay. If the provider offers 465 instead, use <code>smtp_tls_wrappermode = yes<\/code> with port 465; that is not STARTTLS, it is TLS from the first byte, and mixing the two settings produces a handshake that never completes. Ask the provider which one they support rather than guessing.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>postconf -e 'relayhost = [smtp.relay.example.net]:587'\npostconf -e 'smtp_use_tls = yes'\npostconf -e 'smtp_tls_security_level = encrypt'\npostconf -e 'smtp_tls_CAfile = \/etc\/ssl\/certs\/ca-certificates.crt'\npostconf -e 'smtp_sasl_auth_enable = yes'\npostconf -e 'smtp_sasl_password_maps = hash:\/etc\/postfix\/sasl_passwd'\npostconf -e 'smtp_sasl_security_options = noanonymous'\npostconf -e 'smtp_sasl_tls_security_options = noanonymous'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">relayhost syntax and the port that matters<\/h2>\n<!-- wp:paragraph -->\n<p>Set <code>relayhost<\/code> with an explicit port. Without one Postfix assumes 25 and you end up right back in the black hole. Bracket the hostname so Postfix does not attempt MX lookups \u2014 <code>[smtp.relay.example.net]:587<\/code> means &#8220;this exact host, this exact port&#8221;. Bracket syntax is the difference between a working relay and a DNS recursion loop on a host with no outbound resolver path.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For submission on 587 you also need <code>smtp_use_tls = yes<\/code> and STARTTLS enabled on the relay. If the provider offers 465 instead, use <code>smtp_tls_wrappermode = yes<\/code> with port 465; that is not STARTTLS, it is TLS from the first byte, and mixing the two settings produces a handshake that never completes. Ask the provider which one they support rather than guessing.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>postconf -e 'relayhost = [smtp.relay.example.net]:587'\npostconf -e 'smtp_use_tls = yes'\npostconf -e 'smtp_tls_security_level = encrypt'\npostconf -e 'smtp_tls_CAfile = \/etc\/ssl\/certs\/ca-certificates.crt'\npostconf -e 'smtp_sasl_auth_enable = yes'\npostconf -e 'smtp_sasl_password_maps = hash:\/etc\/postfix\/sasl_passwd'\npostconf -e 'smtp_sasl_security_options = noanonymous'\npostconf -e 'smtp_sasl_tls_security_options = noanonymous'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content --><!-- \/wp:heading --><!-- wp:post-content --><!-- wp:paragraph -->\n<p>Many hosting networks and residential ISPs drop outbound traffic on TCP port 25, and the block is silent: Postfix connects, waits for a greeting that never arrives, and the message sits in <code>maillog<\/code> until the queue manager retries and eventually bounces it after five days. If you see <code>connect to mx.example.com[203.0.113.5]:25: Connection timed out<\/code> repeated for every destination, the port is blocked upstream and no amount of Postfix tuning will change the outcome.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>The fix is to relay everything through a smarthost that listens on a submission port you can reach \u2014 usually 587 with STARTTLS, sometimes 465 with implicit TLS. This walkthrough configures <code>relayhost<\/code> and SASL credentials, keeps sender rewriting honest, and shows how to drain the existing queue without losing messages. The machine only needs to reach one host on one port, which any reasonable <a href=\"https:\/\/virtualserversvps.com\/\">KVM virtual server with unrestricted outbound 587<\/a> can do.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Confirm the block before changing config<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Test the outbound path from the same host Postfix runs on, with the same source address. A working <code>telnet<\/code> or <code>nc<\/code> to an MX on 587 tells you the network allows submission; a timeout on 25 while 587 opens proves the filter. Also check whether 25 is blocked only outbound or in both directions, because inbound mail requires a listening daemon that the provider may also restrict.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>nc -zv -w5 smtp.relay.example.net 587\nnc -zv -w5 smtp.relay.example.net 465\nnc -zv -w5 alt4.gmail-smtp-in.l.google.com 25\n\npostconf -n | grep -E 'relayhost|smtp_sasl|smtp_tls'\nmailq | tail -1<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">relayhost syntax and the port that matters<\/h2>\n<!-- wp:paragraph -->\n<p>Set <code>relayhost<\/code> with an explicit port. Without one Postfix assumes 25 and you end up right back in the black hole. Bracket the hostname so Postfix does not attempt MX lookups \u2014 <code>[smtp.relay.example.net]:587<\/code> means &#8220;this exact host, this exact port&#8221;. Bracket syntax is the difference between a working relay and a DNS recursion loop on a host with no outbound resolver path.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For submission on 587 you also need <code>smtp_use_tls = yes<\/code> and STARTTLS enabled on the relay. If the provider offers 465 instead, use <code>smtp_tls_wrappermode = yes<\/code> with port 465; that is not STARTTLS, it is TLS from the first byte, and mixing the two settings produces a handshake that never completes. Ask the provider which one they support rather than guessing.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>postconf -e 'relayhost = [smtp.relay.example.net]:587'\npostconf -e 'smtp_use_tls = yes'\npostconf -e 'smtp_tls_security_level = encrypt'\npostconf -e 'smtp_tls_CAfile = \/etc\/ssl\/certs\/ca-certificates.crt'\npostconf -e 'smtp_sasl_auth_enable = yes'\npostconf -e 'smtp_sasl_password_maps = hash:\/etc\/postfix\/sasl_passwd'\npostconf -e 'smtp_sasl_security_options = noanonymous'\npostconf -e 'smtp_sasl_tls_security_options = noanonymous'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Storing credentials without leaving them world-readable<\/h2>\n<!-- wp:paragraph -->\n<p>The password map is a plain text file plus a Berkeley DB hash. Create it with mode 600, run <code>postmap<\/code>, and delete nothing until <code>postfix reload<\/code> succeeds, because Postfix reads the .db at startup and an empty or stale map produces &#8220;SASL authentication failed; cannot authenticate to server&#8221; with no further detail. The relay host key must match the bracket form you used in <code>relayhost<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>printf '%s\\n' '[smtp.relay.example.net]:587 user@example.com:APPPASSWORD' \\\n  &gt; \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd\npostmap \/etc\/postfix\/sasl_passwd\nchmod 600 \/etc\/postfix\/sasl_passwd.db\npostfix check &amp;&amp; postfix reload<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Use an application-specific password, not the mailbox password. If the provider supports IP allowlists for relay use, add your server&#8217;s current address; that limits the blast radius of a leaked credential to one host instead of the whole account. Note the address only changes when you rebuild the server, which is another reason to keep its network configuration simple.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Sender rewriting so the relay accepts your mail<\/h2>\n<!-- wp:paragraph -->\n<p>Most relays reject envelope senders that do not belong to the authenticated account with a 553 or 550. If your application sends as <code>noreply@yourdomain<\/code>, either the relay must allow it or you must rewrite it. Set <code>sender_canonical_maps<\/code> for targeted rewrites and <code>smtp_generic_maps<\/code> for a blanket envelope rewrite; keep the header From intact where the relay permits it.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Map<\/th><th>Rewrites<\/th><th>Use when<\/th><\/tr><\/thead><tbody><tr><td>sender_canonical_maps<\/td><td>Envelope sender<\/td><td>Relay requires your account address<\/td><\/tr><tr><td>smtp_generic_maps<\/td><td>Envelope + header<\/td><td>Relay rejects mismatched From<\/td><\/tr><tr><td>canonical_maps<\/td><td>All addresses<\/td><td>Avoid unless certain<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Draining the stuck queue<\/h2>\n<!-- wp:paragraph -->\n<p>Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the relay is configured \u2014 unless <code>relayhost<\/code> applies, which it does for all outbound mail by default. Flush with <code>postqueue -f<\/code> and watch the log for a burst of <code>relay=smtp.relay.example.net<\/code> lines. Anything that does not move is usually a sender the relay rejects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For a specific message, <code>postcat -q QUEUEID<\/code> shows the headers and the recipient, and <code>postsuper -r QUEUEID<\/code> requeues it with a fresh timestamp so it jumps to the front. Do not delete deferred mail before reading the queue reason; a 550 from the relay is permanent and retrying will never help, while a 451 is transient and worth one more attempt.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>mailq\npostqueue -f\ngrep 'relay=' \/var\/log\/mail.log | tail -20\npostqueue -p | grep -c '^(connect to'<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Verifying the path end to end<\/h2>\n<!-- wp:paragraph -->\n<p>Send a test through a local submission port rather than straight to Postfix&#8217;s <code>sendmail<\/code> binary, so you exercise the same path your application uses. Then check that the Received chain shows the relay, that SPF for your domain passes at the destination, and that the message is not marked as spam. A message that leaves cleanly but lands in a spam folder points at authentication records, not at the relay.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Keep the local Postfix listening only on loopback unless you genuinely accept inbound mail. <code>inet_interfaces = loopback-only<\/code> plus a firewall that drops 25 inbound is the default posture for a relay-only host, and it eliminates a whole class of open-relay incidents. Confirm with <code>ss -lntp | grep :25<\/code> that nothing is bound to a public address.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">When the relay itself is throttled<\/h2>\n<!-- wp:paragraph -->\n<p>Relay providers impose rate limits, often 100 to 500 messages per hour on shared plans. Exceeding them yields 421 or 450 responses, and Postfix will keep deferring. Tune <code>smtp_destination_rate_delay<\/code> and <code>default_destination_recipient_limit<\/code> to stay under the limit rather than letting the provider apply backpressure for you.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Monitor the deferred count rather than individual bounces; a steady rise over ten minutes means you are being throttled or the credential expired. Setting <code>maximal_queue_lifetime = 1d<\/code> for transactional mail avoids sending stale password resets five days later. With the relay configured and the queue monitored, a port-25-blocked host behaves like any other mail sender on <a href=\"https:\/\/virtualserversvps.com\/\">a standard KVM VPS<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:heading --><!-- \/wp:post-content -->","protected":false},"excerpt":{"rendered":"<p>Draining the stuck queue Messages already deferred are still addressed to their original MX on port 25, so Postfix will keep retrying them on the old path even after the&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":1,"footnotes":""},"categories":[4],"tags":[],"class_list":["post-1597","post","type-post","status-publish","format-standard","hentry","category-security-compliance"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>When Port 25 Is Blocked: Configuring Postfix to Relay Through a Smarthost - Virtual Servers VPS Blog<\/title>\n<meta name=\"description\" content=\"Many hosting networks and residential ISPs drop outbound traffic on TCP port 25, and the block is silent: Postfix connects, waits for a greeting that\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"When Port 25 Is Blocked: Configuring Postfix to Relay Through a Smarthost\" \/>\n<meta property=\"og:description\" content=\"When Port 25 Is Blocked: Configuring Postfix to Relay Through a Smarthost\" \/>\n<meta property=\"og:url\" content=\"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/\" \/>\n<meta property=\"og:site_name\" content=\"Virtual Servers VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-11-04T02:34:04+00:00\" \/>\n<meta name=\"author\" content=\"Virtual-Servers-Vps-Editor\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Virtual-Servers-Vps-Editor\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"15 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/\",\"name\":\"When Port 25 Is Blocked: Configuring Postfix to Relay Through a Smarthost - Virtual Servers VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\"},\"datePublished\":\"2026-11-04T02:34:04+00:00\",\"author\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\"},\"description\":\"Many hosting networks and residential ISPs drop outbound traffic on TCP port 25, and the block is silent: Postfix connects, waits for a greeting that\",\"breadcrumb\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/virtualserversvps.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"When Port 25 Is Blocked: Configuring Postfix to Relay Through a Smarthost\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/\",\"name\":\"Virtual Servers VPS Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\",\"name\":\"Virtual-Servers-Vps-Editor\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"caption\":\"Virtual-Servers-Vps-Editor\"},\"sameAs\":[\"https:\/\/virtualserversvps.com\/blog\"],\"url\":\"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"When Port 25 Is Blocked: Configuring Postfix to Relay Through a Smarthost - Virtual Servers VPS Blog","description":"Many hosting networks and residential ISPs drop outbound traffic on TCP port 25, and the block is silent: Postfix connects, waits for a greeting that","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/","og_locale":"en_US","og_type":"article","og_title":"When Port 25 Is Blocked: Configuring Postfix to Relay Through a Smarthost","og_description":"When Port 25 Is Blocked: Configuring Postfix to Relay Through a Smarthost","og_url":"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/","og_site_name":"Virtual Servers VPS Blog","article_published_time":"2026-11-04T02:34:04+00:00","author":"Virtual-Servers-Vps-Editor","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Virtual-Servers-Vps-Editor","Est. reading time":"15 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/","url":"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/","name":"When Port 25 Is Blocked: Configuring Postfix to Relay Through a Smarthost - Virtual Servers VPS Blog","isPartOf":{"@id":"https:\/\/virtualserversvps.com\/blog\/#website"},"datePublished":"2026-11-04T02:34:04+00:00","author":{"@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0"},"description":"Many hosting networks and residential ISPs drop outbound traffic on TCP port 25, and the block is silent: Postfix connects, waits for a greeting that","breadcrumb":{"@id":"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/virtualserversvps.com\/blog\/postfix-smtp-port-25-blocked-vps\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/virtualserversvps.com\/blog\/"},{"@type":"ListItem","position":2,"name":"When Port 25 Is Blocked: Configuring Postfix to Relay Through a Smarthost"}]},{"@type":"WebSite","@id":"https:\/\/virtualserversvps.com\/blog\/#website","url":"https:\/\/virtualserversvps.com\/blog\/","name":"Virtual Servers VPS Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0","name":"Virtual-Servers-Vps-Editor","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","caption":"Virtual-Servers-Vps-Editor"},"sameAs":["https:\/\/virtualserversvps.com\/blog"],"url":"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/"}]}},"_links":{"self":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1597","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/comments?post=1597"}],"version-history":[{"count":1,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1597\/revisions"}],"predecessor-version":[{"id":1600,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1597\/revisions\/1600"}],"wp:attachment":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/media?parent=1597"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/categories?post=1597"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/tags?post=1597"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}