Once an IP pool has been assigned to a server, there is no way to clear (unset) it from the web UI. The IP-pool dropdown on the server-edit form lists only existing pools — there is no "(none)" / blank option. Saving the form re-persists whatever pool is already selected, so servers.ip_pool_id cannot become NULL through the UI.
The Server model itself accepts a nil ip_pool_id:
# app/models/server.rb
belongs_to :ip_pool, optional: true
def validate_ip_pool_belongs_to_organization
return unless ip_pool && ip_pool_id_changed? && !organization.ip_pools.include?(ip_pool)
errors.add :ip_pool_id, "must belong to the organization"
end
…and the controller's strong parameters already permit :ip_pool_id. Only the view stands in the way of an unset.
To Reproduce
Prerequisites: postal.use_ip_pools: true in postal.yml; an organization with at least one IP pool assigned.
- Sign in as an admin user.
- Go to Admin → IP Pools and create a pool (e.g.
Test Pool); add at least one IP address to it.
- Assign the pool to an organization at
/org/<org>/ip_pools.
- Inside that organization, create a new mail server (or edit an existing one). On the server form, the IP Pool dropdown lists
Test Pool. Pick it and save.
- Click Edit on that server again.
- Try to clear the IP pool. There is no blank/none option in the dropdown — the only options are the existing pools.
- No matter what you do, you cannot save the server with
ip_pool_id = NULL.
Verifying via Rails console makes the symptom unambiguous:
bin/rails runner 'Server.find_by(permalink: "<server>").update(ip_pool_id: nil); \
puts Server.find_by(permalink: "<server>").ip_pool_id.inspect'
# → nil (the model accepts the change)
But the same change is impossible through the UI.
Expected behaviour
The IP-pool dropdown on the server-edit form should include a blank/"no pool" option (e.g. "No specific pool (use rules / default)") so that an operator can set servers.ip_pool_id = NULL. Saving with the blank option selected should persist as nil and survive a page reload.
(Same minor concern on the IP-Pool-Rule form at /org/<org>/servers/<server>/ip_pool_rules/new — the dropdown auto-selects the first pool with no placeholder. A prompt: would make the form's intent clearer for new rules. However, since IPPoolRule#ip_pool is required, this should be a prompt: rather than include_blank:.)
Environment details
- OS: any (reproduced on Ubuntu 22.04 + Docker, and on macOS dev environment)
- Browser: any (reproduced in Chrome and Safari)
- Postal version: 3.3.6 (also present on
main at time of report)
- Type: web admin UI
Additional information/context
Root cause
app/views/servers/_form.html.haml line 28:
= f.collection_select :ip_pool_id,
organization.ip_pools.includes(:ip_addresses).order("`default` desc, name asc"),
:id, :name,
{}, # ← empty options hash, no include_blank / no prompt
:class => 'input input--select'
The 5th argument is the options hash. With nothing in it, Rails' collection_select renders only the rows from the collection — no blank <option> is emitted, and the browser submits whichever pool is currently selected.
Same pattern exists on app/views/ip_pool_rules/_form.html.haml line 27.
Why this matters operationally
This bug is most visible in cloud environments where the IPs configured in the pool aren't bindable on the host (e.g. AWS Elastic IPs that aren't on the local NIC; the bind has to target the matching secondary private IP instead). When that happens, Net::SMTP#source_address = ip_address.ipv4 fails with EADDRNOTAVAIL, queued messages stop sending, and the operator's natural recovery — "let me clear the pool from the server until I sort the IP rows out" — is impossible from the UI. Recovery currently requires shell access to a Rails console, which is often not available in production.
Suggested fix
Add include_blank: "No specific pool (use rules / default)" to the server form's collection_select and prompt: "Select an IP pool" to the rule form's collection_select.
A PR with exactly that change is up at: <PASTE_PR_LINK_ONCE_OPENED>
Once an IP pool has been assigned to a server, there is no way to clear (unset) it from the web UI. The IP-pool dropdown on the server-edit form lists only existing pools — there is no "(none)" / blank option. Saving the form re-persists whatever pool is already selected, so
servers.ip_pool_idcannot becomeNULLthrough the UI.The
Servermodel itself accepts a nilip_pool_id:…and the controller's strong parameters already permit
:ip_pool_id. Only the view stands in the way of an unset.To Reproduce
Prerequisites:
postal.use_ip_pools: trueinpostal.yml; an organization with at least one IP pool assigned.Test Pool); add at least one IP address to it./org/<org>/ip_pools.Test Pool. Pick it and save.ip_pool_id = NULL.Verifying via Rails console makes the symptom unambiguous:
But the same change is impossible through the UI.
Expected behaviour
The IP-pool dropdown on the server-edit form should include a blank/"no pool" option (e.g. "No specific pool (use rules / default)") so that an operator can set
servers.ip_pool_id = NULL. Saving with the blank option selected should persist as nil and survive a page reload.(Same minor concern on the IP-Pool-Rule form at
/org/<org>/servers/<server>/ip_pool_rules/new— the dropdown auto-selects the first pool with no placeholder. Aprompt:would make the form's intent clearer for new rules. However, sinceIPPoolRule#ip_poolis required, this should be aprompt:rather thaninclude_blank:.)Environment details
mainat time of report)Additional information/context
Root cause
app/views/servers/_form.html.hamlline 28:= f.collection_select :ip_pool_id, organization.ip_pools.includes(:ip_addresses).order("`default` desc, name asc"), :id, :name, {}, # ← empty options hash, no include_blank / no prompt :class => 'input input--select'The 5th argument is the options hash. With nothing in it, Rails'
collection_selectrenders only the rows from the collection — no blank<option>is emitted, and the browser submits whichever pool is currently selected.Same pattern exists on
app/views/ip_pool_rules/_form.html.hamlline 27.Why this matters operationally
This bug is most visible in cloud environments where the IPs configured in the pool aren't bindable on the host (e.g. AWS Elastic IPs that aren't on the local NIC; the bind has to target the matching secondary private IP instead). When that happens,
Net::SMTP#source_address = ip_address.ipv4fails withEADDRNOTAVAIL, queued messages stop sending, and the operator's natural recovery — "let me clear the pool from the server until I sort the IP rows out" — is impossible from the UI. Recovery currently requires shell access to a Rails console, which is often not available in production.Suggested fix
Add
include_blank: "No specific pool (use rules / default)"to the server form'scollection_selectandprompt: "Select an IP pool"to the rule form'scollection_select.A PR with exactly that change is up at: <PASTE_PR_LINK_ONCE_OPENED>