Shared helpers for data shaping, module resolution, and small parsing tasks.
Domain modules should use these helpers when the logic is generic enough to apply across dispatch, hooks, templates, and policies.
Summary
Functions
Returns the existing atom for a binary, falling back to the original binary when the atom is unknown. Used to normalize map keys from external input without growing the global atom table.
Atomizes string keys in a map using String.to_existing_atom/1. Falls back
to returning the original map unchanged when any key is unknown — this
preserves all-or-nothing semantics so callers downstream can rely on a
single key shape (atom-only or string-only) rather than a mixed map.
Extracts and lowercases the first email address in a value.
Extracts and lowercases the domain of the first email address in a value.
Converts an atom or existing atom string into an atom.
Fetches key from map, transparently accepting either a string or atom
key. Looks up the literal key first; on miss, tries atom_or_string/1 to
resolve a matching atom key without growing the atom table. Returns
default when neither shape is present.
Reads a dotted path from a map with string or existing-atom keys.
Same as http_method!/1, but returns default for unknown input instead
of raising. Use this for untrusted input (request payloads, user maps).
Coerces an HTTP method into the lowercase atom Req and Plug expect.
Raises when the value is not one of GET / POST / PUT / PATCH / DELETE.
Resolves a module from an atom or existing Elixir module name.
Extracts the recipient domain from a value. Accepts either a payload-like
map with to/recipient keys (string or atom) or a plain email-like
string. Returns the lowercased domain part, or nil when no email-shaped
value is present.
Calculates the next scheduled timestamp for a timing struct.
Normalizes a string key (trim, downcase, replace - with _). Atoms pass
through unchanged. Returns nil for nil. Used for channel/provider keys
and other slug-style identifiers.
Recursively converts map keys to strings.
Functions
Returns the existing atom for a binary, falling back to the original binary when the atom is unknown. Used to normalize map keys from external input without growing the global atom table.
Atomizes string keys in a map using String.to_existing_atom/1. Falls back
to returning the original map unchanged when any key is unknown — this
preserves all-or-nothing semantics so callers downstream can rely on a
single key shape (atom-only or string-only) rather than a mixed map.
Extracts and lowercases the first email address in a value.
Extracts and lowercases the domain of the first email address in a value.
Converts an atom or existing atom string into an atom.
Fetches key from map, transparently accepting either a string or atom
key. Looks up the literal key first; on miss, tries atom_or_string/1 to
resolve a matching atom key without growing the atom table. Returns
default when neither shape is present.
Use anywhere config / credential / payload maps may arrive with string OR
atom keys, instead of writing Map.get(map, key) || Map.get(map, String.to_atom(key))
(which is unsafe — String.to_atom grows the atom table).
Reads a dotted path from a map with string or existing-atom keys.
Same as http_method!/1, but returns default for unknown input instead
of raising. Use this for untrusted input (request payloads, user maps).
Coerces an HTTP method into the lowercase atom Req and Plug expect.
Raises when the value is not one of GET / POST / PUT / PATCH / DELETE.
Use this for trusted input (e.g. enum-validated DB columns) where any failure represents a real invariant break.
@spec module_from_string(module() | binary() | nil, term(), term()) :: {:ok, module()} | {:error, term()}
Resolves a module from an atom or existing Elixir module name.
Extracts the recipient domain from a value. Accepts either a payload-like
map with to/recipient keys (string or atom) or a plain email-like
string. Returns the lowercased domain part, or nil when no email-shaped
value is present.
Mirrors the sender-side extraction (email_domain/1 invoked against a
from/reply_to field) for the recipient side, used by the
per-recipient-domain rate-limit scope to bucket sends by recipient ISP.
@spec scheduled_for(Ecto.Schema.t()) :: DateTime.t()
Calculates the next scheduled timestamp for a timing struct.
Normalizes a string key (trim, downcase, replace - with _). Atoms pass
through unchanged. Returns nil for nil. Used for channel/provider keys
and other slug-style identifiers.
Recursively converts map keys to strings.