-
Notifications
You must be signed in to change notification settings - Fork 345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Sparkpost adapter #118
Closed
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
cae785f
Add Sparkpost adapter
andrewtimberlake 5bf7d39
Merge branch 'master' into master
andrewtimberlake 57f5b3e
Merge remote-tracking branch 'upstream/master'
andrewtimberlake 84e9fc1
Add free port changes from 1de0bc4
andrewtimberlake 7386ec8
Add SparkpostHelper for additional message attributes
andrewtimberlake 0e65cb5
Merge remote-tracking branch 'upstream/master'
andrewtimberlake File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
defmodule Bamboo.SparkpostAdapter do | ||
@moduledoc """ | ||
Sends email using Sparkpost's JSON API. | ||
|
||
Use this adapter to send emails through Sparkpost's API. Requires that an API | ||
key is set in the config. See `Bamboo.SparkpostHelper` for extra functions that | ||
can be used by `Bamboo.SparkpostAdapter` (tagging, merge vars, etc.) | ||
|
||
## Example config | ||
|
||
# In config/config.exs, or config/prod.exs, etc. | ||
config :my_app, MyApp.Mailer, | ||
adapter: Bamboo.SparkpostAdapter, | ||
api_key: "my_api_key" | ||
|
||
# Define a Mailer. Maybe in lib/my_app/mailer.ex | ||
defmodule MyApp.Mailer do | ||
use Bamboo.Mailer, otp_app: :my_app | ||
end | ||
""" | ||
|
||
@default_base_uri "https://api.sparkpost.com/" | ||
@send_message_path "api/v1/transmissions" | ||
@behaviour Bamboo.Adapter | ||
|
||
defmodule ApiError do | ||
defexception [:message] | ||
|
||
def exception(%{params: params, response: response}) do | ||
filtered_params = params |> Poison.decode! |> Map.put("key", "[FILTERED]") | ||
|
||
message = """ | ||
There was a problem sending the email through the Sparkpost API. | ||
|
||
Here is the response: | ||
|
||
#{inspect response, limit: :infinity} | ||
|
||
|
||
Here are the params we sent: | ||
|
||
#{inspect filtered_params, limit: :infinity} | ||
""" | ||
%ApiError{message: message} | ||
end | ||
end | ||
|
||
def deliver(email, config) do | ||
api_key = get_key(config) | ||
params = email |> convert_to_sparkpost_params |> Poison.encode! | ||
case request!(@send_message_path, params, api_key) do | ||
%{status_code: status} = response when status > 299 -> | ||
raise(ApiError, %{params: params, response: response}) | ||
response -> response | ||
end | ||
end | ||
|
||
@doc false | ||
def handle_config(config) do | ||
if config[:api_key] in [nil, ""] do | ||
raise_api_key_error(config) | ||
else | ||
config | ||
end | ||
end | ||
|
||
defp get_key(config) do | ||
case Map.get(config, :api_key) do | ||
nil -> raise_api_key_error(config) | ||
key -> key | ||
end | ||
end | ||
|
||
defp raise_api_key_error(config) do | ||
raise ArgumentError, """ | ||
There was no API key set for the Sparkpost adapter. | ||
|
||
* Here are the config options that were passed in: | ||
|
||
#{inspect config} | ||
""" | ||
end | ||
|
||
defp convert_to_sparkpost_params(email) do | ||
%{ | ||
content: %{ | ||
from: %{ | ||
name: email.from |> elem(0), | ||
email: email.from |> elem(1), | ||
}, | ||
subject: email.subject, | ||
text: email.text_body, | ||
html: email.html_body, | ||
reply_to: extract_reply_to(email), | ||
headers: drop_reply_to(email_headers(email)), | ||
}, | ||
recipients: recipients(email), | ||
} | ||
|> add_message_params(email) | ||
end | ||
|
||
defp email_headers(email) do | ||
if email.cc == [] do | ||
email.headers | ||
else | ||
Map.put_new(email.headers, "CC", Enum.map(email.cc, fn({_,addr}) -> addr end) |> Enum.join(",")) | ||
end | ||
end | ||
|
||
defp extract_reply_to(email) do | ||
email.headers["Reply-To"] | ||
end | ||
|
||
defp drop_reply_to(headers) do | ||
Map.delete(headers, "Reply-To") | ||
end | ||
|
||
defp add_message_params(sparkpost_message, %{private: %{message_params: message_params}}) do | ||
Enum.reduce(message_params, sparkpost_message, fn({key, value}, sparkpost_message) -> | ||
Map.put(sparkpost_message, key, value) | ||
end) | ||
end | ||
defp add_message_params(sparkpost_message, _), do: sparkpost_message | ||
|
||
defp recipients(email) do | ||
[] | ||
|> add_recipients(email.to) | ||
|> add_b_cc(email.cc, email.to) | ||
|> add_b_cc(email.bcc, email.to) | ||
end | ||
|
||
defp add_recipients(recipients, new_recipients) do | ||
Enum.reduce(new_recipients, recipients, fn(recipient, recipients) -> | ||
recipients ++ [%{"address" => %{ | ||
name: recipient |> elem(0), | ||
email: recipient |> elem(1), | ||
}}] | ||
end) | ||
end | ||
|
||
defp add_b_cc(recipients, new_recipients, to) do | ||
Enum.reduce(new_recipients, recipients, fn(recipient, recipients) -> | ||
recipients ++ [%{"address" => %{ | ||
name: recipient |> elem(0), | ||
email: recipient |> elem(1), | ||
header_to: Enum.map(to, fn({_,addr}) -> addr end) |> Enum.join(","), | ||
}}] | ||
end) | ||
end | ||
|
||
defp headers(api_key) do | ||
%{"content-type" => "application/json", "authorization" => api_key} | ||
end | ||
|
||
defp request!(path, params, api_key) do | ||
HTTPoison.post!("#{base_uri}/#{path}", params, headers(api_key)) | ||
end | ||
|
||
defp base_uri do | ||
Application.get_env(:bamboo, :sparkpost_base_uri) || @default_base_uri | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
defmodule Bamboo.SparkpostHelper do | ||
@moduledoc """ | ||
Functions for using features specific to Sparkpost e.g. tagging | ||
""" | ||
|
||
alias Bamboo.Email | ||
|
||
@doc """ | ||
Put extra message parameters that are used by Sparkpost | ||
|
||
Parameters set with this function are sent to Sparkpost when used along with | ||
the Bamboo.SparkpostAdapter. You can set things like `important`, `merge_vars`, | ||
and whatever else you need that the Sparkpost API supports. | ||
|
||
## Example | ||
|
||
|> put_param([:options, :open_tracking], true) | ||
|> put_param(:tags, ["foo", "bar"]) | ||
|> put_param(:meta_data, %{foo: "bar"}) | ||
""" | ||
def put_param(email, keys, value) do | ||
keys = List.wrap(keys) | ||
message_params = (email.private[:message_params] || %{}) | ||
|> ensure_keys(keys) | ||
|> update_value(keys, value) | ||
|
||
|> Email.put_private(:message_params, message_params) | ||
end | ||
|
||
@doc """ | ||
Set a single tag or multiple tags for an email. | ||
|
||
## Example | ||
|
||
tag(email, "welcome-email") | ||
tag(email, ["welcome-email", "marketing"]) | ||
""" | ||
def tag(email, tags) do | ||
put_param(email, :tags, List.wrap(tags)) | ||
end | ||
|
||
@doc ~S""" | ||
Add meta data to an email | ||
|
||
## Example | ||
|
||
|> meta_data(foo: bar) | ||
|> meta_data(%{bar: "baz") | ||
""" | ||
def meta_data(email, map) when is_map(map) do | ||
put_param(email, :metadata, map) | ||
end | ||
def meta_data(email, map) do | ||
put_param(email, :metadata, Enum.into(map, %{})) | ||
end | ||
|
||
@doc ~S""" | ||
Mark an email as transactional | ||
|
||
## Example | ||
email |> mark_transactional | ||
""" | ||
def mark_transactional(email) do | ||
put_param(email, [:options, :transactional], true) | ||
end | ||
|
||
@doc ~S""" | ||
Enable open tracking | ||
|
||
## Example | ||
email |> track_opens | ||
""" | ||
def track_opens(email) do | ||
put_param(email, [:options, :open_tracking], true) | ||
end | ||
|
||
@doc ~S""" | ||
Enable click tracking | ||
|
||
## Example | ||
email |> track_clicks | ||
""" | ||
def track_clicks(email) do | ||
put_param(email, [:options, :click_tracking], true) | ||
end | ||
|
||
defp update_value(map, keys, value) when is_list(value) do | ||
map | ||
|> update_in(keys, fn | ||
nil -> value | ||
val -> val ++ value | ||
end) | ||
end | ||
defp update_value(map, keys, value) when is_map(value) do | ||
map | ||
|> update_in(keys, fn | ||
nil -> value | ||
val -> Map.merge(val, value) | ||
end) | ||
end | ||
defp update_value(map, keys, value) do | ||
map | ||
|> put_in(keys, value) | ||
end | ||
|
||
defp ensure_keys(map, [key]) do | ||
Map.update(map, key, nil, fn(value) -> value end) | ||
end | ||
defp ensure_keys(map, [key | tail]) do | ||
Map.update(map, key, ensure_keys(%{}, tail), fn(value) -> ensure_keys(value, tail) end) | ||
end | ||
defp ensure_keys(map, key), do: ensure_keys(map, [key]) | ||
end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like there is no SparkpostHeloer yet so maybe this could be removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added the helper