hanami-mailer 1.3.2 → 3.0.0.rc1
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 +4 -4
- data/CHANGELOG.md +147 -43
- data/LICENSE +20 -0
- data/README.md +509 -304
- data/hanami-mailer.gemspec +33 -25
- data/lib/hanami/mailer/attachment.rb +128 -0
- data/lib/hanami/mailer/attachment_set.rb +38 -0
- data/lib/hanami/mailer/delivery/result.rb +82 -0
- data/lib/hanami/mailer/delivery/smtp.rb +168 -0
- data/lib/hanami/mailer/delivery/test.rb +53 -0
- data/lib/hanami/mailer/dsl/attachments.rb +108 -0
- data/lib/hanami/mailer/dsl/exposure.rb +69 -0
- data/lib/hanami/mailer/dsl/exposures.rb +111 -0
- data/lib/hanami/mailer/dsl/plucky_proc.rb +135 -0
- data/lib/hanami/mailer/errors.rb +67 -0
- data/lib/hanami/mailer/message.rb +101 -0
- data/lib/hanami/mailer/version.rb +5 -3
- data/lib/hanami/mailer/view_integration.rb +205 -0
- data/lib/hanami/mailer.rb +345 -271
- data/lib/hanami-mailer.rb +3 -1
- metadata +40 -83
- data/LICENSE.md +0 -22
- data/lib/hanami/mailer/configuration.rb +0 -308
- data/lib/hanami/mailer/dsl.rb +0 -626
- data/lib/hanami/mailer/rendering/template_name.rb +0 -53
- data/lib/hanami/mailer/rendering/templates_finder.rb +0 -133
- data/lib/hanami/mailer/template.rb +0 -40
data/README.md
CHANGED
|
@@ -1,421 +1,626 @@
|
|
|
1
|
-
|
|
1
|
+
<!--- This file is synced from hanakai-rb/repo-sync -->
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[actions]: https://github.com/hanami/hanami-mailer/actions
|
|
4
|
+
[chat]: https://discord.gg/naQApPAsZB
|
|
5
|
+
[forum]: https://discourse.hanamirb.org
|
|
6
|
+
[rubygem]: https://rubygems.org/gems/hanami-mailer
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
# hanami-mailer [][rubygem] [][actions]
|
|
6
9
|
|
|
7
|
-
[](https://circleci.com/gh/hanami/mailer/tree/master)
|
|
10
|
-
[](https://codecov.io/gh/hanami/mailer)
|
|
11
|
-
[](https://depfu.com/github/hanami/mailer?project=Bundler)
|
|
12
|
-
[](http://inch-ci.org/github/hanami/mailer)
|
|
10
|
+
[][forum]
|
|
11
|
+
[][chat]
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
Email delivery for Hanami apps and Ruby projects.
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
* Mailing List: http://hanamirb.org/mailing-list
|
|
18
|
-
* API Doc: http://rdoc.info/gems/hanami-mailer
|
|
19
|
-
* Bugs/Issues: https://github.com/hanami/mailer/issues
|
|
20
|
-
* Support: http://stackoverflow.com/questions/tagged/hanami
|
|
21
|
-
* Chat: http://chat.hanamirb.org
|
|
15
|
+
## Installation
|
|
22
16
|
|
|
23
|
-
|
|
17
|
+
Add the following to your app's Gemfile.
|
|
24
18
|
|
|
25
|
-
|
|
19
|
+
```ruby
|
|
20
|
+
gem "hanami-mailer"
|
|
21
|
+
gem "hanami-view" # For standard mailer view rendering
|
|
22
|
+
```
|
|
26
23
|
|
|
27
|
-
##
|
|
24
|
+
## Usage
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
Mailers can be used standalone in any Ruby project, or integrated into a Hanami app. The details below focus on standalone use; for mailers in a Hanami app, see the [Hanami mailers guide](https://hanakai.org/learn/hanami/mailers).
|
|
27
|
+
|
|
28
|
+
### Basic mailer
|
|
29
|
+
|
|
30
|
+
The simplest mailer uses static headers.
|
|
30
31
|
|
|
31
32
|
```ruby
|
|
32
|
-
|
|
33
|
+
class WelcomeMailer < Hanami::Mailer
|
|
34
|
+
config.paths = ["app/templates/mailers"]
|
|
35
|
+
|
|
36
|
+
from "noreply@example.com"
|
|
37
|
+
to "user@example.com"
|
|
38
|
+
subject "Welcome to our app!"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
mailer = WelcomeMailer.new
|
|
42
|
+
mailer.deliver
|
|
33
43
|
```
|
|
34
44
|
|
|
35
|
-
|
|
45
|
+
The HTML and text bodies come from these templates.
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
`app/templates/mailers/welcome_mailer.html.erb`:
|
|
38
48
|
|
|
39
|
-
|
|
49
|
+
```erb
|
|
50
|
+
<h1>Welcome to our app!</h1>
|
|
51
|
+
```
|
|
40
52
|
|
|
41
|
-
|
|
53
|
+
`app/templates/mailers/welcome_mailer.text.erb`:
|
|
42
54
|
|
|
43
|
-
|
|
55
|
+
```erb
|
|
56
|
+
Welcome to our app!
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
By default, a mailer's template is inferred from its mailer class name, e.g. `WelcomeMailer` renders
|
|
60
|
+
a `welcome_mailer` template. Set `config.template` to configure a name explicitly, e.g.
|
|
61
|
+
`config.template = "welcome"` would look for `welcome.html.erb` and `welcome.text.erb`.
|
|
62
|
+
|
|
63
|
+
### Rendering with Hanami View
|
|
64
|
+
|
|
65
|
+
Mailers render their HTML and text bodies using [Hanami View]. This is an optional dependency; add `hanami-view` to your bundle to enable it.
|
|
66
|
+
|
|
67
|
+
[Hanami View]: https://github.com/hanami/hanami-view
|
|
68
|
+
|
|
69
|
+
Hanami View settings are available directly on your mailer class, just like `config.paths` and `config.template` as used above. Each mailer builds its own view class behind the scenes to render your templates.
|
|
70
|
+
|
|
71
|
+
A mailer has two body formats, `:html` and `:text`, each rendered from its own template. The format is the first extension in the template file name: `welcome_mailer.html.erb` provides the `:html` body and `welcome_mailer.text.erb` the `:text` body.
|
|
72
|
+
|
|
73
|
+
Both HTML and text formats are rendered by default, producing a multipart email. Pass `format: :html` or `format: :text` to `#deliver` or `#prepare` to render a single format only.
|
|
74
|
+
|
|
75
|
+
Without Hanami View, mailers still send mail — you just supply the bodies yourself (see [Custom rendering without Hanami View](#custom-rendering-without-hanami-view)).
|
|
76
|
+
|
|
77
|
+
### Dynamic headers and exposures
|
|
78
|
+
|
|
79
|
+
Use header methods with blocks to compute headers dynamically based on input data.
|
|
80
|
+
|
|
81
|
+
Use `expose` to prepare values and make them available to your headers, attachments, and delivery options, and when Hanami View is available, your view templates for rendering.
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
class UserMailer < Hanami::Mailer
|
|
85
|
+
config.paths = ["app/templates/mailers"]
|
|
86
|
+
|
|
87
|
+
from "notifications@example.com"
|
|
88
|
+
to { |user:| user[:email] }
|
|
89
|
+
subject { |user:| "Hello, #{user[:name]}!" }
|
|
90
|
+
|
|
91
|
+
expose :user
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
mailer = UserMailer.new
|
|
95
|
+
mailer.deliver(user: {name: "Alice", email: "alice@example.com"})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The HTML and text bodies come from these templates.
|
|
99
|
+
|
|
100
|
+
`app/templates/mailers/user_mailer.html.erb`:
|
|
101
|
+
|
|
102
|
+
```erb
|
|
103
|
+
<h1>Hello, <%= user[:name] %>!</h1>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`app/templates/mailers/user_mailer.text.erb`:
|
|
107
|
+
|
|
108
|
+
```erb
|
|
109
|
+
Hello, <%= user[:name] %>!
|
|
110
|
+
```
|
|
44
111
|
|
|
45
|
-
|
|
112
|
+
`expose` comes in a few forms:
|
|
46
113
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
114
|
+
```ruby
|
|
115
|
+
# A value passed straight through from the input.
|
|
116
|
+
expose :user
|
|
117
|
+
|
|
118
|
+
# A value computed by a block.
|
|
119
|
+
expose(:greeting) { |customer:| "Hello, #{customer[:name]}!" }
|
|
120
|
+
|
|
121
|
+
# A default for optional input.
|
|
122
|
+
expose :greeting, default: "Hello"
|
|
51
123
|
|
|
52
|
-
|
|
124
|
+
# A private value: available to other exposures, headers, attachments, and delivery options, but
|
|
125
|
+
# never passed to the view for rendering.
|
|
126
|
+
private_expose :full_name do |first_name:, last_name:|
|
|
127
|
+
"#{first_name} #{last_name}"
|
|
128
|
+
end
|
|
129
|
+
```
|
|
53
130
|
|
|
54
|
-
|
|
131
|
+
### Accessing input and exposures in blocks
|
|
132
|
+
|
|
133
|
+
Mailer class methods receiving blocks (`expose`, as well as the header methods, `attachment`, and `delivery_option`) follow one rule for their parameters:
|
|
134
|
+
|
|
135
|
+
- **Keyword parameters** receive matching keys from the `deliver` input. Give them defaults to make those keys optional.
|
|
136
|
+
- **Positional parameters** receive exposure values, matched by name.
|
|
55
137
|
|
|
56
138
|
```ruby
|
|
57
|
-
|
|
139
|
+
class OrderMailer < Hanami::Mailer
|
|
140
|
+
from "orders@example.com"
|
|
141
|
+
|
|
142
|
+
# `customer:` comes from the same keyword arg given to `#deliver`
|
|
143
|
+
to { |customer:| customer[:email] }
|
|
144
|
+
|
|
145
|
+
# `customer:` comes from the input; `greeting` becomes an exposure
|
|
146
|
+
expose :greeting do |customer:|
|
|
147
|
+
"Hello, #{customer[:name]}!"
|
|
148
|
+
end
|
|
58
149
|
|
|
59
|
-
|
|
60
|
-
|
|
150
|
+
# `greeting` receives the value from the `:greeting` exposure above
|
|
151
|
+
subject { |greeting| greeting }
|
|
61
152
|
end
|
|
153
|
+
|
|
154
|
+
OrderMailer.new.deliver(customer: {name: "Alice", email: "alice@example.com"})
|
|
62
155
|
```
|
|
63
156
|
|
|
64
|
-
|
|
157
|
+
### Standard and custom email headers
|
|
158
|
+
|
|
159
|
+
Aside from the standard headers (which have their own dedicated convenience methods), you can add additional custom headers using `header`.
|
|
65
160
|
|
|
66
161
|
```ruby
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
bcc 'alice@example.com'
|
|
88
|
-
|
|
89
|
-
subject 'Welcome'
|
|
162
|
+
class CampaignMailer < Hanami::Mailer
|
|
163
|
+
# Standard headers, with dedicated class methods
|
|
164
|
+
from "sender@example.com"
|
|
165
|
+
to { |recipient:| recipient[:email] }
|
|
166
|
+
cc { |cc_list:| cc_list }
|
|
167
|
+
bcc "archive@example.com"
|
|
168
|
+
reply_to "support@example.com"
|
|
169
|
+
return_path "bounces@example.com"
|
|
170
|
+
subject { |subject_line:| subject_line }
|
|
171
|
+
|
|
172
|
+
# Custom headers for bulk emails
|
|
173
|
+
header :precedence, "bulk"
|
|
174
|
+
header(:list_unsubscribe) { |unsubscribe_url:| "<#{unsubscribe_url}>" }
|
|
175
|
+
|
|
176
|
+
# Custom headers for tracking (symbol names auto-convert to Title-Case)
|
|
177
|
+
header(:x_campaign_id) { |campaign:| campaign[:id] } # => "X-Campaign-Id"
|
|
178
|
+
header(:x_user_segment) { |user:| user[:segment] } # => "X-User-Segment"
|
|
179
|
+
|
|
180
|
+
# Use strings for exact casing control
|
|
181
|
+
header "X-Mailer-Version", "2.0"
|
|
90
182
|
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Overriding headers at delivery time
|
|
91
186
|
|
|
92
|
-
|
|
187
|
+
Override any header when calling `deliver`.
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
class NotificationMailer < Hanami::Mailer
|
|
191
|
+
from "notifications@example.com"
|
|
192
|
+
to "default@example.com"
|
|
193
|
+
subject "Default Subject"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
mailer = NotificationMailer.new
|
|
197
|
+
mailer.deliver(
|
|
198
|
+
headers: {
|
|
199
|
+
to: "priority-user@example.com",
|
|
200
|
+
subject: "URGENT: Important Update",
|
|
201
|
+
cc: "manager@example.com",
|
|
202
|
+
x_priority: "1"
|
|
203
|
+
}
|
|
204
|
+
)
|
|
93
205
|
```
|
|
94
206
|
|
|
95
|
-
###
|
|
207
|
+
### Static attachments
|
|
208
|
+
|
|
209
|
+
Load attachment files from configured paths.
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
class WelcomePackMailer < Hanami::Mailer
|
|
213
|
+
from "welcome@example.com"
|
|
214
|
+
to { |user:| user[:email] }
|
|
215
|
+
subject "Welcome Pack"
|
|
216
|
+
|
|
217
|
+
# Configure paths to search for attachment files
|
|
218
|
+
config.attachment_paths = ["public/attachments"]
|
|
219
|
+
|
|
220
|
+
# These files will be loaded from the configured paths
|
|
221
|
+
attachment "terms.pdf"
|
|
222
|
+
attachment "getting-started-guide.pdf"
|
|
223
|
+
attachment "company-logo.png"
|
|
224
|
+
end
|
|
225
|
+
```
|
|
96
226
|
|
|
97
|
-
|
|
227
|
+
You can configure multiple attachment paths in a base mailer class.
|
|
98
228
|
|
|
99
229
|
```ruby
|
|
100
|
-
|
|
230
|
+
class ApplicationMailer < Hanami::Mailer
|
|
231
|
+
config.attachment_paths = [
|
|
232
|
+
"app/attachments",
|
|
233
|
+
"app/assets/pdfs"
|
|
234
|
+
]
|
|
235
|
+
end
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
If a file cannot be found in any of the configured paths, a `MissingAttachmentError` is raised.
|
|
101
239
|
|
|
102
|
-
|
|
103
|
-
luca = User.new('Luca', 'jodosha', 'luca@jodosha.com')
|
|
240
|
+
### Dynamic attachments
|
|
104
241
|
|
|
105
|
-
|
|
106
|
-
include Hanami::Mailer
|
|
242
|
+
Return one or more attachments from an `attachment` block, whose parameters work [like every other block](#accessing-input-and-exposures-in-blocks). Use the `file` helper to create attachment objects.
|
|
107
243
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
244
|
+
```ruby
|
|
245
|
+
class ReportMailer < Hanami::Mailer
|
|
246
|
+
from "reports@example.com"
|
|
247
|
+
to { |user:| user[:email] }
|
|
248
|
+
subject "Monthly Report"
|
|
249
|
+
|
|
250
|
+
expose :user
|
|
251
|
+
|
|
252
|
+
# A single attachment from a block
|
|
253
|
+
attachment do |user:|
|
|
254
|
+
file(
|
|
255
|
+
"report-#{user[:id]}.pdf",
|
|
256
|
+
generate_report_pdf(user),
|
|
257
|
+
content_type: "application/pdf"
|
|
258
|
+
)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# You can have multiple attachment blocks
|
|
262
|
+
attachment do
|
|
263
|
+
file("summary.txt", "Here is your summary.")
|
|
264
|
+
end
|
|
111
265
|
|
|
112
266
|
private
|
|
113
267
|
|
|
114
|
-
def
|
|
115
|
-
|
|
268
|
+
def generate_report_pdf(user)
|
|
269
|
+
# ... generate PDF content
|
|
116
270
|
end
|
|
117
271
|
end
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
You can also return multiple attachments from a single block.
|
|
118
275
|
|
|
119
|
-
|
|
276
|
+
```ruby
|
|
277
|
+
attachment do |documents:|
|
|
278
|
+
documents.map do |doc|
|
|
279
|
+
file(doc[:name], doc[:content])
|
|
280
|
+
end
|
|
281
|
+
end
|
|
120
282
|
```
|
|
121
283
|
|
|
122
|
-
|
|
284
|
+
Or use a named instance method instead of a block.
|
|
123
285
|
|
|
124
|
-
```
|
|
125
|
-
|
|
286
|
+
```ruby
|
|
287
|
+
class InvoiceMailer < Hanami::Mailer
|
|
288
|
+
from "billing@example.com"
|
|
289
|
+
to { |customer:| customer[:email] }
|
|
290
|
+
subject "Invoice"
|
|
291
|
+
|
|
292
|
+
expose :invoice
|
|
293
|
+
|
|
294
|
+
attachment :invoice_pdf
|
|
295
|
+
|
|
296
|
+
private
|
|
297
|
+
|
|
298
|
+
def invoice_pdf(invoice:)
|
|
299
|
+
file(
|
|
300
|
+
"invoice-#{invoice[:number]}.pdf",
|
|
301
|
+
generate_pdf(invoice),
|
|
302
|
+
content_type: "application/pdf"
|
|
303
|
+
)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
126
306
|
```
|
|
127
307
|
|
|
128
|
-
###
|
|
308
|
+
### Inline attachments
|
|
129
309
|
|
|
130
|
-
|
|
310
|
+
Use inline attachments to embed images in your email HTML. The Content-ID is based on the filename, so you can reference it using `cid:filename`.
|
|
131
311
|
|
|
132
312
|
```ruby
|
|
133
|
-
|
|
313
|
+
class NewsletterMailer < Hanami::Mailer
|
|
314
|
+
from "news@example.com"
|
|
315
|
+
to { |subscriber:| subscriber[:email] }
|
|
316
|
+
subject "Weekly Newsletter"
|
|
134
317
|
|
|
135
|
-
|
|
136
|
-
include Hanami::Mailer
|
|
318
|
+
expose :subscriber
|
|
137
319
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
320
|
+
attachment do
|
|
321
|
+
file("header-image.png", header_image_data, inline: true)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
private
|
|
141
325
|
|
|
142
|
-
def
|
|
143
|
-
|
|
326
|
+
def header_image_data
|
|
327
|
+
File.read("app/assets/images/newsletter-header.png")
|
|
144
328
|
end
|
|
145
329
|
end
|
|
146
330
|
```
|
|
147
331
|
|
|
148
|
-
|
|
149
|
-
|
|
332
|
+
In your HTML template, reference inline attachments using `cid:`.
|
|
333
|
+
|
|
334
|
+
```html
|
|
335
|
+
<img src="cid:header-image.png" alt="Newsletter Header">
|
|
150
336
|
```
|
|
151
337
|
|
|
152
|
-
|
|
338
|
+
Static attachments can also be made inline.
|
|
153
339
|
|
|
154
|
-
|
|
340
|
+
```ruby
|
|
341
|
+
attachment "logo.png", inline: true
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Runtime attachments
|
|
345
|
+
|
|
346
|
+
Add attachments at delivery time without defining them at the class level. This is useful for one-off or conditional attachments, or pre-generated files passed from calling code.
|
|
155
347
|
|
|
156
348
|
```ruby
|
|
157
|
-
|
|
158
|
-
|
|
349
|
+
class OrderMailer < Hanami::Mailer
|
|
350
|
+
from "orders@example.com"
|
|
351
|
+
to { |customer:| customer[:email] }
|
|
352
|
+
subject "Order Confirmation"
|
|
159
353
|
|
|
160
|
-
#
|
|
161
|
-
|
|
162
|
-
|
|
354
|
+
# Class-level attachment always included
|
|
355
|
+
attachment "terms.pdf"
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
mailer = OrderMailer.new
|
|
359
|
+
|
|
360
|
+
# Add runtime attachments using hashes
|
|
361
|
+
mailer.deliver(
|
|
362
|
+
customer: {email: "customer@example.com"},
|
|
363
|
+
attachments: [
|
|
364
|
+
{filename: "invoice-123.pdf", content: pdf_bytes},
|
|
365
|
+
{filename: "receipt.txt", content: "Thank you!"}
|
|
366
|
+
]
|
|
367
|
+
)
|
|
368
|
+
# All three attachments are included: terms.pdf, invoice-123.pdf, and receipt.txt
|
|
369
|
+
|
|
370
|
+
# You can also use the Hanami::Mailer.file helper
|
|
371
|
+
mailer.deliver(
|
|
372
|
+
customer: {email: "customer@example.com"},
|
|
373
|
+
attachments: [
|
|
374
|
+
Hanami::Mailer.file("invoice-123.pdf", pdf_bytes, content_type: "application/pdf")
|
|
375
|
+
]
|
|
376
|
+
)
|
|
163
377
|
```
|
|
164
378
|
|
|
165
|
-
|
|
379
|
+
### Delivery options
|
|
380
|
+
|
|
381
|
+
Delivery options are delivery-method-specific parameters that customize how a message is sent. Their blocks receive arguments [like every other block](#accessing-input-and-exposures-in-blocks), and the resulting options are passed through to the delivery method on the `Message` object.
|
|
382
|
+
|
|
383
|
+
A third-party email service might use these for scheduled sending, priority levels, or tracking.
|
|
166
384
|
|
|
167
385
|
```ruby
|
|
168
|
-
class
|
|
169
|
-
|
|
386
|
+
class CampaignMailer < Hanami::Mailer
|
|
387
|
+
from "campaigns@example.com"
|
|
388
|
+
to { |recipient:| recipient[:email] }
|
|
389
|
+
subject "Special Offer"
|
|
390
|
+
|
|
391
|
+
# Static delivery option
|
|
392
|
+
delivery_option :track_opens, true
|
|
170
393
|
|
|
171
|
-
|
|
394
|
+
# Dynamic delivery option
|
|
395
|
+
delivery_option(:send_at) { |scheduled_time:| scheduled_time }
|
|
396
|
+
delivery_option(:tags) { |campaign:| ["campaign-#{campaign[:id]}"] }
|
|
172
397
|
end
|
|
173
398
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
399
|
+
mailer = CampaignMailer.new(delivery_method: postmark_delivery)
|
|
400
|
+
mailer.deliver(
|
|
401
|
+
recipient: {email: "user@example.com"},
|
|
402
|
+
campaign: {id: 42},
|
|
403
|
+
scheduled_time: Time.now + 3600
|
|
404
|
+
)
|
|
177
405
|
```
|
|
178
406
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
They are listed in order of **higher precedence**, for a given extension.
|
|
185
|
-
For instance, if [ERubis](http://www.kuwata-lab.com/erubis/) is loaded, it will be preferred over ERb to render `.erb` templates.
|
|
186
|
-
|
|
187
|
-
<table>
|
|
188
|
-
<tr>
|
|
189
|
-
<th>Engine</th>
|
|
190
|
-
<th>Extensions</th>
|
|
191
|
-
</tr>
|
|
192
|
-
<tr>
|
|
193
|
-
<td>Erubis</td>
|
|
194
|
-
<td>erb, rhtml, erubis</td>
|
|
195
|
-
</tr>
|
|
196
|
-
<tr>
|
|
197
|
-
<td>ERb</td>
|
|
198
|
-
<td>erb, rhtml</td>
|
|
199
|
-
</tr>
|
|
200
|
-
<tr>
|
|
201
|
-
<td>Redcarpet</td>
|
|
202
|
-
<td>markdown, mkd, md</td>
|
|
203
|
-
</tr>
|
|
204
|
-
<tr>
|
|
205
|
-
<td>RDiscount</td>
|
|
206
|
-
<td>markdown, mkd, md</td>
|
|
207
|
-
</tr>
|
|
208
|
-
<tr>
|
|
209
|
-
<td>Kramdown</td>
|
|
210
|
-
<td>markdown, mkd, md</td>
|
|
211
|
-
</tr>
|
|
212
|
-
<tr>
|
|
213
|
-
<td>Maruku</td>
|
|
214
|
-
<td>markdown, mkd, md</td>
|
|
215
|
-
</tr>
|
|
216
|
-
<tr>
|
|
217
|
-
<td>BlueCloth</td>
|
|
218
|
-
<td>markdown, mkd, md</td>
|
|
219
|
-
</tr>
|
|
220
|
-
<tr>
|
|
221
|
-
<td>Asciidoctor</td>
|
|
222
|
-
<td>ad, adoc, asciidoc</td>
|
|
223
|
-
</tr>
|
|
224
|
-
<tr>
|
|
225
|
-
<td>Builder</td>
|
|
226
|
-
<td>builder</td>
|
|
227
|
-
</tr>
|
|
228
|
-
<tr>
|
|
229
|
-
<td>CSV</td>
|
|
230
|
-
<td>rcsv</td>
|
|
231
|
-
</tr>
|
|
232
|
-
<tr>
|
|
233
|
-
<td>CoffeeScript</td>
|
|
234
|
-
<td>coffee</td>
|
|
235
|
-
</tr>
|
|
236
|
-
<tr>
|
|
237
|
-
<td>WikiCloth</td>
|
|
238
|
-
<td>wiki, mediawiki, mw</td>
|
|
239
|
-
</tr>
|
|
240
|
-
<tr>
|
|
241
|
-
<td>Creole</td>
|
|
242
|
-
<td>wiki, creole</td>
|
|
243
|
-
</tr>
|
|
244
|
-
<tr>
|
|
245
|
-
<td>Etanni</td>
|
|
246
|
-
<td>etn, etanni</td>
|
|
247
|
-
</tr>
|
|
248
|
-
<tr>
|
|
249
|
-
<td>Haml</td>
|
|
250
|
-
<td>haml</td>
|
|
251
|
-
</tr>
|
|
252
|
-
<tr>
|
|
253
|
-
<td>Less</td>
|
|
254
|
-
<td>less</td>
|
|
255
|
-
</tr>
|
|
256
|
-
<tr>
|
|
257
|
-
<td>Liquid</td>
|
|
258
|
-
<td>liquid</td>
|
|
259
|
-
</tr>
|
|
260
|
-
<tr>
|
|
261
|
-
<td>Markaby</td>
|
|
262
|
-
<td>mab</td>
|
|
263
|
-
</tr>
|
|
264
|
-
<tr>
|
|
265
|
-
<td>Nokogiri</td>
|
|
266
|
-
<td>nokogiri</td>
|
|
267
|
-
</tr>
|
|
268
|
-
<tr>
|
|
269
|
-
<td>Plain</td>
|
|
270
|
-
<td>html</td>
|
|
271
|
-
</tr>
|
|
272
|
-
<tr>
|
|
273
|
-
<td>RDoc</td>
|
|
274
|
-
<td>rdoc</td>
|
|
275
|
-
</tr>
|
|
276
|
-
<tr>
|
|
277
|
-
<td>Radius</td>
|
|
278
|
-
<td>radius</td>
|
|
279
|
-
</tr>
|
|
280
|
-
<tr>
|
|
281
|
-
<td>RedCloth</td>
|
|
282
|
-
<td>textile</td>
|
|
283
|
-
</tr>
|
|
284
|
-
<tr>
|
|
285
|
-
<td>Sass</td>
|
|
286
|
-
<td>sass</td>
|
|
287
|
-
</tr>
|
|
288
|
-
<tr>
|
|
289
|
-
<td>Scss</td>
|
|
290
|
-
<td>scss</td>
|
|
291
|
-
</tr>
|
|
292
|
-
<tr>
|
|
293
|
-
<td>Slim</td>
|
|
294
|
-
<td>slim</td>
|
|
295
|
-
</tr>
|
|
296
|
-
<tr>
|
|
297
|
-
<td>String</td>
|
|
298
|
-
<td>str</td>
|
|
299
|
-
</tr>
|
|
300
|
-
<tr>
|
|
301
|
-
<td>Yajl</td>
|
|
302
|
-
<td>yajl</td>
|
|
303
|
-
</tr>
|
|
304
|
-
</table>
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
### Configuration
|
|
308
|
-
|
|
309
|
-
__Hanami::Mailer__ can be configured with a DSL that determines its behavior.
|
|
310
|
-
It supports a few options:
|
|
407
|
+
The delivery method receives these options via `message.delivery_options` and can act on them however it sees fit.
|
|
408
|
+
|
|
409
|
+
### Inheritance
|
|
410
|
+
|
|
411
|
+
Mailers support inheritance, which is useful for sharing common configuration.
|
|
311
412
|
|
|
312
413
|
```ruby
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
414
|
+
class ApplicationMailer < Hanami::Mailer
|
|
415
|
+
from "noreply@example.com"
|
|
416
|
+
config.attachment_paths = ["app/attachments"]
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
class WelcomeMailer < ApplicationMailer
|
|
420
|
+
to { |user:| user[:email] }
|
|
421
|
+
subject "Welcome!"
|
|
422
|
+
|
|
423
|
+
expose :user
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
class NewsletterMailer < ApplicationMailer
|
|
427
|
+
to { |subscriber:| subscriber[:email] }
|
|
428
|
+
subject "Weekly Newsletter"
|
|
429
|
+
|
|
430
|
+
expose :subscriber
|
|
431
|
+
|
|
432
|
+
attachment "terms.pdf"
|
|
330
433
|
end
|
|
331
434
|
```
|
|
332
435
|
|
|
333
|
-
|
|
436
|
+
Headers, exposures, attachments, and delivery options are all inherited and can be extended in subclasses.
|
|
437
|
+
|
|
438
|
+
### Delivery methods
|
|
439
|
+
|
|
440
|
+
`Hanami::Mailer` expects the delivery method to be provided as a `delivery_method:` dependency at initialization.
|
|
334
441
|
|
|
335
|
-
|
|
442
|
+
Every delivery method must respond to `#call(message)` and return a `Delivery::Result`.
|
|
443
|
+
|
|
444
|
+
#### Test delivery (default)
|
|
445
|
+
|
|
446
|
+
The test delivery method stores results in memory. It's the default when no delivery method is specified.
|
|
447
|
+
|
|
448
|
+
```ruby
|
|
449
|
+
mailer = WelcomeMailer.new
|
|
450
|
+
result = mailer.deliver(user: user)
|
|
451
|
+
|
|
452
|
+
result.success? # => true
|
|
453
|
+
result.message # => the Hanami::Mailer::Message that was delivered
|
|
454
|
+
|
|
455
|
+
# Inspect all deliveries via the mailer's delivery method instance
|
|
456
|
+
mailer.delivery_method.deliveries # => [result, ...]
|
|
457
|
+
mailer.delivery_method.deliveries.size # => 1
|
|
458
|
+
mailer.delivery_method.clear # reset between tests
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
#### SMTP delivery
|
|
462
|
+
|
|
463
|
+
For production use, provide an SMTP delivery method.
|
|
336
464
|
|
|
337
465
|
```ruby
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
466
|
+
smtp = Hanami::Mailer::Delivery::SMTP.new(
|
|
467
|
+
address: "smtp.example.com",
|
|
468
|
+
port: 587,
|
|
469
|
+
user_name: ENV["SMTP_USERNAME"],
|
|
470
|
+
password: ENV["SMTP_PASSWORD"],
|
|
471
|
+
authentication: :plain,
|
|
472
|
+
enable_starttls_auto: true
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
mailer = WelcomeMailer.new(delivery_method: smtp)
|
|
476
|
+
result = mailer.deliver(user: user)
|
|
477
|
+
|
|
478
|
+
result.success? # => true if SMTP accepted the message
|
|
479
|
+
result.response # => the Mail::Message object
|
|
480
|
+
result.error # => nil on success, the exception on failure
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### Custom delivery methods
|
|
484
|
+
|
|
485
|
+
Implement your own delivery method by creating a class that responds to `#call(message)` and returns a `Delivery::Result`.
|
|
486
|
+
|
|
487
|
+
```ruby
|
|
488
|
+
class MyApiDelivery
|
|
489
|
+
def call(message)
|
|
490
|
+
response = SomeEmailApi.send(
|
|
491
|
+
from: message.from,
|
|
492
|
+
to: message.to,
|
|
493
|
+
subject: message.subject,
|
|
494
|
+
html: message.html_body,
|
|
495
|
+
options: message.delivery_options
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
Hanami::Mailer::Delivery::Result.new(
|
|
499
|
+
message: message,
|
|
500
|
+
response: response,
|
|
501
|
+
success: response.ok?
|
|
502
|
+
)
|
|
503
|
+
rescue => error
|
|
504
|
+
Hanami::Mailer::Delivery::Result.new(
|
|
505
|
+
message: message,
|
|
506
|
+
success: false,
|
|
507
|
+
error: error
|
|
508
|
+
)
|
|
346
509
|
end
|
|
347
510
|
end
|
|
348
511
|
```
|
|
349
512
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
The global delivery method is defined through the __Hanami::Mailer__ configuration, as:
|
|
513
|
+
Third-party delivery methods can subclass `Delivery::Result` to expose service-specific attributes.
|
|
353
514
|
|
|
354
515
|
```ruby
|
|
355
|
-
Hanami::Mailer
|
|
356
|
-
|
|
516
|
+
class Postmark::Result < Hanami::Mailer::Delivery::Result
|
|
517
|
+
attr_reader :message_id, :submitted_at
|
|
518
|
+
|
|
519
|
+
def initialize(message_id:, submitted_at: nil, **)
|
|
520
|
+
super(**)
|
|
521
|
+
@message_id = message_id
|
|
522
|
+
@submitted_at = submitted_at
|
|
523
|
+
end
|
|
357
524
|
end
|
|
358
525
|
```
|
|
359
526
|
|
|
527
|
+
### Preparing messages without delivering
|
|
528
|
+
|
|
529
|
+
Use `prepare` to build a `Message` without sending it. This is useful for inspection, queuing, or delivering later through a different method.
|
|
530
|
+
|
|
360
531
|
```ruby
|
|
361
|
-
Hanami::Mailer
|
|
362
|
-
|
|
532
|
+
class WelcomeMailer < Hanami::Mailer
|
|
533
|
+
from "welcome@example.com"
|
|
534
|
+
to { |user:| user[:email] }
|
|
535
|
+
subject { |user:| "Welcome, #{user[:name]}!" }
|
|
536
|
+
|
|
537
|
+
expose :user
|
|
363
538
|
end
|
|
539
|
+
|
|
540
|
+
mailer = WelcomeMailer.new
|
|
541
|
+
message = mailer.prepare(user: {name: "Alice", email: "alice@example.com"})
|
|
542
|
+
|
|
543
|
+
message.from # => ["welcome@example.com"]
|
|
544
|
+
message.to # => ["alice@example.com"]
|
|
545
|
+
message.subject # => "Welcome, Alice!"
|
|
546
|
+
message.html_body # => rendered HTML (if templates exist)
|
|
547
|
+
message.text_body # => rendered text (if templates exist)
|
|
548
|
+
|
|
549
|
+
# Deliver the prepared message directly through a delivery method
|
|
550
|
+
smtp = Hanami::Mailer::Delivery::SMTP.new(address: "smtp.example.com")
|
|
551
|
+
smtp.call(message)
|
|
364
552
|
```
|
|
365
553
|
|
|
366
|
-
|
|
554
|
+
### Previewing messages
|
|
367
555
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
556
|
+
Delivery methods expose a `preview` hook that returns a prepared message without sending it. The default (and test) delivery method returns the message unchanged; a third-party delivery method can override `preview` to apply service-specific logic, such as resolving a template through a remote API.
|
|
557
|
+
|
|
558
|
+
```ruby
|
|
559
|
+
mailer = WelcomeMailer.new
|
|
560
|
+
message = mailer.prepare(user: {name: "Alice", email: "alice@example.com"})
|
|
561
|
+
|
|
562
|
+
preview = mailer.delivery_method.preview(message)
|
|
563
|
+
```
|
|
373
564
|
|
|
374
|
-
###
|
|
565
|
+
### Using a custom view class
|
|
375
566
|
|
|
376
|
-
|
|
567
|
+
By default mailers render using a subclass of `Hanami::View`. To inherit from another view class, configure it via `config.view_class`. The mailer's view will inherit from this and use its configuration — context, parts, scopes, paths, helpers, and so on. This is how mailers in a Hanami app pick up the app's standard view behaviour.
|
|
377
568
|
|
|
378
569
|
```ruby
|
|
379
|
-
|
|
570
|
+
class ReportMailer < Hanami::Mailer
|
|
571
|
+
config.view_class = MyApp::View
|
|
380
572
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
end
|
|
573
|
+
from "reports@example.com"
|
|
574
|
+
to { |user:| user[:email] }
|
|
575
|
+
subject "Monthly Report"
|
|
385
576
|
|
|
386
|
-
|
|
387
|
-
# ...
|
|
388
|
-
end
|
|
577
|
+
expose :user
|
|
389
578
|
end
|
|
390
|
-
|
|
391
|
-
Hanami::Mailer.configure do
|
|
392
|
-
delivery_method MandrillDeliveryMethod,
|
|
393
|
-
username: ENV['MANDRILL_USERNAME'],
|
|
394
|
-
password: ENV['MANDRILL_API_KEY']
|
|
395
|
-
end.load!
|
|
396
579
|
```
|
|
397
580
|
|
|
398
|
-
|
|
399
|
-
with the constructor (`#initialize`) and respond to `#deliver!`.
|
|
581
|
+
Because template paths are inherited from the configured view class, you typically don't need to set `config.paths` or yourself in this case.
|
|
400
582
|
|
|
401
|
-
###
|
|
583
|
+
### Custom rendering without Hanami View
|
|
402
584
|
|
|
403
|
-
|
|
404
|
-
For a given mailer, the framework looks up for associated text (`.txt`) and `HTML` (`.html`) templates and render them.
|
|
585
|
+
If you don't use Hanami View, override the internal rendering methods. These are also a hook for integrations with other rendering systems.
|
|
405
586
|
|
|
406
587
|
```ruby
|
|
407
|
-
|
|
408
|
-
|
|
588
|
+
class CustomMailer < Hanami::Mailer
|
|
589
|
+
from "custom@example.com"
|
|
590
|
+
to { |user:| user[:email] }
|
|
591
|
+
subject "Custom Email"
|
|
592
|
+
|
|
593
|
+
expose :user
|
|
594
|
+
|
|
595
|
+
private
|
|
596
|
+
|
|
597
|
+
def render_view(format, input)
|
|
598
|
+
user = input[:user]
|
|
599
|
+
|
|
600
|
+
case format
|
|
601
|
+
when :html
|
|
602
|
+
<<~HTML
|
|
603
|
+
<html>
|
|
604
|
+
<body>
|
|
605
|
+
<h1>Hello, #{user[:name]}!</h1>
|
|
606
|
+
</body>
|
|
607
|
+
</html>
|
|
608
|
+
HTML
|
|
609
|
+
when :text
|
|
610
|
+
"Hello, #{user[:name]}!"
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
end
|
|
409
614
|
```
|
|
410
615
|
|
|
411
|
-
|
|
616
|
+
If Hanami View is installed but you don't want mailers building a view from it automatically, turn off auto view-building with `config.integrate_view = false`. Your overridden `render_view` then takes full responsibility for rendering.
|
|
412
617
|
|
|
413
|
-
##
|
|
618
|
+
## Links
|
|
414
619
|
|
|
415
|
-
|
|
620
|
+
- [User documentation](https://hanamirb.org)
|
|
621
|
+
- [API documentation](http://rubydoc.info/gems/hanami-mailer)
|
|
416
622
|
|
|
417
|
-
## Copyright
|
|
418
623
|
|
|
419
|
-
|
|
624
|
+
## License
|
|
420
625
|
|
|
421
|
-
|
|
626
|
+
See `LICENSE` file.
|