postalserver/postal · error · Postal::Error
Invalid endpoint class name '#{class_name}'
Error message
Invalid endpoint class name '#{class_name}' What it means
AdditionalRouteEndpoint.find_by_endpoint is the lookup that turns an 'ClassName#uuid' reference (as stored/exposed by _endpoint) back into the join record for a route's extra endpoints. It performs the same guard as Route#_endpoint=: the class part must be in Route::ENDPOINT_TYPES or Postal::Error is raised, so unknown class names cannot be constantized.
Source
Thrown at app/models/additional_route_endpoint.rb:27
# endpoint_type :string(255)
# endpoint_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class AdditionalRouteEndpoint < ApplicationRecord
belongs_to :route
belongs_to :endpoint, polymorphic: true
validate :validate_endpoint_belongs_to_server
validate :validate_wildcard
validate :validate_uniqueness
def self.find_by_endpoint(endpoint)
class_name, id = endpoint.split("#", 2)
unless Route::ENDPOINT_TYPES.include?(class_name)
raise Postal::Error, "Invalid endpoint class name '#{class_name}'"
end
return unless uuid = class_name.constantize.find_by_uuid(id)
where(endpoint_type: class_name, endpoint_id: uuid).first
end
def _endpoint
"#{endpoint_type}##{endpoint.uuid}"
end
def _endpoint=(value)
if value && value =~ /\#/
class_name, id = value.split("#", 2)
unless Route::ENDPOINT_TYPES.include?(class_name)
raise Postal::Error, "Invalid endpoint class name '#{class_name}'"
end
View on GitHub (pinned to d038eaa8c7)
Solutions
- Pass the exact class name: SMTPENDpoint is wrong, SMTPEndpoint is right (same for HTTPEndpoint, AddressEndpoint)
- Guard before calling: split on '#' and check Route::ENDPOINT_TYPES.include?(class_name)
- Derive the reference from a real object: "#{endpoint.class.name}##{endpoint.uuid}"
- Return a 4xx/validation error to the client instead of letting the raise become a 500
Example fix
# before
AdditionalRouteEndpoint.find_by_endpoint("HttpEndpoint##{id}") # -> Postal::Error
# after
AdditionalRouteEndpoint.find_by_endpoint("HTTPEndpoint##{id}")
# or guard first:
# return nil unless Route::ENDPOINT_TYPES.include?(value.split("#", 2).first) Defensive patterns
Strategy: type-guard
Validate before calling
# before calling find_by_endpoint
class_name, = endpoint.split("#", 2)
return nil unless Route::ENDPOINT_TYPES.include?(class_name) Type guard
def lookupable_endpoint?(value)
class_name, id = value.to_s.split("#", 2)
!class_name.nil? && Route::ENDPOINT_TYPES.include?(class_name) && id.present?
end Try / catch
begin
AdditionalRouteEndpoint.find_by_endpoint(ref)
rescue Postal::Error => e
# unknown type in the reference: treat as not-found/bad-request, log the ref
Rails.logger.warn("rejected endpoint reference #{ref.inspect}: #{e.message}")
nil
end Prevention
- Pass through references produced by _endpoint rather than re-composing them
- Validate external input against Route::ENDPOINT_TYPES at the API boundary
- Keep casing consistent (exact constant names) in any client that builds these strings
- Unit-test helper methods that parse 'Class#uuid' against the allowed list
When it happens
Trigger: Calling AdditionalRouteEndpoint.find_by_endpoint('SmtpEndpoint#abc') (wrong casing), 'WebhookEndpoint#abc' (invented name), or any 'X#id' string whose X is not SMTPEndpoint/HTTPEndpoint/AddressEndpoint - typically from admin tooling or API code resolving endpoint references submitted by clients.
Common situations: Client SDKs and scripts passing endpoint type names with different casing conventions; payloads reused from other Postal versions where names differ; debugging consoles pasting references with typos; code that builds the 'Class#uuid' pair from unvalidated input.
Related errors
- Invalid endpoint class name '#{class_name}'
- Invalid email address
- 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/053f0b024c3271e0.
Report an issue: GitHub.