courrier 0.6.0 → 0.8.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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +159 -26
- data/app/controllers/courrier/previews/cleanups_controller.rb +13 -0
- data/app/controllers/courrier/previews_controller.rb +23 -0
- data/app/views/courrier/previews/index.html.erb +171 -0
- data/config/routes.rb +8 -0
- data/courrier.gemspec +3 -3
- data/lib/courrier/configuration/{preview.rb → inbox.rb} +3 -3
- data/lib/courrier/configuration.rb +32 -6
- data/lib/courrier/email/options.rb +15 -0
- data/lib/courrier/email/provider.rb +22 -17
- data/lib/courrier/email/providers/base.rb +2 -1
- data/lib/courrier/email/providers/{preview → inbox}/default.html.erb +13 -6
- data/lib/courrier/email/providers/inbox.rb +83 -0
- data/lib/courrier/email/providers/logger.rb +22 -3
- data/lib/courrier/email/providers/loops.rb +1 -8
- data/lib/courrier/email/providers/resend.rb +32 -0
- data/lib/courrier/email/providers/userlist.rb +13 -13
- data/lib/courrier/email.rb +54 -11
- data/lib/courrier/engine.rb +7 -0
- data/lib/courrier/errors.rb +3 -1
- data/lib/courrier/jobs/email_delivery_job.rb +23 -0
- data/lib/courrier/subscriber/base.rb +51 -0
- data/lib/courrier/subscriber/beehiiv.rb +45 -0
- data/lib/courrier/subscriber/buttondown.rb +28 -0
- data/lib/courrier/subscriber/kit.rb +36 -0
- data/lib/courrier/subscriber/loops.rb +32 -0
- data/lib/courrier/subscriber/mailchimp.rb +39 -0
- data/lib/courrier/subscriber/mailerlite.rb +28 -0
- data/lib/courrier/subscriber/result.rb +41 -0
- data/lib/courrier/subscriber.rb +34 -0
- data/lib/courrier/tasks/courrier.rake +2 -2
- data/lib/courrier/version.rb +1 -1
- data/lib/courrier.rb +2 -0
- data/lib/generators/courrier/email_generator.rb +24 -1
- data/lib/generators/courrier/templates/email/password_reset.rb.tt +29 -0
- data/lib/generators/courrier/templates/email/welcome.rb.tt +43 -0
- data/lib/generators/courrier/templates/initializer.rb.tt +10 -5
- metadata +26 -8
- data/lib/courrier/email/providers/preview.rb +0 -51
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e43b1cb6985e742ca84f68be14ddf25e72bf8f64a26ce7be151272983996ac45
|
|
4
|
+
data.tar.gz: ac7b82db6e5fd9975a76de6a630a4ac8e3f956595e175fc6d6c1ef18d9d4949c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4f5ad1ecb424cca190a86bd10acbf69c906e3f7218724a3cdc29e8ce3e10c4bea4c1c43ba66d6de24af17f53c9db416c659e7b84aadd92fcece63b57ee5a13ce
|
|
7
|
+
data.tar.gz: 9050571fcc8050ecb6a92a321e466f92951c383d3ec1376809f70913440f38537f8245b37f2955bca3c263874d9de178818789dae89cc2a7bfe809c3d9d39eb9
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
# Courrier
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
API-powered email delivery and newsletter subscription management for Ruby apps
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
```ruby
|
|
8
8
|
# Quick example
|
|
9
|
+
class OrderEmail < Courrier::Email
|
|
10
|
+
def subject = "Here is your order!"
|
|
11
|
+
|
|
12
|
+
def text = "Thanks for ordering"
|
|
13
|
+
|
|
14
|
+
def html = "<p>Thanks for ordering</p>"
|
|
15
|
+
end
|
|
16
|
+
|
|
9
17
|
OrderEmail.deliver to: "recipient@railsdesigner.com"
|
|
18
|
+
|
|
19
|
+
# Manage newsletter subscriptions
|
|
20
|
+
Courrier::Subscriber.create "subscriber@example.com"
|
|
10
21
|
```
|
|
11
22
|
|
|
12
23
|
<a href="https://railsdesigner.com/" target="_blank">
|
|
@@ -72,8 +83,11 @@ Courrier uses a configuration system with three levels (from lowest to highest p
|
|
|
72
83
|
1. **Global configuration**
|
|
73
84
|
```ruby
|
|
74
85
|
Courrier.configure do |config|
|
|
75
|
-
config.
|
|
76
|
-
|
|
86
|
+
config.email = {
|
|
87
|
+
provider: "postmark",
|
|
88
|
+
api_key: "xyz"
|
|
89
|
+
}
|
|
90
|
+
|
|
77
91
|
config.from = "devs@railsdesigner.com"
|
|
78
92
|
config.default_url_options = { host: "railsdesigner.com" }
|
|
79
93
|
|
|
@@ -88,7 +102,7 @@ end
|
|
|
88
102
|
class OrderEmail < Courrier::Email
|
|
89
103
|
configure from: "orders@railsdesigner.com",
|
|
90
104
|
cc: "records@railsdesigner.com",
|
|
91
|
-
provider: "mailgun"
|
|
105
|
+
provider: "mailgun"
|
|
92
106
|
end
|
|
93
107
|
```
|
|
94
108
|
|
|
@@ -104,11 +118,12 @@ OrderEmail.deliver to: "recipient@railsdesigner.com",\
|
|
|
104
118
|
Provider and API key settings can be overridden using environment variables (`COURRIER_PROVIDER` and `COURRIER_API_KEY`) for both global configuration and email class defaults.
|
|
105
119
|
|
|
106
120
|
|
|
107
|
-
## Custom
|
|
121
|
+
## Custom attributes
|
|
108
122
|
|
|
109
123
|
Besides the standard email attributes (`from`, `to`, `reply_to`, etc.), you can pass any additional attributes that will be available in your email templates:
|
|
110
124
|
```ruby
|
|
111
|
-
OrderEmail.deliver to: "recipient@railsdesigner.com"
|
|
125
|
+
OrderEmail.deliver to: "recipient@railsdesigner.com",\
|
|
126
|
+
download_url: downloads_path(token: "token")
|
|
112
127
|
```
|
|
113
128
|
|
|
114
129
|
These custom attributes are accessible directly in your email class:
|
|
@@ -121,12 +136,12 @@ end
|
|
|
121
136
|
```
|
|
122
137
|
|
|
123
138
|
|
|
124
|
-
## Result
|
|
139
|
+
## Result object
|
|
125
140
|
|
|
126
141
|
When sending an email through Courrier, a `Result` object is returned that provides information about the delivery attempt. This object offers a simple interface to check the status and access response data.
|
|
127
142
|
|
|
128
143
|
|
|
129
|
-
### Available
|
|
144
|
+
### Available methods
|
|
130
145
|
|
|
131
146
|
| Method | Return Type | Description |
|
|
132
147
|
|:-------|:-----------|:------------|
|
|
@@ -156,32 +171,48 @@ Courrier supports these transactional email providers:
|
|
|
156
171
|
|
|
157
172
|
- [Loops](https://loops.so)
|
|
158
173
|
- [Mailgun](https://mailgun.com)
|
|
159
|
-
- [Mailjet](https://mailjet.com)
|
|
160
174
|
- [MailPace](https://mailpace.com)
|
|
161
175
|
- [Postmark](https://postmarkapp.com)
|
|
162
|
-
- [
|
|
163
|
-
- [SparkPost](https://sparkpost.com)
|
|
176
|
+
- [Resend](https://resend.com)
|
|
164
177
|
- [Userlist](https://userlist.com)
|
|
165
178
|
|
|
166
|
-
⚠️ Some providers still need manual verification of their implementation. If you're using one of these providers, please help verify the implementation by sharing your experience in [this GitHub issue](https://github.com/Rails-Designer/courrier/issues/4). 🙏
|
|
167
|
-
|
|
168
179
|
|
|
169
180
|
## More Features
|
|
170
181
|
|
|
171
|
-
Additional functionality to help with development and
|
|
182
|
+
Additional functionality to help with development and testing:
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
### Background jobs (Rails only)
|
|
186
|
+
|
|
187
|
+
Use `deliver_later` to enqueue delivering using Rails' ActiveJob. You can set
|
|
188
|
+
various ActiveJob-supported options in the email class, like so: `enqueue queue: "emails", wait: 5.minutes`.
|
|
189
|
+
|
|
190
|
+
- `queue`, enqueue the email on the specified queue;
|
|
191
|
+
- `wait`, enqueue the email to be delivered with a delay;
|
|
192
|
+
- `wait_until`, enqueue the email to be delivered at (after) a specific date/time;
|
|
193
|
+
- `priority`, enqueues the email with the specified priority.
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
### Inbox (Rails only)
|
|
172
197
|
|
|
198
|
+
You can preview your emails in the inbox:
|
|
199
|
+
```ruby
|
|
200
|
+
config.provider = "inbox"
|
|
173
201
|
|
|
174
|
-
|
|
202
|
+
# And add to your routes:
|
|
203
|
+
mount Courrier::Engine => "/courrier"
|
|
204
|
+
```
|
|
175
205
|
|
|
176
|
-
|
|
206
|
+
If you want to automatically open every email in your default browser:
|
|
177
207
|
```ruby
|
|
178
|
-
config.provider = "
|
|
208
|
+
config.provider = "inbox"
|
|
209
|
+
config.inbox.auto_open = true
|
|
179
210
|
```
|
|
180
211
|
|
|
181
|
-
|
|
212
|
+
Emails are automatically cleared with `bin/rails tmp:clear`, or manually with `bin/rails courrier:clear`.
|
|
182
213
|
|
|
183
214
|
|
|
184
|
-
### Layout
|
|
215
|
+
### Layout support
|
|
185
216
|
|
|
186
217
|
Wrap your email content using layouts:
|
|
187
218
|
```ruby
|
|
@@ -224,7 +255,7 @@ end
|
|
|
224
255
|
```
|
|
225
256
|
|
|
226
257
|
|
|
227
|
-
### Auto-generate
|
|
258
|
+
### Auto-generate text from HTML
|
|
228
259
|
|
|
229
260
|
Automatically generate plain text versions from your HTML emails:
|
|
230
261
|
```ruby
|
|
@@ -232,7 +263,7 @@ config.auto_generate_text = true # Defaults to false
|
|
|
232
263
|
```
|
|
233
264
|
|
|
234
265
|
|
|
235
|
-
### Email
|
|
266
|
+
### Email address helper
|
|
236
267
|
|
|
237
268
|
Compose email addresses with display names:
|
|
238
269
|
```ruby
|
|
@@ -259,16 +290,17 @@ end
|
|
|
259
290
|
```
|
|
260
291
|
|
|
261
292
|
|
|
262
|
-
### Logger
|
|
293
|
+
### Logger provider
|
|
263
294
|
|
|
264
295
|
Use Ruby's built-in Logger for development and testing:
|
|
265
296
|
|
|
266
297
|
```ruby
|
|
267
|
-
config.provider = "logger" #
|
|
268
|
-
config.logger = custom_logger #
|
|
298
|
+
config.provider = "logger" # outputs emails to STDOUT
|
|
299
|
+
config.logger = custom_logger # optional: defaults to ::Logger.new($stdout)
|
|
269
300
|
```
|
|
270
301
|
|
|
271
|
-
|
|
302
|
+
|
|
303
|
+
### Custom providers
|
|
272
304
|
|
|
273
305
|
Create your own provider by inheriting from `Courrier::Email::Providers::Base`:
|
|
274
306
|
```ruby
|
|
@@ -289,6 +321,107 @@ config.provider = "CustomProvider"
|
|
|
289
321
|
Check the [existing providers](https://github.com/Rails-Designer/courrier/tree/main/lib/courrier/email/providers) for implementation examples.
|
|
290
322
|
|
|
291
323
|
|
|
324
|
+
## Newsletter subscriptions
|
|
325
|
+
|
|
326
|
+
Manage subscribers across popular email marketing platforms:
|
|
327
|
+
```ruby
|
|
328
|
+
Courrier.configure do |config|
|
|
329
|
+
config.subscriber = {
|
|
330
|
+
provider: "buttondown",
|
|
331
|
+
api_key: "your_api_key"
|
|
332
|
+
}
|
|
333
|
+
end
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
```ruby
|
|
337
|
+
# Add a subscriber
|
|
338
|
+
subscriber = Courrier::Subscriber.create "subscriber@example.com"
|
|
339
|
+
|
|
340
|
+
# Remove a subscriber
|
|
341
|
+
subscriber = Courrier::Subscriber.destroy "subscriber@example.com"
|
|
342
|
+
|
|
343
|
+
if subscriber.success?
|
|
344
|
+
puts "Subscriber added!"
|
|
345
|
+
else
|
|
346
|
+
puts "Error: #{subscriber.error}"
|
|
347
|
+
end
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
### Supported providers
|
|
352
|
+
|
|
353
|
+
- [Beehiiv](https://www.beehiiv.com/) - requires `publication_id`
|
|
354
|
+
- [Buttondown](https://buttondown.com)
|
|
355
|
+
- [Kit](https://kit.com/) (formerly ConvertKit) - requires `form_id`
|
|
356
|
+
- [Loops](https://loops.so/)
|
|
357
|
+
- [Mailchimp](https://mailchimp.com/) - requires `dc` and `list_id`
|
|
358
|
+
- [MailerLite](https://www.mailerlite.com/)
|
|
359
|
+
|
|
360
|
+
Provider-specific configuration:
|
|
361
|
+
```ruby
|
|
362
|
+
config.subscriber = {
|
|
363
|
+
provider: "mailchimp",
|
|
364
|
+
api_key: "your_api_key",
|
|
365
|
+
dc: "us19",
|
|
366
|
+
list_id: "abc123"
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Custom providers
|
|
371
|
+
|
|
372
|
+
Create custom providers by inheriting from `Courrier::Subscriber::Base`:
|
|
373
|
+
```ruby
|
|
374
|
+
class CustomSubscriberProvider < Courrier::Subscriber::Base
|
|
375
|
+
ENDPOINT_URL = "https://api.example.com/subscribers"
|
|
376
|
+
|
|
377
|
+
def create(email)
|
|
378
|
+
request(:post, ENDPOINT_URL, {"email" => email})
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def destroy(email)
|
|
382
|
+
request(:delete, "#{ENDPOINT_URL}/#{email}")
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
private
|
|
386
|
+
|
|
387
|
+
def headers
|
|
388
|
+
{
|
|
389
|
+
"Authorization" => "Bearer #{@api_key}",
|
|
390
|
+
"Content-Type" => "application/json"
|
|
391
|
+
}
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Then configure it:
|
|
397
|
+
```ruby
|
|
398
|
+
config.subscriber = {
|
|
399
|
+
provider: CustomSubscriberProvider,
|
|
400
|
+
api_key: "your_api_key"
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
See [existing providers](https://github.com/Rails-Designer/courrier/tree/main/lib/courrier/subscriber) for more examples.
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
## FAQ
|
|
408
|
+
|
|
409
|
+
### Is this a replacement for ActionMailer?
|
|
410
|
+
Yes! While different in approach, Courrier can fully replace ActionMailer. It's a modern alternative that focuses on API-based delivery. The main difference is in how emails are structured - Courrier uses a more straightforward, class-based approach.
|
|
411
|
+
|
|
412
|
+
### Is this for Rails only?
|
|
413
|
+
Not at all! While Courrier has some Rails-specific goodies (like the inbox preview feature and generators), it works great with any Ruby application.
|
|
414
|
+
|
|
415
|
+
### Can it send using SMTP?
|
|
416
|
+
No - Courrier is specifically built for API-based email delivery. If SMTP is needed, ActionMailer would be a better choices.
|
|
417
|
+
|
|
418
|
+
### Can separate view templates be created (like ActionMailer)?
|
|
419
|
+
The approach is different here. Instead of separate view files, email content is defined right in the email class using `text` and `html` methods. Layouts can be used to share common templates. This makes emails more self-contained and easier to reason about.
|
|
420
|
+
|
|
421
|
+
### What's the main benefit over ActionMailer?
|
|
422
|
+
Courrier offers a simpler, more modern approach to sending emails. Each email is a standalone class, configuration is straightforward (typically just only an API key is needed) and it packs few quality-of-life features (like the inbox feature and auto-generate text version).
|
|
423
|
+
|
|
424
|
+
|
|
292
425
|
## Contributing
|
|
293
426
|
|
|
294
427
|
This project uses [Standard](https://github.com/testdouble/standard) for formatting Ruby code. Please make sure to run `rake` before submitting pull requests.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class PreviewsController < ActionController::Base
|
|
5
|
+
def index
|
|
6
|
+
@emails = emails.map { Courrier::Email::Providers::Inbox::Email.from_file(_1) }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def show
|
|
10
|
+
file_path = File.join(Courrier.configuration.inbox.destination, params[:id])
|
|
11
|
+
content = File.read(file_path)
|
|
12
|
+
|
|
13
|
+
render html: content.html_safe, layout: false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def emails
|
|
19
|
+
@emails ||= Dir.glob("#{Courrier.configuration.inbox.destination}/*.html")
|
|
20
|
+
.sort_by { -File.basename(_1, ".html").to_i }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Courrier Inbox</title>
|
|
6
|
+
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
9
|
+
* { margin: 0; padding: 0; }
|
|
10
|
+
body { margin: 0; padding: 0; font-size: 16px; line-height: 1.5; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; background-color: rgb(241, 245, 249); }
|
|
11
|
+
|
|
12
|
+
.sidebar {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
position: sticky;
|
|
16
|
+
top: 0;
|
|
17
|
+
padding: .5rem;
|
|
18
|
+
width: min(350px, 33.3333%); height: 100dvh;
|
|
19
|
+
border-right: 1px solid rgb(226, 232, 240);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.sidebar__options {
|
|
23
|
+
position: sticky;
|
|
24
|
+
bottom: 0;
|
|
25
|
+
padding: .5rem;
|
|
26
|
+
background-color: rgb(241, 245, 249);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.sidebar__btn-clear {
|
|
30
|
+
border: none;
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
column-gap: 0.25rem;
|
|
35
|
+
width: 100%;
|
|
36
|
+
padding: .5rem;
|
|
37
|
+
font-size: .875rem; line-height: 1.25rem;
|
|
38
|
+
font-weight: 500;
|
|
39
|
+
color: rgb(71, 85, 105);
|
|
40
|
+
background-color: rgb(226, 232, 240);
|
|
41
|
+
border-radius: .5rem;
|
|
42
|
+
cursor: default;
|
|
43
|
+
|
|
44
|
+
&:hover { background-color: rgb(203, 213, 225); }
|
|
45
|
+
&:active { transform: scale(.98); }
|
|
46
|
+
|
|
47
|
+
[data-slot="icon"] {
|
|
48
|
+
width: .875rem; height: .875rem;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.email-previews {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
flex: 1;
|
|
56
|
+
row-gap: .5rem;
|
|
57
|
+
overflow-y: auto;
|
|
58
|
+
width: 100%;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.email-preview {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: row;
|
|
64
|
+
column-gap: .5rem;
|
|
65
|
+
width: 100%;
|
|
66
|
+
padding: .5rem .75rem;
|
|
67
|
+
text-decoration: none;
|
|
68
|
+
border-radius: .5rem;
|
|
69
|
+
|
|
70
|
+
&:hover {
|
|
71
|
+
background-color: rgba(226, 232, 240, .75);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.email-preview--empty {
|
|
76
|
+
display: none;
|
|
77
|
+
text-align: center;
|
|
78
|
+
|
|
79
|
+
&:only-child {
|
|
80
|
+
display: block;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.email-preview__avatar {
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
padding: .25rem;
|
|
87
|
+
width: 1.5rem; height: 1.5rem;
|
|
88
|
+
background-color: rgb(203, 213, 225);
|
|
89
|
+
color: rgb(51, 65, 85);
|
|
90
|
+
border-radius: 9999px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.email-preview__content {
|
|
94
|
+
font-size: 0.875rem; line-height: 1.25rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.email-preview__recipient {
|
|
98
|
+
font-size: 1rem; line-height: 1.5rem;
|
|
99
|
+
font-weight: 600;
|
|
100
|
+
letter-spacing: -.025em;
|
|
101
|
+
color: rgb(51, 65, 85);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.email-preview__subject {
|
|
105
|
+
margin-top: .125rem;
|
|
106
|
+
overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1;
|
|
107
|
+
color: rgb(100, 116, 139);
|
|
108
|
+
}
|
|
109
|
+
</style>
|
|
110
|
+
</head>
|
|
111
|
+
<body>
|
|
112
|
+
<div style="display: flex; column-gap: 1rem;">
|
|
113
|
+
<aside class="sidebar">
|
|
114
|
+
<ul class="email-previews">
|
|
115
|
+
<li class="email-preview--empty">
|
|
116
|
+
<p class="email-preview__subject">
|
|
117
|
+
No emails yet…
|
|
118
|
+
</p>
|
|
119
|
+
</li>
|
|
120
|
+
|
|
121
|
+
<% @emails.each do |email| %>
|
|
122
|
+
<li>
|
|
123
|
+
<%= link_to courrier.preview_path(email.filename), data: {remote: true}, class: "email-preview" do %>
|
|
124
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon" class="email-preview__avatar">
|
|
125
|
+
<path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd"/>
|
|
126
|
+
</svg>
|
|
127
|
+
|
|
128
|
+
<div class="email-preview__content">
|
|
129
|
+
<small class="email-preview__recipient">
|
|
130
|
+
<%= email.metadata.to %>
|
|
131
|
+
</small>
|
|
132
|
+
|
|
133
|
+
<p class="email-preview__subject">
|
|
134
|
+
<%= email.metadata.subject || "No subject" %>
|
|
135
|
+
</p>
|
|
136
|
+
</div>
|
|
137
|
+
<% end %>
|
|
138
|
+
</li>
|
|
139
|
+
<% end %>
|
|
140
|
+
</ul>
|
|
141
|
+
|
|
142
|
+
<div class="sidebar__options">
|
|
143
|
+
<%= button_to courrier.cleanup_path, class: "sidebar__btn-clear" do %>
|
|
144
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon">
|
|
145
|
+
<path fill-rule="evenodd" d="M16.5 4.478v.227a48.816 48.816 0 0 1 3.878.512.75.75 0 1 1-.256 1.478l-.209-.035-1.005 13.07a3 3 0 0 1-2.991 2.77H8.084a3 3 0 0 1-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 0 1-.256-1.478A48.567 48.567 0 0 1 7.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 0 1 3.369 0c1.603.051 2.815 1.387 2.815 2.951Zm-6.136-1.452a51.196 51.196 0 0 1 3.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 0 0-6 0v-.113c0-.794.609-1.428 1.364-1.452Zm-.355 5.945a.75.75 0 1 0-1.5.058l.347 9a.75.75 0 1 0 1.499-.058l-.346-9Zm5.48.058a.75.75 0 1 0-1.498-.058l-.347 9a.75.75 0 0 0 1.5.058l.345-9Z" clip-rule="evenodd"/>
|
|
146
|
+
</svg>
|
|
147
|
+
|
|
148
|
+
Clear inbox
|
|
149
|
+
<% end %>
|
|
150
|
+
</div>
|
|
151
|
+
</aside>
|
|
152
|
+
|
|
153
|
+
<article style="flex: 1;" id="email-preview">
|
|
154
|
+
</article>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<script>
|
|
158
|
+
document.querySelectorAll("a[data-remote]").forEach(link => {
|
|
159
|
+
link.addEventListener("click", (event) => {
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
|
|
162
|
+
fetch(link.href)
|
|
163
|
+
.then(response => response.text())
|
|
164
|
+
.then(html => {
|
|
165
|
+
document.getElementById("email-preview").innerHTML = html;
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
</script>
|
|
170
|
+
</body>
|
|
171
|
+
</html>
|
data/config/routes.rb
ADDED
data/courrier.gemspec
CHANGED
|
@@ -8,15 +8,15 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ["Rails Designer"]
|
|
9
9
|
spec.email = ["devs@railsdesigner.com"]
|
|
10
10
|
|
|
11
|
-
spec.summary = "
|
|
12
|
-
spec.description = "
|
|
11
|
+
spec.summary = "API-powered email delivery for Ruby apps"
|
|
12
|
+
spec.description = "API-powered email delivery for Ruby apps with support for Postmark, SendGrid, Mailgun and more."
|
|
13
13
|
spec.homepage = "https://railsdesigner.com/courrier/"
|
|
14
14
|
spec.license = "MIT"
|
|
15
15
|
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
17
|
spec.metadata["source_code_uri"] = "https://github.com/Rails-Designer/courrier/"
|
|
18
18
|
|
|
19
|
-
spec.files = Dir["{bin,app,config,
|
|
19
|
+
spec.files = Dir["{bin,app,config,lib}/**/*", "Rakefile", "README.md", "courrier.gemspec", "Gemfile", "Gemfile.lock"]
|
|
20
20
|
|
|
21
21
|
spec.required_ruby_version = ">= 3.2.0"
|
|
22
22
|
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
module Courrier
|
|
4
4
|
class Configuration
|
|
5
|
-
class
|
|
5
|
+
class Inbox
|
|
6
6
|
attr_accessor :destination, :auto_open, :template_path
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
9
9
|
@destination = default_destination
|
|
10
|
-
@auto_open =
|
|
11
|
-
@template_path = File.expand_path("../../../courrier/email/providers/
|
|
10
|
+
@auto_open = false
|
|
11
|
+
@template_path = File.expand_path("../../../courrier/email/providers/inbox/default.html.erb", __FILE__)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
private
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "courrier/configuration/
|
|
3
|
+
require "courrier/configuration/inbox"
|
|
4
4
|
require "courrier/configuration/providers"
|
|
5
5
|
|
|
6
6
|
module Courrier
|
|
@@ -19,13 +19,15 @@ module Courrier
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
class Configuration
|
|
22
|
-
attr_accessor :
|
|
22
|
+
attr_accessor :email, :subscriber, :logger, :email_path, :layouts, :default_url_options, :auto_generate_text,
|
|
23
23
|
:from, :reply_to, :cc, :bcc
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
attr_reader :providers, :inbox
|
|
25
26
|
|
|
26
27
|
def initialize
|
|
27
|
-
@
|
|
28
|
-
@
|
|
28
|
+
@email = {provider: "logger"}
|
|
29
|
+
@subscriber = {}
|
|
30
|
+
|
|
29
31
|
@logger = ::Logger.new($stdout)
|
|
30
32
|
@email_path = default_email_path
|
|
31
33
|
|
|
@@ -39,7 +41,31 @@ module Courrier
|
|
|
39
41
|
@bcc = nil
|
|
40
42
|
|
|
41
43
|
@providers = Courrier::Configuration::Providers.new
|
|
42
|
-
@
|
|
44
|
+
@inbox = Courrier::Configuration::Inbox.new
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def provider
|
|
48
|
+
warn "[DEPRECATION] `provider` is deprecated. Use `email = { provider: '…' }` instead. Will be removed in 1.0.0"
|
|
49
|
+
|
|
50
|
+
@email[:provider]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def provider=(value)
|
|
54
|
+
warn "[DEPRECATION] `provider=` is deprecated. Use `email = { provider: '…' }` instead. Will be removed in 1.0.0"
|
|
55
|
+
|
|
56
|
+
@email[:provider] = value
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def api_key
|
|
60
|
+
warn "[DEPRECATION] `api_key` is deprecated. Use `email = { api_key: '…' }` instead. Will be removed in 1.0.0"
|
|
61
|
+
|
|
62
|
+
@email[:api_key]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def api_key=(value)
|
|
66
|
+
warn "[DEPRECATION] `api_key=` is deprecated. Use `email = { api_key: '…' }` instead. Will be removed in 1.0.0"
|
|
67
|
+
|
|
68
|
+
@email[:api_key] = value
|
|
43
69
|
end
|
|
44
70
|
|
|
45
71
|
private
|
|
@@ -31,6 +31,21 @@ module Courrier
|
|
|
31
31
|
|
|
32
32
|
def html = wrap(@html, with_layout: :html)
|
|
33
33
|
|
|
34
|
+
def to_h
|
|
35
|
+
{
|
|
36
|
+
from: @from,
|
|
37
|
+
to: @to,
|
|
38
|
+
reply_to: @reply_to,
|
|
39
|
+
cc: @cc,
|
|
40
|
+
bcc: @bcc,
|
|
41
|
+
subject: @subject,
|
|
42
|
+
text: @text,
|
|
43
|
+
html: @html,
|
|
44
|
+
auto_generate_text: @auto_generate_text,
|
|
45
|
+
layouts: @layouts
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
34
49
|
private
|
|
35
50
|
|
|
36
51
|
def wrap(content, with_layout:)
|