postalserver/postal · error · Postal::Error
Invalid email address
Error message
Invalid email address
What it means
Raised by Postal's DomainsController#verify action (email verification branch). Postal can verify domain ownership by emailing a code to one of a fixed set of administrative mailboxes generated per domain (@domain.verification_email_addresses, e.g. postmaster@/abuse@host). If the submitted email_address param is not in that list, Postal raises Postal::Error instead of sending the verification mail, because the recipient would not prove control of the domain.
Source
Thrown at app/controllers/domains_controller.rb:83
else
respond_to do |wants|
wants.html { flash.now[:alert] = "We couldn't verify your domain. Please double check you've added the TXT record correctly." }
wants.json { render json: { flash: { alert: "We couldn't verify your domain. Please double check you've added the TXT record correctly." } } }
end
end
when "Email"
if params[:code]
if @domain.verification_token == params[:code].to_s.strip
@domain.mark_as_verified
redirect_to_with_json [:setup, organization, @server, @domain], notice: "#{@domain.name} has been verified successfully. You now need to configure your DNS records."
else
respond_to do |wants|
wants.html { flash.now[:alert] = "Invalid verification code. Please check and try again." }
wants.json { render json: { flash: { alert: "Invalid verification code. Please check and try again." } } }
end
end
elsif params[:email_address].present?
raise Postal::Error, "Invalid email address" unless @domain.verification_email_addresses.include?(params[:email_address])
AppMailer.verify_domain(@domain, params[:email_address], current_user).deliver
if @domain.owner.is_a?(Server)
redirect_to_with_json verify_organization_server_domain_path(organization, @server, @domain, email_address: params[:email_address])
else
redirect_to_with_json verify_organization_domain_path(organization, @domain, email_address: params[:email_address])
end
end
end
end
def setup
return if @domain.verified?
redirect_to [:verify, organization, @server, @domain], alert: "You can't set up DNS for this domain until it has been verified."
end
def checkView on GitHub (pinned to d038eaa8c7)
Solutions
- Submit one of the exact addresses exposed by the domain's verification_email_addresses (the values the UI dropdown offers)
- Strip and normalize params[:email_address] (e.g. .to_s.strip.downcase) before comparing, if the mismatch is only whitespace/case
- If the domain was renamed, reload the verify page so the form uses the newly generated addresses
- As a maintainer, replace the raise with a respond_to flash/alert (mirroring the invalid-code branch above it) so bad input returns a 4xx instead of a 500
- Verify the domain via the DNS/records method instead of the email method
Example fix
# before
raise Postal::Error, "Invalid email address" unless @domain.verification_email_addresses.include?(params[:email_address])
# after
unless @domain.verification_email_addresses.include?(params[:email_address].to_s.strip.downcase)
return respond_to do |wants|
wants.html { flash.now[:alert] = "Choose one of the listed verification email addresses." }
wants.json { render json: { flash: { alert: "Choose one of the listed verification email addresses." } }, status: :unprocessable_entity }
end
end Defensive patterns
Strategy: validation
Validate before calling
# before POSTing the verify form / calling verify
address = params[:email_address].to_s.strip.downcase
allowed = @domain.verification_email_addresses.map { |a| a.downcase }
return redirect_back(alert: "Choose one of the listed verification addresses.") unless allowed.include?(address) Type guard
def permitted_verification_address?(domain, candidate) domain.verification_email_addresses.map(&:downcase).include?(candidate.to_s.strip.downcase) end
Try / catch
begin # verify call with email_address rescue Postal::Error => e # bad input from the user: re-render with a 4xx and an alert, do not retry flash.now[:alert] = e.message render :verify, status: :unprocessable_entity end
Prevention
- Render the email-address choice as a closed dropdown sourced from verification_email_addresses, not a free-text input
- Normalize (strip/downcase) the param before comparing against the generated list
- When a domain is renamed, invalidate cached verify forms so stale addresses are never submitted
- Prefer DNS-record verification when mailbox access is unreliable
When it happens
Trigger: POST to the domain verify action with mode=Email and params[:email_address] set to any mailbox not in @domain.verification_email_addresses - e.g. typing a custom address into the form, tampering with the form/API payload, or resubmitting a stale form after the domain name was changed (old generated addresses no longer match).
Common situations: Editing the email input in the verify-domain UI before submitting; scripts/API calls guessing an address; case or whitespace differences between the submitted param and the generated list; a saved form from before the domain record was renamed.
Related errors
- Invalid endpoint class name '#{class_name}'
- Invalid endpoint class name '#{class_name}'
- OIDC cannot be used unless enabled in the configuration
- No host was given for the request
- Could not resolve '#{@host}' to any IP address
AI-assisted analysis of postalserver/postal@d038eaa8c7 (2026-08-21).
Data as JSON: /api/errors/74df7bf8c9fd99ef.
Report an issue: GitHub.