added .env support for a systemd EnvironmentFile with an example and updated email sender
This commit is contained in:
29
.env.example
Normal file
29
.env.example
Normal file
@@ -0,0 +1,29 @@
|
||||
# Phoenix / App
|
||||
PHX_HOST=mixer.example.com
|
||||
PHX_SERVER=true
|
||||
PORT=4000
|
||||
SECRET_KEY_BASE=REPLACE_WITH_64_CHAR_SECRET # generate with: mix phx.gen.secret
|
||||
|
||||
# Database
|
||||
DATABASE_URL=ecto://USER:PASSWORD@HOST/DATABASE
|
||||
ECTO_IPV6=false
|
||||
POOL_SIZE=10
|
||||
|
||||
# Clustering (leave blank if not using DNS-based clustering)
|
||||
DNS_CLUSTER_QUERY=
|
||||
|
||||
# Auth
|
||||
TOKEN_SIGNING_SECRET=REPLACE_WITH_SECRET
|
||||
|
||||
# S3 / Object Storage
|
||||
S3_ACCESS_KEY_ID=your-access-key-id
|
||||
S3_SECRET_ACCESS_KEY=your-secret-access-key
|
||||
S3_HOST=s3.amazonaws.com
|
||||
S3_BUCKET=your-bucket-name
|
||||
S3_ASSET_HOST=https://your-bucket.s3.amazonaws.com
|
||||
S3_SCHEME=https://
|
||||
S3_PORT=80
|
||||
S3_VIRTUAL_HOST=false
|
||||
|
||||
# Email (Brevo)
|
||||
BREVO_API_KEY=your-brevo-api-key
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
# .env
|
||||
.env
|
||||
|
||||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ config :mixer, MixerWeb.Endpoint,
|
||||
]
|
||||
|
||||
# Configure Swoosh API Client
|
||||
config :swoosh, api_client: Swoosh.ApiClient.Req
|
||||
config :swoosh, api_client: Swoosh.ApiClient.Hackney
|
||||
|
||||
# Disable Swoosh Local Memory Storage
|
||||
config :swoosh, local: false
|
||||
|
||||
@@ -52,7 +52,7 @@ if config_env() == :prod do
|
||||
You can generate one by calling: mix phx.gen.secret
|
||||
"""
|
||||
|
||||
host = System.get_env("PHX_HOST") || "example.com"
|
||||
host = System.get_env("PHX_HOST") || "mixer.jimweaver.com"
|
||||
|
||||
config :mixer, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
|
||||
|
||||
@@ -97,6 +97,12 @@ if config_env() == :prod do
|
||||
System.get_env("S3_ASSET_HOST") ||
|
||||
raise("Missing environment variable `S3_ASSET_HOST`!")
|
||||
|
||||
config :mixer, Mixer.Mailer,
|
||||
adapter: Swoosh.Adapters.Brevo,
|
||||
api_key:
|
||||
System.get_env("BREVO_API_KEY") ||
|
||||
raise("Missing environment variable `BREVO_API_KEY`!")
|
||||
|
||||
# ## SSL Support
|
||||
#
|
||||
# To get SSL working, you will need to add the `https` key
|
||||
|
||||
@@ -21,8 +21,7 @@ defmodule Mixer.Accounts.User.Senders.SendMagicLinkEmail do
|
||||
end
|
||||
|
||||
new()
|
||||
# TODO: Replace with your email
|
||||
|> from({"noreply", "noreply@example.com"})
|
||||
|> from({"noreply", "noreply@jimweaver.com"})
|
||||
|> to(to_string(email))
|
||||
|> subject("Your login link")
|
||||
|> html_body(body(token: token, email: email))
|
||||
@@ -31,10 +30,79 @@ defmodule Mixer.Accounts.User.Senders.SendMagicLinkEmail do
|
||||
|
||||
defp body(params) do
|
||||
# NOTE: You may have to change this to match your magic link acceptance URL.
|
||||
link = url(~p"/magic_link/#{params[:token]}")
|
||||
email_template("Your magic link", "Hello, #{params[:email]}!", """
|
||||
<p style="margin:0 0 20px 0;color:#4B5563;font-size:16px;line-height:1.6;">
|
||||
Use the button below to sign in to Mixer. This link is valid for a short time and can only be used once.
|
||||
</p>
|
||||
<p style="margin:0 0 32px 0;color:#4B5563;font-size:16px;line-height:1.6;">
|
||||
If you didn't request this, you can safely ignore this email.
|
||||
</p>
|
||||
""", link, "Sign In to Mixer")
|
||||
end
|
||||
|
||||
defp email_template(title, greeting, content, button_url, button_label) do
|
||||
"""
|
||||
<p>Hello, #{params[:email]}! Click this link to sign in:</p>
|
||||
<p><a href="#{url(~p"/magic_link/#{params[:token]}")}">#{url(~p"/magic_link/#{params[:token]}")}</a></p>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>#{title}</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background-color:#09090f;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#09090f;padding:48px 16px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" border="0" style="max-width:600px;width:100%;">
|
||||
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background-color:#0e0e18;border-radius:12px 12px 0 0;padding:32px 40px;text-align:center;border:1px solid #1e1e30;border-bottom:none;">
|
||||
<div style="font-size:28px;font-style:italic;font-weight:400;color:#e8e8f0;letter-spacing:-0.02em;font-family:Georgia,'Times New Roman',serif;">Mixer</div>
|
||||
<div style="font-size:11px;color:#4a4a6a;margin-top:6px;letter-spacing:0.1em;text-transform:uppercase;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;">Your social feed</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Accent bar -->
|
||||
<tr>
|
||||
<td style="background-color:#7c3aed;height:2px;font-size:0;line-height:0;border-left:1px solid #1e1e30;border-right:1px solid #1e1e30;"> </td>
|
||||
</tr>
|
||||
|
||||
<!-- Body -->
|
||||
<tr>
|
||||
<td style="background-color:#111120;padding:40px 40px 32px 40px;border:1px solid #1e1e30;border-top:none;border-bottom:none;">
|
||||
<h1 style="margin:0 0 20px 0;font-size:20px;font-weight:600;color:#e8e8f0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;letter-spacing:-0.01em;">#{greeting}</h1>
|
||||
#{content}
|
||||
<!-- CTA Button -->
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="border-radius:8px;background-color:#7c3aed;">
|
||||
<a href="#{button_url}" style="display:inline-block;padding:13px 28px;font-size:14px;font-weight:600;color:#ffffff;text-decoration:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;letter-spacing:0.01em;border-radius:8px;">#{button_label}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="background-color:#0e0e18;border-radius:0 0 12px 12px;padding:24px 40px;border:1px solid #1e1e30;border-top:1px solid #1e1e30;">
|
||||
<p style="margin:0 0 8px 0;font-size:12px;color:#4a4a6a;line-height:1.6;font-family:'Courier New',Courier,monospace;letter-spacing:0.02em;">
|
||||
This is an automated message — replies to this address are not monitored.
|
||||
</p>
|
||||
<p style="margin:0;font-size:12px;color:#35354a;line-height:1.6;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;">
|
||||
You received this because you have an account on Mixer.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,8 +13,7 @@ defmodule Mixer.Accounts.User.Senders.SendNewUserConfirmationEmail do
|
||||
@impl true
|
||||
def send(user, token, _) do
|
||||
new()
|
||||
# TODO: Replace with your email
|
||||
|> from({"noreply", "noreply@example.com"})
|
||||
|> from({"noreply", "noreply@jimweaver.com"})
|
||||
|> to(to_string(user.email))
|
||||
|> subject("Confirm your email address")
|
||||
|> html_body(body(token: token))
|
||||
@@ -22,11 +21,79 @@ defmodule Mixer.Accounts.User.Senders.SendNewUserConfirmationEmail do
|
||||
end
|
||||
|
||||
defp body(params) do
|
||||
url = url(~p"/confirm_new_user/#{params[:token]}")
|
||||
link = url(~p"/confirm_new_user/#{params[:token]}")
|
||||
email_template("Confirm your email", "Welcome to Mixer!", """
|
||||
<p style="margin:0 0 20px 0;color:#4B5563;font-size:16px;line-height:1.6;">
|
||||
Thanks for signing up. Just one more step — confirm your email address to activate your account.
|
||||
</p>
|
||||
<p style="margin:0 0 32px 0;color:#4B5563;font-size:16px;line-height:1.6;">
|
||||
If you didn't create an account on Mixer, you can safely ignore this email.
|
||||
</p>
|
||||
""", link, "Confirm Email Address")
|
||||
end
|
||||
|
||||
defp email_template(title, greeting, content, button_url, button_label) do
|
||||
"""
|
||||
<p>Click this link to confirm your email:</p>
|
||||
<p><a href="#{url}">#{url}</a></p>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>#{title}</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background-color:#09090f;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#09090f;padding:48px 16px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" border="0" style="max-width:600px;width:100%;">
|
||||
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background-color:#0e0e18;border-radius:12px 12px 0 0;padding:32px 40px;text-align:center;border:1px solid #1e1e30;border-bottom:none;">
|
||||
<div style="font-size:28px;font-style:italic;font-weight:400;color:#e8e8f0;letter-spacing:-0.02em;font-family:Georgia,'Times New Roman',serif;">Mixer</div>
|
||||
<div style="font-size:11px;color:#4a4a6a;margin-top:6px;letter-spacing:0.1em;text-transform:uppercase;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;">Your social feed</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Accent bar -->
|
||||
<tr>
|
||||
<td style="background-color:#7c3aed;height:2px;font-size:0;line-height:0;border-left:1px solid #1e1e30;border-right:1px solid #1e1e30;"> </td>
|
||||
</tr>
|
||||
|
||||
<!-- Body -->
|
||||
<tr>
|
||||
<td style="background-color:#111120;padding:40px 40px 32px 40px;border:1px solid #1e1e30;border-top:none;border-bottom:none;">
|
||||
<h1 style="margin:0 0 20px 0;font-size:20px;font-weight:600;color:#e8e8f0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;letter-spacing:-0.01em;">#{greeting}</h1>
|
||||
#{content}
|
||||
<!-- CTA Button -->
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="border-radius:8px;background-color:#7c3aed;">
|
||||
<a href="#{button_url}" style="display:inline-block;padding:13px 28px;font-size:14px;font-weight:600;color:#ffffff;text-decoration:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;letter-spacing:0.01em;border-radius:8px;">#{button_label}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="background-color:#0e0e18;border-radius:0 0 12px 12px;padding:24px 40px;border:1px solid #1e1e30;border-top:1px solid #1e1e30;">
|
||||
<p style="margin:0 0 8px 0;font-size:12px;color:#4a4a6a;line-height:1.6;font-family:'Courier New',Courier,monospace;letter-spacing:0.02em;">
|
||||
This is an automated message — replies to this address are not monitored.
|
||||
</p>
|
||||
<p style="margin:0;font-size:12px;color:#35354a;line-height:1.6;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;">
|
||||
You received this because you signed up for Mixer.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,8 +13,7 @@ defmodule Mixer.Accounts.User.Senders.SendPasswordResetEmail do
|
||||
@impl true
|
||||
def send(user, token, _) do
|
||||
new()
|
||||
# TODO: Replace with your email
|
||||
|> from({"noreply", "noreply@example.com"})
|
||||
|> from({"noreply", "noreply@jimweaver.com"})
|
||||
|> to(to_string(user.email))
|
||||
|> subject("Reset your password")
|
||||
|> html_body(body(token: token))
|
||||
@@ -22,11 +21,79 @@ defmodule Mixer.Accounts.User.Senders.SendPasswordResetEmail do
|
||||
end
|
||||
|
||||
defp body(params) do
|
||||
url = url(~p"/password-reset/#{params[:token]}")
|
||||
link = url(~p"/password-reset/#{params[:token]}")
|
||||
email_template("Reset your password", "Password reset request", """
|
||||
<p style="margin:0 0 20px 0;color:#4B5563;font-size:16px;line-height:1.6;">
|
||||
We received a request to reset the password for your Mixer account. Click the button below to choose a new one.
|
||||
</p>
|
||||
<p style="margin:0 0 32px 0;color:#4B5563;font-size:16px;line-height:1.6;">
|
||||
If you didn't request a password reset, you can safely ignore this email — your password will not change.
|
||||
</p>
|
||||
""", link, "Reset My Password")
|
||||
end
|
||||
|
||||
defp email_template(title, greeting, content, button_url, button_label) do
|
||||
"""
|
||||
<p>Click this link to reset your password:</p>
|
||||
<p><a href="#{url}">#{url}</a></p>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>#{title}</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background-color:#09090f;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#09090f;padding:48px 16px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" border="0" style="max-width:600px;width:100%;">
|
||||
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background-color:#0e0e18;border-radius:12px 12px 0 0;padding:32px 40px;text-align:center;border:1px solid #1e1e30;border-bottom:none;">
|
||||
<div style="font-size:28px;font-style:italic;font-weight:400;color:#e8e8f0;letter-spacing:-0.02em;font-family:Georgia,'Times New Roman',serif;">Mixer</div>
|
||||
<div style="font-size:11px;color:#4a4a6a;margin-top:6px;letter-spacing:0.1em;text-transform:uppercase;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;">Your social feed</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Accent bar -->
|
||||
<tr>
|
||||
<td style="background-color:#7c3aed;height:2px;font-size:0;line-height:0;border-left:1px solid #1e1e30;border-right:1px solid #1e1e30;"> </td>
|
||||
</tr>
|
||||
|
||||
<!-- Body -->
|
||||
<tr>
|
||||
<td style="background-color:#111120;padding:40px 40px 32px 40px;border:1px solid #1e1e30;border-top:none;border-bottom:none;">
|
||||
<h1 style="margin:0 0 20px 0;font-size:20px;font-weight:600;color:#e8e8f0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;letter-spacing:-0.01em;">#{greeting}</h1>
|
||||
#{content}
|
||||
<!-- CTA Button -->
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="border-radius:8px;background-color:#7c3aed;">
|
||||
<a href="#{button_url}" style="display:inline-block;padding:13px 28px;font-size:14px;font-weight:600;color:#ffffff;text-decoration:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;letter-spacing:0.01em;border-radius:8px;">#{button_label}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="background-color:#0e0e18;border-radius:0 0 12px 12px;padding:24px 40px;border:1px solid #1e1e30;border-top:1px solid #1e1e30;">
|
||||
<p style="margin:0 0 8px 0;font-size:12px;color:#4a4a6a;line-height:1.6;font-family:'Courier New',Courier,monospace;letter-spacing:0.02em;">
|
||||
This is an automated message — replies to this address are not monitored.
|
||||
</p>
|
||||
<p style="margin:0;font-size:12px;color:#35354a;line-height:1.6;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;">
|
||||
You received this because a password reset was requested for your Mixer account.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user