Packages
phoenix_kit
1.7.10
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_export.ex
defmodule Mix.Tasks.PhoenixKit.Email.Export do
@shortdoc "Export email tracking data to CSV or JSON"
@moduledoc """
Mix task to export email tracking data to various formats.
## Usage
# Export all logs to CSV
mix phoenix_kit.email.export --format csv
# Export specific campaign to JSON
mix phoenix_kit.email.export --format json --campaign newsletter
# Export logs from date range
mix phoenix_kit.email.export --from 2025-01-01 --to 2025-01-31
# Export with custom filters
mix phoenix_kit.email.export --status delivered --tag authentication --provider aws_ses
## Options
--format FORMAT Export format: csv, json (default: csv)
--output FILE Output file path (default: stdout)
--from DATE Start date (YYYY-MM-DD)
--to DATE End date (YYYY-MM-DD)
--campaign ID Filter by campaign ID
--status STATUS Filter by status (sent, delivered, bounced, etc.)
--tag TAG Filter by message tag/type (authentication, marketing, etc.)
--provider PROVIDER Filter by email provider (aws_ses, smtp, local, etc.)
--limit NUMBER Limit number of records (default: no limit)
--include-events Include email events (opens, clicks) in export
## Output Formats
### CSV Format
Exports logs with columns: id, message_id, to, from, subject, status, sent_at, delivered_at, provider, campaign_id
### JSON Format
Exports complete log objects with all fields and optional events array
## Examples
# Basic CSV export
mix phoenix_kit.email.export --format csv > email_logs.csv
# Campaign analysis with events
mix phoenix_kit.email.export --campaign welcome-series --include-events --format json > campaign_analysis.json
# Recent bounced emails for investigation
mix phoenix_kit.email.export --status bounced --from $(date -d '7 days ago' '+%Y-%m-%d') --format csv > recent_bounces.csv
# Authentication emails analysis
mix phoenix_kit.email.export --tag authentication --from 2025-01-01 --include-events > auth_emails.csv
# Provider performance comparison
mix phoenix_kit.email.export --provider aws_ses --from 2025-01-01 --include-events > ses_performance.csv
"""
use Mix.Task
alias PhoenixKit.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.")
exit({:shutdown, 1})
end
# Build filters from options
filters = build_filters(options)
# Get data
logs = fetch_logs(filters, options)
# Export in requested format
case options[:format] do
"json" -> export_json(logs, options)
_ -> export_csv(logs, options)
end
log_export_summary(logs, options)
end
defp parse_options(args) do
{options, remaining, _errors} =
OptionParser.parse(args,
strict: [
format: :string,
output: :string,
from: :string,
to: :string,
campaign: :string,
status: :string,
tag: :string,
provider: :string,
limit: :integer,
include_events: :boolean
]
)
# Set defaults
options =
options
|> Keyword.put_new(:format, "csv")
|> Keyword.put_new(:include_events, false)
{options, remaining}
end
defp build_filters(options) do
filters = %{}
filters =
if options[:from] do
Map.put(filters, :sent_after, Date.from_iso8601!(options[:from]))
else
filters
end
filters =
if options[:to] do
Map.put(filters, :sent_before, Date.from_iso8601!(options[:to]))
else
filters
end
filters =
if options[:campaign] do
Map.put(filters, :campaign_id, options[:campaign])
else
filters
end
filters =
if options[:status] do
Map.put(filters, :status, options[:status])
else
filters
end
filters =
if options[:tag] do
Map.put(filters, :message_tag, options[:tag])
else
filters
end
filters =
if options[:provider] do
Map.put(filters, :provider, options[:provider])
else
filters
end
filters =
if options[:limit] do
Map.put(filters, :limit, options[:limit])
else
filters
end
filters
end
defp fetch_logs(filters, options) do
logs = Emails.list_logs(filters)
if options[:include_events] do
# Load events for each log
Enum.map(logs, fn log ->
events = Emails.list_events_for_log(log.id)
Map.put(log, :events, events)
end)
else
logs
end
end
defp export_csv(logs, options) do
output_stream = get_output_stream(options[:output])
# CSV Header
header = build_csv_header(options)
IO.puts(output_stream, header)
# CSV Rows
Enum.each(logs, fn log ->
row = build_csv_row(log, options)
IO.puts(output_stream, row)
end)
if options[:output] do
File.close(output_stream)
end
end
defp export_json(logs, options) do
json_data = %{
exported_at: DateTime.utc_now(),
total_records: length(logs),
filters: build_filters(options),
logs: logs
}
json_output = Jason.encode!(json_data, pretty: true)
if options[:output] do
File.write!(options[:output], json_output)
else
Mix.shell().info(json_output)
end
end
defp get_output_stream(nil), do: :stdio
defp get_output_stream(file_path) do
{:ok, file} = File.open(file_path, [:write])
file
end
defp build_csv_header(options) do
base_headers = [
"id",
"message_id",
"to",
"from",
"subject",
"status",
"sent_at",
"delivered_at",
"provider",
"campaign_id",
"template_name",
"size_bytes",
"retry_count"
]
if options[:include_events] do
base_headers ++ ["events_count", "last_opened", "total_clicks"]
else
base_headers
end
|> Enum.join(",")
end
defp build_csv_row(log, options) do
base_values = [
log.id,
escape_csv(log.message_id),
escape_csv(log.to),
escape_csv(log.from),
escape_csv(log.subject),
escape_csv(log.status),
format_datetime(log.sent_at),
format_datetime(log.delivered_at),
escape_csv(log.provider),
escape_csv(log.campaign_id),
escape_csv(log.template_name),
log.size_bytes || 0,
log.retry_count || 0
]
values =
if options[:include_events] && Map.has_key?(log, :events) do
events = log.events || []
opens = Enum.filter(events, &(&1.event_type == "open"))
clicks = Enum.filter(events, &(&1.event_type == "click"))
last_opened =
opens
|> Enum.map(& &1.occurred_at)
|> Enum.max(DateTime, fn -> nil end)
base_values ++
[
length(events),
format_datetime(last_opened),
length(clicks)
]
else
base_values
end
values
|> Enum.map_join(",", &to_string/1)
end
defp escape_csv(nil), do: ""
defp escape_csv(value) when is_binary(value) do
if String.contains?(value, [",", "\"", "\n"]) do
"\"#{String.replace(value, "\"", "\"\"")}\""
else
value
end
end
defp escape_csv(value), do: to_string(value)
defp format_datetime(nil), do: ""
defp format_datetime(datetime) do
DateTime.to_iso8601(datetime)
end
defp log_export_summary(logs, options) do
count = length(logs)
format = options[:format] || "csv"
Mix.shell().error("✅ Exported #{count} emails to #{format} format")
if options[:output] do
Mix.shell().error("📄 Output saved to: #{options[:output]}")
end
# Basic stats summary
if count > 0 do
status_counts =
logs
|> Enum.group_by(& &1.status)
|> Enum.map(fn {status, logs} -> {status, length(logs)} end)
|> Enum.sort_by(fn {_status, count} -> count end, :desc)
Mix.shell().error("📊 Status breakdown:")
for {status, status_count} <- status_counts do
percentage = Float.round(status_count / count * 100, 1)
Mix.shell().error(" #{String.pad_trailing(status, 12)} #{status_count} (#{percentage}%)")
end
end
end
end