Packages

phoenix_kit

1.7.208
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib phoenix_kit email send_profile.ex
Raw

lib/phoenix_kit/email/send_profile.ex

defmodule PhoenixKit.Email.SendProfile do
@moduledoc """
Ecto schema for newsletter send profiles ("Send Settings").
A send profile references a core `PhoenixKit.Integrations` connection
(by `integration_uuid` — no FK, since integrations live in
`phoenix_kit_settings`) and carries per-account send parameters: sender
identity, signature, rate limits, and provider-specific `advanced`
extras. Multiple profiles may share one integration. At most one
profile may be `is_default` (the service-wide default), enforced by a
partial unique index on `phoenix_kit_email_send_profiles`.
"""
use Ecto.Schema
use PhoenixKit.SchemaPrefix
import Ecto.Changeset
alias PhoenixKit.Email.ProviderOptions
alias PhoenixKit.Integrations.Providers
@type t :: %__MODULE__{}
@primary_key {:uuid, UUIDv7, autogenerate: true}
schema "phoenix_kit_email_send_profiles" do
field(:name, :string)
field(:integration_uuid, UUIDv7)
field(:provider_kind, :string)
field(:from_name, :string)
field(:from_email, :string)
field(:reply_to, :string)
field(:signature_html, :string)
field(:signature_text, :string)
field(:rate_per_hour, :integer)
field(:rate_per_day, :integer)
field(:pause_seconds, :integer, default: 0)
field(:advanced, :map, default: %{})
field(:enabled, :boolean, default: true)
field(:is_default, :boolean, default: false)
timestamps(type: :utc_datetime)
end
def changeset(send_profile, attrs) do
send_profile
|> cast(attrs, [
:name,
:integration_uuid,
:provider_kind,
:from_name,
:from_email,
:reply_to,
:signature_html,
:signature_text,
:rate_per_hour,
:rate_per_day,
:pause_seconds,
:advanced,
:enabled,
:is_default
])
|> validate_required([:name, :integration_uuid, :provider_kind])
|> validate_inclusion(:provider_kind, valid_provider_kinds())
|> validate_number(:rate_per_hour, greater_than_or_equal_to: 0)
|> validate_number(:rate_per_day, greater_than_or_equal_to: 0)
|> validate_number(:pause_seconds, greater_than_or_equal_to: 0)
|> validate_provider_kind_matches_integration()
|> cast_advanced()
|> unique_constraint(:is_default,
name: :idx_email_send_profiles_default,
message: "another profile is already the default"
)
end
@doc """
The provider kinds a send profile may point at.
Derived from the core Integrations registry rather than hardcoded, so an
email provider added there — built-in, or contributed by another module
via `integration_providers/0` — becomes selectable in Send Settings
without a change here. `:email_send` is the capability that makes a
provider a *sender*; an AI or storage provider must never be pickable.
"""
def valid_provider_kinds do
:email_send
|> Providers.with_capability()
|> Enum.map(& &1.key)
end
# `advanced` holds provider-specific send settings. Re-cast it through
# ProviderOptions so only keys the chosen provider actually declares can
# be persisted: without this, params could smuggle arbitrary keys into a
# JSONB column that the delivery worker later feeds to a Swoosh adapter.
# Also drops stale keys when a profile is repointed at another provider.
defp cast_advanced(changeset) do
case fetch_change(changeset, :advanced) do
{:ok, advanced} ->
provider_kind = get_field(changeset, :provider_kind)
put_change(changeset, :advanced, ProviderOptions.cast(provider_kind, advanced))
:error ->
changeset
end
end
# Cross-field consistency: the profile's declared provider_kind must
# match the actual provider of the integration it points at, so the two
# sources of truth (this row's provider_kind vs. the Integrations
# connection's real provider) can't drift apart. Only runs once both
# fields resolve to a value — validate_required/inclusion already cover
# the missing/invalid cases on their own. Adds a :base error (rather
# than crashing) when the integration can't be found, e.g. it was
# deleted after this profile was created.
defp validate_provider_kind_matches_integration(changeset) do
integration_uuid = get_field(changeset, :integration_uuid)
provider_kind = get_field(changeset, :provider_kind)
if is_binary(integration_uuid) and is_binary(provider_kind) do
case PhoenixKit.Integrations.get_integration_by_uuid(integration_uuid) do
{:ok, %{provider: ^provider_kind}} ->
changeset
{:ok, %{provider: actual_provider}} ->
add_error(
changeset,
:base,
"provider_kind (#{provider_kind}) does not match the integration's provider (#{actual_provider})"
)
{:error, _} ->
add_error(changeset, :base, "integration not found")
end
else
changeset
end
end
end