thoughtbot / bamboo Goto Github PK
View Code? Open in Web Editor NEWTestable, composable, and adapter based Elixir email library for devs that love piping.
Home Page: https://hex.pm/packages/bamboo
License: MIT License
Testable, composable, and adapter based Elixir email library for devs that love piping.
Home Page: https://hex.pm/packages/bamboo
License: MIT License
This is probably going to come a bit later since I have never needed to send an attachment in an email, it's always a link back to the app. I know this is useful to people though so it should be added.
Usually assert_delivered_email(Email.welcome_email(user)
will work fine, but there are cases when it won't
I think I should add assert_delivered_email(subject: "Something")
in case you can't/don't want to assert against a whole email.
This would be helpful if parts of the email have state that would make it difficult to test the whole email. For example, if a token is generated in the text/html_body from Phoenix.Token, then the body would be different every time since the token would change. So it might be better to assert just a few of the most important fields.
@scrogson suggested improving the folder names for clarity
I would create
lib/bamboo/adapters
movemandrill_adapter.ex
tolib/bamboo/adapters/mandrill.ex
same for thetest_adapter.ex
makelib/bamboo/adapters/mandrill/email.ex
andlib/bamboo/adapters/test/mailbox.ex
and update the module names accordingly
use Bamboo.Phoenix
should be raise a helpful error if the view is not added. Almost done with this
While using this in one of our applications we had to do something like this
users = Repo.all(User)
if length(users) > 0 do
Emails.welcome_email(users) |> Mailer.deliver_async
end
Maybe deliver_async should throw a warning? Maybe an option to allow empty_recipients?
deliver_async(allow_empty_recipients: true)
Maybe this could be done at the mailer level via an option?
defmodule MyApp.Mailer do
use Bamboo.Mailer, otp_app: :my_app
@allow_empty_recipients true
end
Possible libraries to use
It feels like it would be nicer if the adapters were not coupled to the bamboo codebase.
What do you think?
Great for viewing sent emails in dev
It should normalize the email addresses, and should only match against the keys that are in the options. For asserting just what you pass in, do something like this
defmacro assert_delivered_email(email_params) do
# Normalize the address as well
quote do
import ExUnit.Assertions
assert_received({:delivered_email, unquote(email_params)})
end
end
Instead have Bamboo.EmailAddress.format
It should also have a second argument that is passed a keyword list (or maybe a map) that contains what type of address it is (from, cc, bcc, to)
That way you can do something like
defimpl Bamboo.EmailAddress, for: MyApp.User do
# Add the app's name when sending the email from a user
def format(user, %{type: :from}) do
%{name: "#{user.name} (NameOfApp)", address: user.address}
end
def format(user, _opts) do
%{name: user.name, address: user.address
end
end
See https://github.com/phoenixframework/phoenix/blob/740bda4250f17944ae12661e2905724cf0fd00df/lib/phoenix/view.ex#L170 for how to do that. Maybe allow setting a default layout use Bamboo.Phoenix, view: EmailView, layout: {LayoutView, "email.html"}
. Also add function like put_layout
?
Instead grab the adapter and other info (API key, etc.) at runtime. This makes it easier to change during tests, or while using in iex. That way you can switch to a TestAdapter or LocalAdapter long enough to send some tests emails and then switch it back if you wanted to. Also would clean up the Bamboo tests
EDIT: I'm not totally sure about this. Probably going to hold off on it for a bit
When switching to #52 it might make sense to rename the function
So it raises if you pass keys that don't exist in the Bamboo.Email struct. Also required elixir 1.2
The merge vars can be pretty handy but it's a pain to use them. Something like
email |> put_merge_params(users, fn(user) -> %{first_name: user.first_name} end)
That way it can be used for test and dev environments
Much easier to work with, and easier to see what's going on at a glance. I've started work on this and it's almost done
https://github.com/paulcsmith/bamboo/blob/master/lib/bamboo/phoenix.ex#L14
Remove that and let people set it like this
use Bamboo.Phoenix, render_with: MyApp.EmailView
Raise helpful error if render_with
is not set
Add an adapter for the Mailgun API
I'm starting on this one.
Great for use in staging environments
whitelist_emails: System.get_env("WHITELIST_EMAILS") # if nil, [] or "" then it will allow all emails
It only imports two modules and most people won't use this that much (usually will have just a couple email modules). So I think it's better to just let people import the modules they need.
This is almost done
If you use deliver_async TestMailbox.one will often fail because the email hasn't quite been sent yet. The Mailbox should wait a bit. Maybe using receive
This will
SentEmail.reset
So you can see that something went wrong
This makes it easier to find out when you accidentally forget to render the body, but still allows you to leave it empty if you really want to.
The reason that you might want to set empty html and text bodies is that some adapters may be used to render emails from another service. For example, you can use Mandrill to render responsive templates that are rendered on their server, so you would not set a text or html body
Task.async
is linking the task process with the parent process so if the latter dies first it will kill the former.
For example, with Phoenix it would mean that a HTTP request could finish before we actually had a chance to send the email.
Elixir 1.2 introduced Task.async_no_link which could be used here.
Happy to submit a PR if you think that's the way to go.
Right now view rendering uses Phoenix.View.render_existing which is ok except that it can make it easy to think you're sending an email with both formats, but in reality you had a typo in a template name and you only send a text or html version. Instead I think this should be more explicit so you get more immediate and helpful feedback.
Right now you do: render("template_name")
which will render a text or html template if it exists
Instead I think it should be render("template_name.html")
and render("template_name.text")
if you have just one type of template to send.
If you want to send text and html then you would do render(:template_name)
and it will render both and raise if either one is missing.
Would work something like this
assert_one_email_sent Emails.welcome_email(user)
That API key should be removed from the errors if it is anything but nil or an empty string. We don't want keys accidentally leaked into the logs
I think Task.Supervisor.start_child should do the trick. Task.start_link links to the caller process and if the caller process dies, so does the background task. This is probably not preferred.
nil
doesn't really mean much so it can be hard to figure out where it's coming from and what it means. Instead the default should be a struct like %UnknownMailbox{}
or %UnsetMailbox{}
. Something along those lines
Add actual code
https://github.com/paulcsmith/bamboo/blob/master/test/lib/bamboo/mandrill_adapter_test.exs#L17
Could probably make a new hex package out of this actually
I think this makes the library a bit easier to use. That way deliver
uses the strategy you want and if you want to deliver right away then you use deliver_sync
. This essentially flips how the current mailer works.
Still not 100% sold on the name though. Function name ideas:
So that Phoenix is not required to use Bamboo
I wonder if there should be some way to test that the task worker is added so you don't deploy to prod and find out that you forgot to add the Bamboo.TaskSupervisor to your supervisor.
Or maybe tie the test mailbox to the process so that each new process has a fresh mailbox? Not really sure quite yet
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.