Git Product home page Git Product logo

mailer's Introduction

Hanami 🌸

The web, with simplicity.

Version

This branch contains the code for hanami: 2.2

Frameworks

Hanami is a full-stack Ruby web framework. It's made up of smaller, single-purpose libraries.

This repository is for the full-stack framework, which provides the glue that ties all the parts together:

These components are designed to be used independently or together in a Hanami application.

Status

Gem Version CI Test Coverage Depfu

Installation

Hanami supports Ruby (MRI) 3.1+.

gem install hanami

Usage

hanami new bookshelf
cd bookshelf && bundle
bundle exec hanami server # visit http://localhost:2300

Please follow along with the Getting Started guide.

Donations

You can give back to Open Source, by supporting Hanami development via GitHub Sponsors.

Supporters

Contact

Community

We strive for an inclusive and helpful community. We have a Code of Conduct to handle controversial cases. In general, we expect you to be nice with other people. Our hope is for a great software and a great Community.

Contributing Open Source Helpers

  1. Fork it ( https://github.com/hanami/hanami/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

In addition to contributing code, you can help to triage issues. This can include reproducing bug reports, or asking for vital information such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to subscribe to hanami on CodeTriage.

Tests

To run all test suite:

$ bundle exec rake

To run all the unit tests:

$ bundle exec rspec spec/unit

To run all the integration tests:

$ bundle exec rspec spec/integration

To run a single test:

$ bundle exec rspec path/to/spec.rb

Development Requirements

  • Ruby >= 3.1
  • Bundler
  • Node.js (MacOS)

Versioning

Hanami uses Semantic Versioning 2.0.0

Copyright

Copyright © 2014–2024 Hanami Team – Released under MIT License.

mailer's People

Contributors

alfonsouceda avatar benbach avatar cllns avatar davydovanton avatar deepj avatar depfu[bot] avatar glaszig avatar inescoelho avatar jduarte avatar jodosha avatar lucasallan avatar nickgnd avatar okuramasafumi avatar rogeriozambon avatar rosafaria avatar timoschilling avatar vyper avatar weppos avatar wuarmin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mailer's Issues

Multipart rendering

Lotus::Mailer must carry on an instance method that renders a single template.

module Lotus
  module Mailer
    def render(format)
      # ...
    end
  end
end

Using the template informations (#2 ) that we have, it must render that template.

Template Engines

Like we do with Lotus::View, we want to be able to support a variety of template engines via tilt gem.
Tilt is able to determine the engine, by looking at the last extension that we have in a template. Eg. .erb, will be rendered with ERB. Please have a look at Tilt docs.

Make compatible with mime-types 3.0

lotus-mailer depends on mail.
Starting from mail-2.6.8, this gem will depend on mime-types-3.0, because this can save a few megabytes of memory for Ruby applications.

I've tried to run our test suite with mail-2.6.8-rc2 (in our Gemfile), but this test fails:

  1) Failure:
Lotus::Mailer::.deliver::test delivery with hardcoded values#test_0004_interprets the prepare statement [/Users/luca/Code/lotus/mailer/test/delivery_test.rb:60]:
--- expected
+++ actual
@@ -1 +1 @@
-"application/pdf; charset=UTF-8; filename=invoice.pdf"
+"application/pdf; filename=invoice.pdf"

I reported this problem and got a reply from @halostatue (maintainer of mime-types) who explains the reason of this failure.


We need to make sure that if our users will do a bundle update and use under the hood mail-2.6.8, won't be affected by any problem.

Associated templates

Be able to include Lotus::Mailer in Ruby objects.

When included, it exposes one class method .templates.
It's a Hash that holds the templates that the mailer will be able to render.

Location And Name

By convention, it should look for templates in the same directory. The name of those templates must be the "underscored" version of the class name.

Given a mailer named InvoiceMailer, that lives in lib/bookshelf/mailers, it must lookup for templates in that directory with a invoice_mailer.*.* pattern.

Format

Once a matching template is found, it must be associated to .templates.
The key of the hash is the format expressed as a Symbol, while the value is a template object.

This object must hold two references: the first is the absolute path of where the file is located, while the second value is the body of the file.

The format is the first extension for that file. For instance, if we have a template named invoice_mailer.html.erb, the format will be :html.

For a depth understanding of what we aim for, please have a look at http://lotusrb.org/guides/views/templates

The only difference with Lotus::View, is that we have multiple templates per class. This is useful to send multipart emails.

Laziness

The lookup of those templates must be lazy. It MUST be triggered by Lotus::Mailer.load!.

Please have a look at Lotus::View.load!.

Customization

A developer may want to customize the template for a given format.
They can use .template followed by the format (which MUST be stored as a Symbol) and the path to the template. This path must be joined with the root defined by the configuration (#1).

class InvoiceMailer
  include Lotus::Mailer
  template :html, 'path/to/template'
end

Problems with nesting of multipart message with pdf-attachment

also discussed in the chat
i have this mailer:

# locals: tempfile, client_contact_email, test_mail

module Mailers
  class Invoice
    include Hanami::Mailer

    from '<[email protected]>'
    to :recipient
    subject :subject

    def prepare
      Hanami.logger.debug "Start #{__method__} in #{self.class.inspect} #{self.object_id} :
        tempfile: #{tempfile.path} size: #{tempfile.size  }\n
        client_contact_email: #{client_contact_email.pretty_inspect}\n
        test_mail: #{test_mail.pretty_inspect}\n
        mail: #{mail.pretty_inspect}"

      Hanami.logger.debug "During #{__method__} in #{self.class.inspect} #{self.object_id} :

      mail.attachments['invoice.pdf'] = tempfile.open.read
      # from https://www.rubydoc.info/gems/hanami-mailer#Attachments

      Hanami.logger.debug "End #{__method__} in #{self.class.inspect} #{self.object_id} :
        mail: #{mail.attachments.pretty_inspect}"
    end

    private

    def recipient
      if test_mail
        '[email protected]'
      else
        client_contact_email
      end
    end

    def subject
      'Invoice for service'
    end

  end
end

The mailer is called from a non-crud controller that prepares the context and locals, the pdf is generated by an interactor.

what i'm expecting the email to be structured like:

multipart/mixed
  multipart/alternative
    text/plain
    text/html
  application/pdf (i.e. in case of pdf attachment)

what i'm receiving:

multipart/alternative
  text/plain
  text/html
  application/pdf (i.e. in case of pdf attachment)

the practical effect:
thunderbird renders the right text body, depending on user preference (txt or html), but does not show the attachment as an attached file (it is however in the email source code as a bas63-encoded block)
ios mail shows an empty email body, and only the pdf attachment as a file, since it is the last entry, and apparently the mail client then only renders this.

for reference some issues about this from the Mail gem:
mikel/mail#853
mikel/mail#1022
mikel/mail#862
mikel/mail#590

here's a gist that prevents this from happening when using the gem only:
https://gist.github.com/steve500002/bfc4b027d93c0c04f126

any other detail needed?

`prepare` DSL block called multiple times

In the context of Hanami app:

Let's say we have my_app/config/environment.rb configured like this:

module Foo
  def self.included(klass)
    puts 'Foo was included'
  end
end

Hanami.configure do
  mount Web::Application, at: '/'

  mailer do
    root 'lib/my_app/mailers'
    delivery :test

    prepare do
      include Foo
    end
  end
end

And I have a single mailer class in project. Then it properly calls prepare block once for mailers indicated by output:

Foo was included

But if I want to add additional configuration for some environment, as soon as I specify another mailer block, everything in my initial prepare block gets called another time on app initialisation:

Hanami.configure do
  mount Web::Application, at: '/'

  mailer do
    root 'lib/my_app/mailers'
    delivery :test

    prepare do
      include Foo
    end
  end

  environment :development do
    mailer do
      # Nothing here
    end
  end
end
Foo was included
Foo was included

Delivery Method

Let developers to specify a global delivery method.
This is done via the framework configuration (#1 ).

Lotus::Mailer.configure do
  # ...
  delivery_method :sendmail

  # or
  delivery_method :smtp, address: "localhost", port: 1025

  # or
  delivery :test

  # or
  delivery_method MyCustomDeliveryMethod, foo: 'bar'
end

Lotus::Mailer will depend on mail gem. This library already supports this scenario.
Our goal is to store that settings and to pass them at the single mail instance, that we're gonna to deliver.

In other words, we MUST avoid this:

Mail.defaults do
  delivery_method :smtp, address: "localhost", port: 1025
end

In favor of this:

mail = Mail.new
mail.delivery_method Lotus::Mailer.configuration.delivery_method

Configuration

We should be able to configure the framework via a DSL.

Lotus::Mailer.configure do
  # ...
end

Lotus::Mailer MUST be a module.

That configuration is made available at Lotus::Mailer.configuration. Please note that this is used only internally and not exposed as public API to developers.

Lazyness

The given block MUST not be evaluated when defined. This operation IS postponed when a developer triggers one of the following methods:

Lotus::Mailer.load!

# or

Lotus::Mailer.configure do
  # ...
end.load!

Please note that for compatibility with other Lotus frameworks, both the .load! triggers MUST be implemented, but their effect is equivalent.

Root

The initial setting that we should be able to setup is root. It can be a String, a Pathname or an object that implements #to_pathname that express the root directory where to find the mailer templates. See Lotus::Utils::Kernel.Pathname.

Lotus::Mailer.configure do
  root '/path/to/root'
end

If a developer doesn't set that value, the default is ".".
That #root method acts both as a getter and setter, so if we do Lotus::Mailer.configuration.root, it MUST return the current value. Please have a look at Lotus::View::Configuration.

Prepare

We want to let developers to customize the mail message at the low level.

This MUST be done via #prepare. It's a method that if implemented by a developer, it MUST be called as the last step of the initialization process of a mailer.

One pratical use case are attachments. The mail gem supports them and let developers to fiddle with the mail object (@mail) that a mailer holds, it will allow them to attach a file and Lotus::Mailer won't add any code for this!

class SubscriptionMailer
  include Lotus::Mailer

  def prepare
    mail.attachments['invoice.pdf'] = '/path/to/invoice.pdf'
  end
end

We MUST expose a reader mail that returns the internal @mail object, which is an instance of Mail (from the mail gem).

Shouldn't mailer belong to a specific application?

Hi I was wondering about the following:
Shouldn't a mailer belong to a specific application. An admin app might send different emails than the regular web interface.

Maybe I am understanding it all wrong, but for mailing is another way of serving content like rendering an html page is

Amazon SES raise SMTP Error `Net::SMTPFatalError (554 Transaction failed: Empty address)`

When using hanami/mailer with Amazon SES, this raises Net::SMTPFatalError (554 Transaction failed: Empty address) because of the empty Return-Path header.

Hanami::Mailer.new seems to set nil to Return-Path and send an email with empty Return-Path finally.
I think Hanami::Mailer shouldn't send Return-Path if that is nil.

I don't know that point out is correct because I'm not familiar with mail protocol.
What do you think about that?

Mailer delivery DSL

When a Ruby object includes Lotus::Mailer, a developer can customize delivery details.

From

This is the field that will determine the sender of the email.

class InvoiceMailer
  include Lotus::Mailer

  from "[email protected]"
  # or
  from -> { customized_sender }

  def customized_sender
    "user-#{ user.id }@example.com"
  end
end

It can accept a string or a Proc, to determine the value.

In case of a Proc, it MUST be used when the mailer is initialized and the block evaluated in the context of that instance. Ruby's instance_eval is your friend ;)

To

This represent one or more email addresses that will be the recipients of the mail.
The DSL is similar to from, and we want to be able to accept both String and Proc.

Subject

This is the subject of the message.
The DSL is similar to from, and we want to be able to accept both String and Proc.

Can't deliver email until CustomMailer.templates is called

Hello!

I'm trying to add hanami-mailer to my project, however I seem to have found a bug.
When I try to deliver email I get an exception
App::Mail::Mailers::Foo::Bar.deliver

NoMethodError: undefined method `fetch' for nil:NilClass
from /home/graudeejs/.asdf/installs/ruby/2.6.3/lib/ruby/gems/2.6.0/gems/hanami-mailer-1.3.1/lib/hanami/mailer/dsl.rb:85:in `templates'

However if I call .templates before .deliver it works

App::Mail::Mailers::Foo::Bar.templates
App::Mail::Mailers::Foo::Bar.deliver

I've created a small repo where this issue can be replicated:
https://github.com/graudeejs/hanami-mailer-issue-79

$ ./bin/console
[1] pry(main)> App::Mail::Mailers::Foo::Bar.deliver
NoMethodError: undefined method `fetch' for nil:NilClass
from /home/graudeejs/.asdf/installs/ruby/2.6.3/lib/ruby/gems/2.6.0/gems/hanami-mailer-1.3.1/lib/hanami/mailer/dsl.rb:85:in `templates'
[2] pry(main)> 

New release for hanami 2.x

Hey @jodosha ,

a new hanami-mailer release for hanami 2.x would be appreciated. hanami-mailer 1.3.x depends on hanami-utils 1.3

Could not find compatible versions

Because hanami >= 2.0.0 depends on hanami-utils ~> 2.0
  and hanami-mailer >= 1.3.0 depends on hanami-utils ~> 1.3,
  hanami >= 2.0.0 is incompatible with hanami-mailer >= 1.3.0.
So, because Gemfile depends on hanami ~> 2.0.3
  and Gemfile depends on hanami-mailer ~> 1.3,
  version solving has failed.

Thanks

prepare block in the lib/ configuration doesn't work due to load order

given a new app created using
lotus new demo

and a new mailer created using
lotus generate mailer Welcome

and a shared module to set the from in all mailers following the guide here http://lotusrb.org/guides/mailers/share-code/

then the configuration block in the generated 'lib/demo.rb' file as follows:

require 'lotus/model'
require 'lotus/mailer'
Dir["#{ __dir__ }/demo/**/*.rb"].each { |file| require_relative file }

Lotus::Model.configure do
  ##
  # Database adapter
  #
  # Available options:
  #
  #  * Memory adapter
  #    adapter type: :memory, uri: 'memory://localhost/demo_development'
  #
  #  * SQL adapter
  #    adapter type: :sql, uri: 'sqlite://db/demo_development.sqlite3'
  #    adapter type: :sql, uri: 'postgres://localhost/demo_development'
  #    adapter type: :sql, uri: 'mysql://localhost/demo_development'
  #
  adapter type: :file_system, uri: ENV['DEMO_DATABASE_URL']

  ##
  # Database mapping
  #
  # Intended for specifying application wide mappings.
  #
  # You can specify mapping file to load with:
  #
  # mapping "#{__dir__}/config/mapping"
  #
  # Alternatively, you can use a block syntax like the following:
  #
  mapping do
    # collection :users do
    #   entity     User
    #   repository UserRepository
    #
    #   attribute :id,   Integer
    #   attribute :name, String
    # end
  end
end.load!

Lotus::Mailer.configure do
  root "#{ __dir__ }/demo/mailers"

  # See http://lotusrb.org/guides/mailers/delivery
  delivery do
    development :test
    test        :test
    # production :stmp, address: ENV['SMTP_PORT'], port: 1025
  end

  prepare do
    include Mailers::DefaultSender
  end
end.load!

This doesn't work as the included hook of the lib/demo/mailers/welcome.rb class when including Lotus::Mailers gets called before the configuration for the shared code is defined as one of the globbed require_relatives in Dir["#{ __dir__ }/demo/**/*.rb"].each { |file| require_relative file } and the underlying .copy! method is called before there is anything configured https://github.com/lotus/mailer/blob/master/lib/lotus/mailer.rb#L89

Perhaps this isn't a bug and I'm just placing configuration in the wrong place? If it is I'm happy to help out in any way I can.

Thanks

Louis

Lotus::Mailer::MissingDeliveryDataError overwriting Mail::ArgumentError

The default messages that come from Mail::ArgumentError are being overwritten by the error class from Lotus::Mailer, I had a little typo on my :smtp config hash. But all that Lotus said was to check from or or, but those were ok. Probably those params will be the ones missing the most, but having access to the complete, and more descritive, error message from Mail is good. I had to overwrite Lotus::Mailer#deliver to debug the original error message just too see that my :authentication value was wrong.

Multipart delivery

When a class includes Lotus::Mailer, then it will expose .render.
It delivers a multipart email, by looking at all the associated templates (#2) and render them ( #4 ).
It accepts a set of locals (#5 ), instantiate a mailer and deliver the email.

InvoiceMailer.deliver(user: user, invoice: invoice)

Under the hood it will look like this:

def self.deliver(locals)
  new(locals).deliver
end

Please note that once a mailer is instantiate, it MUST be immutable.

uninitialized constant Hanami::Mailer::Configuration (NameError)

Hanami.app.register_provider :hanami_mailer do
start do

require 'hanami/mailer'

 configuration = Hanami::Mailer::Configuration.new do |config|
   config.root            = File.expand_path(__dir__, "base")
   config.delivery_method = :test
 end

 register "hanami_mailer", configuration

end
end

When registering a provider or using the block of code (in the 'start' block above) from the examples directory as an initializer getting error:

uninitialized constant Hanami::Mailer::Configuration (NameError)

configuration = Hanami::Mailer::Configuration.new do |config|
                              ^^^^^^^^^^^^^^^

Ruby: ruby 3.3.0preview2
Hanami: 2.0.3
OS: 14.0 (23A344)

Rendering Context

When a mailer renders a template (#4 ), we want to be able to interpolate parts of it, with values coming from the mailer itself.

For instance, if we have the following template:

Hello <%= user.name %>,

We want to be able to replace <%= user.name %> with the actual name of the current user.

We MUST have two sources for these values (simplified version of Lotus::View).

Concrete Methods

If a mailer defines a concrete method, it must be available as rendering context for the template.

class WelcomeMailer
  include Lotus::Mailer

  def greeting
    "Ahoy"
  end
end
<%= greeting %>!

Locals

The other source is named locals. It's a set of objects passed to the constructor.

class InvitationMailer
  include Lotus::Mailer
end

luca = User.new(first_name: 'Luca', username: 'jodosha')
mailer = InvoiceMailer.new(user: luca)

Then we MUST be able to use user from the template.

Please note that the name of the key (:user) is important in this process.
If we change it with :person, then we must reference that object as person in the template.

Use other solution for preview emails in dev env

I think that mail previewing is very important part for developers. That's why I suggest to add letter_opener gem for this. Integration with this gem is very simple and I hope that it'll be helpful for others developers too.

What do you think?

/cc @hanami/contributors and @hanami/core-team

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.