lotus-mailer 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 376a615362a8b09e1ed02f98bc625e788dcc781f
4
- data.tar.gz: 756ab718d25f778a6c30a2eb922f19ec32cc1df9
3
+ metadata.gz: 010f5e6d0f8485fe6d90af54b84181fceb6d639e
4
+ data.tar.gz: be1bbd47898677b7f6a6179377d86c0457944899
5
5
  SHA512:
6
- metadata.gz: d217f39b95869118b6d339b124bd301f320a06e6425ae02951947eb319226d977aa6d428e7e19736c8e9e556426183678fdddda8d75a74f77a0d8ec806458a45
7
- data.tar.gz: 5ecb5dfad868949e6ee9bfbbeb509d4fe4d5dd04487bb0bc2309f2fae4d7e6e63a1f122ca086c49a96116bbbaf7a9ffb4556ac964fed422551ea9e7c99d38d81
6
+ metadata.gz: 71838f83857156392f4cd227676ac025a3dca6ba8021b0c05aa5c03b4e00ffa0f87eb5df58c7d1333d9a21520237f9e9dcb2c9294035a3992966e2381285eba3
7
+ data.tar.gz: 9eefa471b50fa187498e31a58eb484dcded69b0f43f540655f42a2966c055cd5d165a4e895aa43b6e529e2ffad66d5fab5eea4f0bd38493277d81a9056988339
@@ -1,2 +1,10 @@
1
1
  # Lotus::Router
2
2
  Mail for Ruby applications
3
+
4
+ ## v0.1.0 - 2015-09-30
5
+ ### Added
6
+ - [Ines Coelho & Rosa Faria & Luca Guidi] Email delivery
7
+ - [Ines Coelho & Rosa Faria & Luca Guidi] Attachments
8
+ - [Ines Coelho & Rosa Faria & Luca Guidi] Multipart rendering
9
+ - [Ines Coelho & Rosa Faria & Luca Guidi] Configuration
10
+ - [Ines Coelho & Rosa Faria & Luca Guidi] Official support for Ruby 2.0
data/README.md CHANGED
@@ -42,24 +42,375 @@ Or install it yourself as:
42
42
 
43
43
  ## Usage
44
44
 
45
- TODO: Write usage instructions here
45
+ ### Conventions
46
46
 
47
- ## Versioning
47
+ * Templates are searched under `Lotus::Mailer.configuration.root`, set this value according to your app structure (eg. `"app/templates"`).
48
+ * A mailer will look for a template with a file name that is composed by its full class name (eg. `"articles/index"`).
49
+ * A template must have two concatenated extensions: one for the format and one for the engine (eg. `".html.erb"`).
50
+ * The framework must be loaded before rendering the first time: `Lotus::Mailer.load!`.
48
51
 
49
- __Lotus::Mailer__ uses [Semantic Versioning 2.0.0](http://semver.org)
52
+ ### Mailers
50
53
 
51
- ## Development
54
+ A simple mailer looks like this:
52
55
 
53
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
56
+ ```ruby
57
+ require lotus/mailer
54
58
 
55
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
59
+ class InvoiceMailer
60
+ include Lotus::Mailer
61
+ end
62
+ ```
56
63
 
57
- ## Contributing
64
+ A mailer with `.to` and `.from` addresses and mailer delivery:
58
65
 
59
- Bug reports and pull requests are welcome on GitHub at https://github.com/lotus/mailer.
66
+ ```ruby
67
+ require lotus/mailer
60
68
 
61
- ## Copyright
69
+ Lotus::Mailer.configuration do
70
+ delivery_method :smtp,
71
+ address: "smtp.gmail.com",
72
+ port: 587,
73
+ domain: "example.com",
74
+ user_name: ENV['SMTP_USERNAME'],
75
+ password: ENV['SMTP_PASSWORD'],
76
+ authentication: "plain",
77
+ enable_starttls_auto: true
78
+ end.load!
62
79
 
63
- Copyright © 2015 Luca Guidi – Released under MIT License
80
+ class WelcomeMailer
81
+ include Lotus::Mailer
82
+
83
+ from 'noreply@sender.com'
84
+ to 'noreply@recipient.com'
85
+ subject 'Welcome'
86
+ end
87
+
88
+ WelcomeMailer.deliver
89
+ ```
90
+
91
+ ### Locals
92
+
93
+ The set of objects passed in the `deliver` call are called `locals` and are avaliable inside the mailer and the template.
94
+
95
+ ```ruby
96
+ require lotus/mailer
97
+
98
+ User = Struct.new(:name, :username)
99
+ user = User.new('Luca', 'jodosha')
100
+
101
+ Lotus::Mailer.load!
102
+
103
+ class WelcomeMailer
104
+ include Lotus::Mailer
105
+
106
+ from 'noreply@sender.com'
107
+ subject 'Welcome'
108
+ to :recipient
109
+
110
+ private
111
+
112
+ def recipient
113
+ user.email
114
+ end
115
+ end
116
+
117
+ InvoiceMailer.deliver(user: luca)
118
+ ```
119
+
120
+ The corresponding `erb` file:
121
+
122
+ ```erb
123
+ Hello <%= user.name %>!
124
+ ```
125
+
126
+ ### Scope
127
+
128
+ All public methods defined in the mailer are accessible from the template:
129
+
130
+ ```ruby
131
+ require lotus/mailer
132
+
133
+ class WelcomeMailer
134
+ include Lotus::Mailer
135
+
136
+ from 'noreply@sender.com'
137
+ to 'noreply@recipient.com'
138
+ subject 'Welcome'
139
+
140
+ def greeting
141
+ 'Ahoy'
142
+ end
143
+ end
144
+ ```
145
+
146
+ ```erb
147
+ <h2><%= greeting %></h2>
148
+ ```
149
+
150
+ ### Template
151
+
152
+ The template file must be located under the relevant `root` and must match the inflected snake case of the mailer class name.
153
+
154
+ ```ruby
155
+ # Given this root
156
+ Lotus::Mailer.configuration.root # => #<Pathname:app/templates>
157
+
158
+ # For InvoiceMailer, it looks for:
159
+ # * app/templates/invoice_mailer.html.erb
160
+ # * app/templates/invoice_mailer.txt.erb
161
+ ```
162
+
163
+ If we want to specify a different template, we can do:
164
+
165
+ ```ruby
166
+ class InvoiceMailer
167
+ include Lotus::Mailer
168
+
169
+ template 'invoice'
170
+ end
171
+
172
+ # It will look for:
173
+ # * app/templates/invoice.html.erb
174
+ # * app/templates/invoice.txt.erb
175
+ ```
176
+
177
+ ### Engines
178
+
179
+ The builtin rendering engine is [ERb](http://en.wikipedia.org/wiki/ERuby).
64
180
 
181
+ This is the list of the supported engines.
182
+ They are listed in order of **higher precedence**, for a given extension.
183
+ For instance, if [ERubis](http://www.kuwata-lab.com/erubis/) is loaded, it will be preferred over ERb to render `.erb` templates.
65
184
 
185
+ <table>
186
+ <tr>
187
+ <th>Engine</th>
188
+ <th>Extensions</th>
189
+ </tr>
190
+ <tr>
191
+ <td>Erubis</td>
192
+ <td>erb, rhtml, erubis</td>
193
+ </tr>
194
+ <tr>
195
+ <td>ERb</td>
196
+ <td>erb, rhtml</td>
197
+ </tr>
198
+ <tr>
199
+ <td>Redcarpet</td>
200
+ <td>markdown, mkd, md</td>
201
+ </tr>
202
+ <tr>
203
+ <td>RDiscount</td>
204
+ <td>markdown, mkd, md</td>
205
+ </tr>
206
+ <tr>
207
+ <td>Kramdown</td>
208
+ <td>markdown, mkd, md</td>
209
+ </tr>
210
+ <tr>
211
+ <td>Maruku</td>
212
+ <td>markdown, mkd, md</td>
213
+ </tr>
214
+ <tr>
215
+ <td>BlueCloth</td>
216
+ <td>markdown, mkd, md</td>
217
+ </tr>
218
+ <tr>
219
+ <td>Asciidoctor</td>
220
+ <td>ad, adoc, asciidoc</td>
221
+ </tr>
222
+ <tr>
223
+ <td>Builder</td>
224
+ <td>builder</td>
225
+ </tr>
226
+ <tr>
227
+ <td>CSV</td>
228
+ <td>rcsv</td>
229
+ </tr>
230
+ <tr>
231
+ <td>CoffeeScript</td>
232
+ <td>coffee</td>
233
+ </tr>
234
+ <tr>
235
+ <td>WikiCloth</td>
236
+ <td>wiki, mediawiki, mw</td>
237
+ </tr>
238
+ <tr>
239
+ <td>Creole</td>
240
+ <td>wiki, creole</td>
241
+ </tr>
242
+ <tr>
243
+ <td>Etanni</td>
244
+ <td>etn, etanni</td>
245
+ </tr>
246
+ <tr>
247
+ <td>Haml</td>
248
+ <td>haml</td>
249
+ </tr>
250
+ <tr>
251
+ <td>Less</td>
252
+ <td>less</td>
253
+ </tr>
254
+ <tr>
255
+ <td>Liquid</td>
256
+ <td>liquid</td>
257
+ </tr>
258
+ <tr>
259
+ <td>Markaby</td>
260
+ <td>mab</td>
261
+ </tr>
262
+ <tr>
263
+ <td>Nokogiri</td>
264
+ <td>nokogiri</td>
265
+ </tr>
266
+ <tr>
267
+ <td>Plain</td>
268
+ <td>html</td>
269
+ </tr>
270
+ <tr>
271
+ <td>RDoc</td>
272
+ <td>rdoc</td>
273
+ </tr>
274
+ <tr>
275
+ <td>Radius</td>
276
+ <td>radius</td>
277
+ </tr>
278
+ <tr>
279
+ <td>RedCloth</td>
280
+ <td>textile</td>
281
+ </tr>
282
+ <tr>
283
+ <td>Sass</td>
284
+ <td>sass</td>
285
+ </tr>
286
+ <tr>
287
+ <td>Scss</td>
288
+ <td>scss</td>
289
+ </tr>
290
+ <tr>
291
+ <td>Slim</td>
292
+ <td>slim</td>
293
+ </tr>
294
+ <tr>
295
+ <td>String</td>
296
+ <td>str</td>
297
+ </tr>
298
+ <tr>
299
+ <td>Yajl</td>
300
+ <td>yajl</td>
301
+ </tr>
302
+ </table>
303
+
304
+
305
+ ### Configuration
306
+
307
+ __Lotus::Mailer__ can be configured with a DSL that determines its behavior.
308
+ It supports a few options:
309
+
310
+ ```ruby
311
+ require 'lotus/mailer'
312
+
313
+ Lotus::Maler.configure do
314
+ # Set the root path where to search for templates
315
+ # Argument: String, Pathname, #to_pathname, defaults to the current directory
316
+ #
317
+ root '/path/to/root'
318
+
319
+ # Set the default charset for emails
320
+ # Argument: String, defaults to "UTF-8"
321
+ #
322
+ default_charset 'iso-8859'
323
+
324
+ # Set the delivery method
325
+ # Argument: Symbol
326
+ # Argument: Hash, optional configurations
327
+ delivery_method :stmp
328
+ ```
329
+
330
+ ### Attachments
331
+
332
+ Attachments can be added with the following API:
333
+
334
+ ```ruby
335
+ class InvoiceMailer
336
+ include Lotus::Mailer
337
+ # ...
338
+
339
+ def prepare
340
+ mail.attachments['invoice.pdf'] = '/path/to/invoice.pdf'
341
+ # or
342
+ mail.attachments['invoice.pdf'] = File.read('/path/to/invoice.pdf')
343
+ end
344
+ end
345
+ ```
346
+
347
+ ### Delivery Method
348
+
349
+ The global delivery method is defined through the __Lotus::Mailer__ configuration, as:
350
+
351
+ ```ruby
352
+ Lotus::Mailer.configuration do
353
+ delivery_method :smtp
354
+ end
355
+ ```
356
+
357
+ ```ruby
358
+ Lotus::Mailer.configuration do
359
+ delivery_method :smtp, address: "localhost", port: 1025
360
+ end
361
+ ```
362
+
363
+ Builtin options are:
364
+
365
+ * Exim (`:exim`)
366
+ * Sendmail (`:sendmail`)
367
+ * SMTP (`:smtp`, for local installations)
368
+ * SMTP Connection (`:smtp_connection`, via `Net::SMTP` - for remote installations)
369
+ * Test (`:test`, for testing purposes)
370
+
371
+ ### Custom Delivery Method
372
+
373
+ Developers can specify their own custom delivery policy:
374
+
375
+ ```ruby
376
+ require 'lotus/mailer'
377
+
378
+ class MandrillDeliveryMethod
379
+ def initialize(options)
380
+ @options = options
381
+ end
382
+
383
+ def deliver!(mail)
384
+ # ...
385
+ end
386
+ end
387
+
388
+ Lotus::Mailer.configure do
389
+ delivery_method MandrillDeliveryMethod,
390
+ username: ENV['MANDRILL_USERNAME'],
391
+ password: ENV['MANDRILL_API_KEY']
392
+ end.load!
393
+ ```
394
+
395
+ The class passed to `.delivery_method` must accept an optional set of options
396
+ with the constructor (`#initialize`) and respond to `#deliver!`.
397
+
398
+ ### Multipart Delivery
399
+
400
+ All the email are sent as multipart messages by default.
401
+ For a given mailer, the framework looks up for associated text (`.txt`) and `HTML` (`.html`) templates and render them.
402
+
403
+ ```ruby
404
+ InvoiceMailer.deliver # delivers both text and html templates
405
+ InvoiceMailer.deliver(format: :txt) # delivers only text template
406
+ ```
407
+
408
+ Please note that **they aren't both mandatory, but at least one of them MUST** be present.
409
+
410
+ ## Versioning
411
+
412
+ __Lotus::Mailer__ uses [Semantic Versioning 2.0.0](http://semver.org)
413
+
414
+ ## Copyright
415
+
416
+ Copyright © 2015 Luca Guidi – Released under MIT License
@@ -0,0 +1 @@
1
+ require 'lotus/mailer'
@@ -1,7 +1,316 @@
1
- require "lotus/mailer/version"
1
+ require 'lotus/utils/class_attribute'
2
+ require 'lotus/mailer/version'
3
+ require 'lotus/mailer/configuration'
4
+ require 'lotus/mailer/dsl'
5
+ require 'mail'
2
6
 
3
7
  module Lotus
8
+ # Lotus::Mailer
9
+ #
10
+ # @since 0.1.0
4
11
  module Mailer
5
- # Your code goes here...
12
+ # Base error for Lotus::Mailer
13
+ #
14
+ # @since 0.1.0
15
+ class Error < ::StandardError
16
+ end
17
+
18
+ # Missing delivery data error
19
+ #
20
+ # It's raised when a mailer doesn't specify <tt>from</tt> or <tt>to</tt>.
21
+ #
22
+ # @since 0.1.0
23
+ class MissingDeliveryDataError < Error
24
+ def initialize
25
+ super("Missing delivery data, please check 'from', or 'to'")
26
+ end
27
+ end
28
+
29
+ # Content types mapping
30
+ #
31
+ # @since 0.1.0
32
+ # @api private
33
+ CONTENT_TYPES = {
34
+ html: 'text/html',
35
+ txt: 'text/plain'
36
+ }.freeze
37
+
38
+ include Utils::ClassAttribute
39
+
40
+ # @since 0.1.0
41
+ # @api private
42
+ class_attribute :configuration
43
+ self.configuration = Configuration.new
44
+
45
+ # Configure the framework.
46
+ # It yields the given block in the context of the configuration
47
+ #
48
+ # @param blk [Proc] the configuration block
49
+ #
50
+ # @since 0.1.0
51
+ #
52
+ # @see Lotus::Mailer::Configuration
53
+ #
54
+ # @example
55
+ # require 'lotus/mailer'
56
+ #
57
+ # Lotus::Mailer.configure do
58
+ # root '/path/to/root'
59
+ # end
60
+ def self.configure(&blk)
61
+ configuration.instance_eval(&blk)
62
+ self
63
+ end
64
+
65
+ # Override Ruby's hook for modules.
66
+ # It includes basic Lotus::Mailer modules to the given Class.
67
+ # It sets a copy of the framework configuration
68
+ #
69
+ # @param base [Class] the target mailer
70
+ #
71
+ # @since 0.1.0
72
+ # @api private
73
+ #
74
+ # @see http://www.ruby-doc.org/core/Module.html#method-i-included
75
+ def self.included(base)
76
+ conf = self.configuration
77
+ conf.add_mailer(base)
78
+
79
+ base.class_eval do
80
+ extend Dsl
81
+ extend ClassMethods
82
+
83
+ include Utils::ClassAttribute
84
+ class_attribute :configuration
85
+
86
+ self.configuration = conf.duplicate
87
+ end
88
+
89
+ conf.copy!(base)
90
+ end
91
+
92
+ # Test deliveries
93
+ #
94
+ # This is a collection of delivered messages, used when <tt>delivery_method</tt>
95
+ # is set on <tt>:test</tt>
96
+ #
97
+ # @return [Array] a collection of delivered messages
98
+ #
99
+ # @since 0.1.0
100
+ #
101
+ # @see Lotus::Mailer::Configuration#delivery_mode
102
+ #
103
+ # @example
104
+ # require 'lotus/mailer'
105
+ #
106
+ # Lotus::Mailer.configure do
107
+ # delivery_method :test
108
+ # end.load!
109
+ #
110
+ # # In testing code
111
+ # Signup::Welcome.deliver
112
+ # Lotus::Mailer.deliveries.count # => 1
113
+ def self.deliveries
114
+ Mail::TestMailer.deliveries
115
+ end
116
+
117
+ # Load the framework
118
+ #
119
+ # @since 0.1.0
120
+ # @api private
121
+ def self.load!
122
+ Mail.eager_autoload!
123
+ configuration.load!
124
+ end
125
+
126
+ # @since 0.1.0
127
+ module ClassMethods
128
+ # Delivers a multipart email message.
129
+ #
130
+ # When a mailer defines a <tt>html</tt> and <tt>txt</tt> template, they are
131
+ # both delivered.
132
+ #
133
+ # In order to selectively deliver only one of the two templates, use
134
+ # <tt>Signup::Welcome.deliver(format: :txt)</tt>
135
+ #
136
+ # All the given locals, excepted the reserved ones (<tt>:format</tt> and
137
+ # <tt>charset</tt>), are avaliable as rendering context for the templates.
138
+ #
139
+ # @param locals [Hash] a set of objects that acts as context for the rendering
140
+ # @option :format [Symbol] specify format to deliver
141
+ # @option :charset [String] charset
142
+ #
143
+ # @since 0.1.0
144
+ #
145
+ # @see Lotus::Mailer::Configuration#default_charset
146
+ #
147
+ # @example
148
+ # require 'lotus/mailer'
149
+ #
150
+ # Lotus::Mailer.configure do
151
+ # delivery_method :smtp
152
+ # end.load!
153
+ #
154
+ # module Billing
155
+ # class Invoice
156
+ # include Lotus::Mailer
157
+ #
158
+ # from 'noreply@example.com'
159
+ # to :recipient
160
+ # subject :subject_line
161
+ #
162
+ # def prepare
163
+ # mail.attachments['invoice.pdf'] = File.read('/path/to/invoice.pdf')
164
+ # end
165
+ #
166
+ # private
167
+ #
168
+ # def recipient
169
+ # user.email
170
+ # end
171
+ #
172
+ # def subject_line
173
+ # "Invoice - #{ invoice.number }"
174
+ # end
175
+ # end
176
+ # end
177
+ #
178
+ # invoice = Invoice.new
179
+ # user = User.new(name: 'L', email: 'user@example.com')
180
+ #
181
+ # Billing::Invoice.deliver(invoice: invoice, user: user) # Deliver both text, HTML parts and the attachment
182
+ # Billing::Invoice.deliver(invoice: invoice, user: user, format: :txt) # Deliver only the text part and the attachment
183
+ # Billing::Invoice.deliver(invoice: invoice, user: user, format: :html) # Deliver only the text part and the attachment
184
+ # Billing::Invoice.deliver(invoice: invoice, user: user, charset: 'iso-8859') # Deliver both the parts with "iso-8859"
185
+ def deliver(locals = {})
186
+ new(locals).deliver
187
+ end
188
+ end
189
+
190
+ # Initialize a mailer
191
+ #
192
+ # @param locals [Hash] a set of objects that acts as context for the rendering
193
+ # @option :format [Symbol] specify format to deliver
194
+ # @option :charset [String] charset
195
+ #
196
+ # @since 0.1.0
197
+ def initialize(locals = {})
198
+ @locals = locals
199
+ @format = locals.fetch(:format, nil)
200
+ @charset = charset = locals.fetch(:charset, self.class.configuration.default_charset)
201
+ @mail = Mail.new.tap do |m|
202
+ m.from = __dsl(:from)
203
+ m.to = __dsl(:to)
204
+ m.subject = __dsl(:subject)
205
+
206
+ m.charset = charset
207
+ m.html_part = __part(:html)
208
+ m.text_part = __part(:txt)
209
+
210
+ m.delivery_method(*Lotus::Mailer.configuration.delivery_method)
211
+ end
212
+
213
+ prepare
214
+ end
215
+
216
+ # Render a single template with the specified format.
217
+ #
218
+ # @param format [Symbol] format
219
+ #
220
+ # @return [String] the output of the rendering process.
221
+ #
222
+ # @since 0.1.0
223
+ # @api private
224
+ def render(format)
225
+ self.class.templates(format).render(self, @locals)
226
+ end
227
+
228
+ # Delivers a multipart email, by looking at all the associated templates and render them.
229
+ #
230
+ # @since 0.1.0
231
+ # @api private
232
+ def deliver
233
+ mail.deliver
234
+ rescue ArgumentError
235
+ raise MissingDeliveryDataError
236
+ end
237
+
238
+ protected
239
+
240
+ # Prepare the email message when a new mailer is initialized.
241
+ #
242
+ # This is a hook that can be overwritten by mailers.
243
+ #
244
+ # @since 0.1.0
245
+ #
246
+ # @example
247
+ # require 'lotus/mailer'
248
+ #
249
+ # module Billing
250
+ # class Invoice
251
+ # include Lotus::Mailer
252
+ #
253
+ # subject 'Invoice'
254
+ # from 'noreply@example.com'
255
+ # to ''
256
+ #
257
+ # def prepare
258
+ # mail.attachments['invoice.pdf'] = File.read('/path/to/invoice.pdf')
259
+ # end
260
+ #
261
+ # private
262
+ #
263
+ # def recipient
264
+ # user.email
265
+ # end
266
+ # end
267
+ # end
268
+ #
269
+ # invoice = Invoice.new
270
+ # user = User.new(name: 'L', email: 'user@example.com')
271
+ def prepare
272
+ end
273
+
274
+ # @private
275
+ # @since 0.1.0
276
+ def method_missing(m)
277
+ @locals.fetch(m) { super }
278
+ end
279
+
280
+ # @since 0.1.0
281
+ attr_reader :mail
282
+
283
+ # @private
284
+ # @since 0.1.0
285
+ attr_reader :charset
286
+
287
+ private
288
+
289
+ # @private
290
+ # @since 0.1.0
291
+ def __dsl(method_name)
292
+ case result = self.class.__send__(method_name)
293
+ when Symbol
294
+ __send__(result)
295
+ else
296
+ result
297
+ end
298
+ end
299
+
300
+ # @private
301
+ # @since 0.1.0
302
+ def __part(format)
303
+ Mail::Part.new.tap do |part|
304
+ part.content_type = "#{ CONTENT_TYPES.fetch(format) }; charset=#{ charset }"
305
+ part.body = render(format)
306
+ end if __part?(format)
307
+ end
308
+
309
+ # @private
310
+ # @since 0.1.0
311
+ def __part?(format)
312
+ @format == format ||
313
+ (!@format && !self.class.templates(format).nil?)
314
+ end
6
315
  end
7
316
  end