Packages
phoenix_kit
1.7.63
1.7.209
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
Current section
Files
lib/mix/tasks/phoenix_kit/email_stats.ex
defmodule Mix.Tasks.PhoenixKit.Email.Stats do
@shortdoc "Display email system statistics"
@moduledoc """
Mix task to display comprehensive email system statistics.
## Usage
# Show default stats (last 30 days)
mix phoenix_kit.email.stats
# Show stats for specific date range
mix phoenix_kit.email.stats --from 2025-01-01 --to 2025-01-31
# Show stats for specific campaign
mix phoenix_kit.email.stats --campaign welcome-series
# Show detailed breakdown
mix phoenix_kit.email.stats --detailed
## Options
--from DATE Start date (YYYY-MM-DD format)
--to DATE End date (YYYY-MM-DD format)
--campaign ID Show stats for specific campaign
--detailed Show detailed breakdown by provider/template
--format FORMAT Output format: table (default), csv, json
## Examples
# Last 7 days summary
mix phoenix_kit.email.stats --from $(date -d '7 days ago' '+%Y-%m-%d')
# Export campaign stats to CSV
mix phoenix_kit.email.stats --campaign newsletter --format csv > campaign_stats.csv
# Detailed breakdown with provider performance
mix phoenix_kit.email.stats --detailed
"""
use Mix.Task
alias PhoenixKit.Modules.Emails
@impl Mix.Task
def run(args) do
Mix.Task.run("app.start")
{options, _remaining} = parse_options(args)
unless Emails.enabled?() do
Mix.shell().error("Email is not enabled. Enable it first with:")
Mix.shell().info(" PhoenixKit.Modules.Emails.enable_system()")
exit({:shutdown, 1})
end
case options[:format] do
"csv" -> show_csv_stats(options)
"json" -> show_json_stats(options)
_ -> show_table_stats(options)
end
end
defp parse_options(args) do
{options, remaining, _errors} =
OptionParser.parse(args,
strict: [
from: :string,
to: :string,
campaign: :string,
detailed: :boolean,
format: :string
]
)
# Set defaults
options =
options
|> Keyword.put_new(:format, "table")
|> Keyword.put_new(:detailed, false)
{options, remaining}
end
defp show_table_stats(options) do
stats = get_stats_data(options)
Mix.shell().info(IO.ANSI.cyan() <> "\n📧 Email Statistics" <> IO.ANSI.reset())
Mix.shell().info(String.duplicate("=", 50))
# General stats
Mix.shell().info("\n📊 Overview:")
Mix.shell().info(" Total Sent: #{format_number(stats.total_sent)}")
Mix.shell().info(
" Delivered: #{format_number(stats.delivered)} (#{format_percentage(stats.delivery_rate)})"
)
Mix.shell().info(
" Bounced: #{format_number(stats.bounced)} (#{format_percentage(stats.bounce_rate)})"
)
Mix.shell().info(
" Complaints: #{format_number(stats.complaints)} (#{format_percentage(stats.complaint_rate)})"
)
if stats.total_opened > 0 do
Mix.shell().info(
" Opened: #{format_number(stats.total_opened)} (#{format_percentage(stats.open_rate)})"
)
end
if stats.total_clicked > 0 do
Mix.shell().info(
" Clicked: #{format_number(stats.total_clicked)} (#{format_percentage(stats.click_rate)})"
)
end
if options[:detailed] do
show_detailed_breakdown(stats)
end
if options[:campaign] do
Mix.shell().info("\n📈 Campaign: #{options[:campaign]}")
end
Mix.shell().info("\n✅ Statistics generated successfully")
end
defp show_detailed_breakdown(stats) do
if Map.has_key?(stats, :by_provider) and stats.by_provider != [] do
Mix.shell().info("\n🔧 By Provider:")
for {provider, provider_stats} <- stats.by_provider do
Mix.shell().info(
" #{String.pad_trailing(provider, 15)} #{format_number(provider_stats.sent)} sent, #{format_percentage(provider_stats.delivery_rate)} delivery"
)
end
end
if Map.has_key?(stats, :by_template) and stats.by_template != [] do
Mix.shell().info("\n📝 Top Templates:")
stats.by_template
|> Enum.take(5)
|> Enum.each(fn {template, template_stats} ->
Mix.shell().info(
" #{String.pad_trailing(template || "unknown", 20)} #{format_number(template_stats.sent)} sent"
)
end)
end
end
defp show_csv_stats(options) do
stats = get_stats_data(options)
# CSV Header
Mix.shell().info("metric,value,percentage")
# CSV Data
Mix.shell().info("total_sent,#{stats.total_sent},")
Mix.shell().info("delivered,#{stats.delivered},#{stats.delivery_rate}")
Mix.shell().info("bounced,#{stats.bounced},#{stats.bounce_rate}")
Mix.shell().info("complaints,#{stats.complaints},#{stats.complaint_rate}")
Mix.shell().info("opened,#{stats.total_opened || 0},#{stats.open_rate || 0}")
Mix.shell().info("clicked,#{stats.total_clicked || 0},#{stats.click_rate || 0}")
end
defp show_json_stats(options) do
stats = get_stats_data(options)
json_output = Jason.encode!(stats, pretty: true)
Mix.shell().info(json_output)
end
defp get_stats_data(options) do
period = determine_period(options)
base_stats = Emails.get_system_stats(period)
stats =
if options[:detailed] do
Map.merge(base_stats, %{
by_provider: Emails.get_provider_performance(period),
by_template: get_template_stats(period)
})
else
base_stats
end
if options[:campaign] do
Emails.get_campaign_stats(options[:campaign])
else
stats
end
end
defp determine_period(options) do
cond do
options[:from] && options[:to] ->
{:date_range, Date.from_iso8601!(options[:from]), Date.from_iso8601!(options[:to])}
options[:from] ->
{:date_range, Date.from_iso8601!(options[:from]), Date.utc_today()}
true ->
:last_30_days
end
end
defp get_template_stats(period) do
Emails.get_template_stats(period)
end
defp format_number(number) when is_integer(number) do
number
|> to_string()
|> String.graphemes()
|> Enum.reverse()
|> Enum.chunk_every(3)
|> Enum.map(&Enum.reverse/1)
|> Enum.reverse()
|> Enum.map_join(",", &Enum.join/1)
end
defp format_number(number), do: to_string(number)
defp format_percentage(rate) when is_float(rate) do
"#{:erlang.float_to_binary(rate, decimals: 1)}%"
end
defp format_percentage(rate) when is_integer(rate) do
"#{rate}%"
end
defp format_percentage(_), do: "0%"
end