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

  1. Pass the exact class name: SMTPENDpoint is wrong, SMTPEndpoint is right (same for HTTPEndpoint, AddressEndpoint)
  2. Guard before calling: split on '#' and check Route::ENDPOINT_TYPES.include?(class_name)
  3. Derive the reference from a real object: "#{endpoint.class.name}##{endpoint.uuid}"
  4. 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

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


AI-assisted analysis of postalserver/postal@d038eaa8c7 (2026-08-21). Data as JSON: /api/errors/053f0b024c3271e0. Report an issue: GitHub.