rails_courrier 0.5.0 → 1.0.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 +1 -0
- data/Gemfile.lock +8 -3
- data/README.md +60 -18
- data/app/assets/stylesheets/courrier/application.css +145 -0
- data/app/views/courrier/previews/default.html.erb +260 -0
- data/app/views/courrier/previews/index.html.erb +15 -110
- data/bin/rails +14 -0
- data/lib/courrier/configuration/inbox.rb +6 -3
- data/lib/courrier/email/delivery_job.rb +6 -1
- data/lib/courrier/email/providers/inbox.rb +2 -2
- data/lib/courrier/email/translation.rb +19 -0
- data/lib/courrier/railtie.rb +1 -0
- data/lib/generators/courrier/install_generator.rb +10 -0
- data/lib/generators/courrier/templates/initializer.rb.tt +23 -5
- data/lib/rails_courrier/version.rb +1 -1
- data/lib/rails_courrier.rb +1 -0
- data/rails_courrier.gemspec +2 -2
- metadata +9 -6
- data/lib/courrier/email/providers/inbox/default.html.erb +0 -127
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b5250c19187be9e82ca4e09ca8e93d7c033581ebbdc649cbb63720683876dd9
|
|
4
|
+
data.tar.gz: b75dccdb78ae7b06714c76b51bf17cbe3df5cb9f5d4b4cddce8910b0c3aad684
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ccc0fc249bb46a36b1581c450d5b358b875e6155226a4f72e3d54847d34a237b93aedcaf37b3c8e521b4429f78184b2d5a7c80296bbdfa6a71423a23abdbda00
|
|
7
|
+
data.tar.gz: 21eec3ae5f91d04f211e2fb4ac9000eadea726a2cc241ab73be6a1d6d424a08dd3331f4c8b5584411b6dc5072fce5a05bef571dae094a48f87b9a0b8e807c86a
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
rails_courrier (0.
|
|
4
|
+
rails_courrier (1.0.0)
|
|
5
5
|
actionpack (>= 7.0)
|
|
6
6
|
actionview (>= 7.0)
|
|
7
7
|
activejob (>= 7.0)
|
|
8
|
-
courrier (~> 0.
|
|
8
|
+
courrier (~> 1.0.0)
|
|
9
9
|
launchy (>= 3.1, < 4)
|
|
10
10
|
railties (>= 7.0)
|
|
11
11
|
|
|
@@ -54,7 +54,7 @@ GEM
|
|
|
54
54
|
logger (~> 1.5)
|
|
55
55
|
concurrent-ruby (1.3.6)
|
|
56
56
|
connection_pool (3.0.2)
|
|
57
|
-
courrier (0.
|
|
57
|
+
courrier (1.0.0)
|
|
58
58
|
logger (>= 1.5, < 3)
|
|
59
59
|
nokogiri (>= 1.18, < 2)
|
|
60
60
|
crass (1.0.6)
|
|
@@ -111,6 +111,10 @@ GEM
|
|
|
111
111
|
prettyprint
|
|
112
112
|
prettyprint (0.2.0)
|
|
113
113
|
prism (1.9.0)
|
|
114
|
+
propshaft (1.3.2)
|
|
115
|
+
actionpack (>= 7.0.0)
|
|
116
|
+
activesupport (>= 7.0.0)
|
|
117
|
+
rack
|
|
114
118
|
psych (5.4.0)
|
|
115
119
|
date
|
|
116
120
|
stringio
|
|
@@ -206,6 +210,7 @@ PLATFORMS
|
|
|
206
210
|
DEPENDENCIES
|
|
207
211
|
debug (~> 1.9, >= 1.9.2)
|
|
208
212
|
minitest (~> 5.25, >= 5.25.5)
|
|
213
|
+
propshaft
|
|
209
214
|
rails_courrier!
|
|
210
215
|
rake (~> 13.2, >= 13.2.1)
|
|
211
216
|
standard (~> 1.49)
|
data/README.md
CHANGED
|
@@ -2,16 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Rails-specific features for [Courrier](https://github.com/Rails-Designer/courrier), the API-powered email delivery gem for Ruby.
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
## Installation
|
|
9
|
-
|
|
10
|
-
Add to your Gemfile:
|
|
11
|
-
```bash
|
|
12
|
-
bundle add rails_courrier
|
|
13
|
-
```
|
|
14
|
-
|
|
15
8
|
<a href="https://railsdesigner.com/" target="_blank">
|
|
16
9
|
<picture>
|
|
17
10
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Rails-Designer/courrier/HEAD/.github/logo-dark.svg">
|
|
@@ -23,11 +16,22 @@ bundle add rails_courrier
|
|
|
23
16
|
**Sponsored By [Rails Designer](https://railsdesigner.com/)**
|
|
24
17
|
|
|
25
18
|
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Add to your Gemfile:
|
|
22
|
+
```bash
|
|
23
|
+
bundle add rails_courrier
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
|
|
26
27
|
## Setup
|
|
27
28
|
|
|
28
29
|
Generate the initializer:
|
|
29
30
|
```bash
|
|
30
31
|
bin/rails generate courrier:install
|
|
32
|
+
|
|
33
|
+
# Or pass a provider to pre-fill provider-specific config:
|
|
34
|
+
bin/rails generate courrier:install --provider=mailpace
|
|
31
35
|
```
|
|
32
36
|
|
|
33
37
|
This creates `config/initializers/courrier.rb`.
|
|
@@ -39,7 +43,7 @@ bin/rails generate courrier:email Order
|
|
|
39
43
|
|
|
40
44
|
This creates `app/emails/order_email.rb`.
|
|
41
45
|
|
|
42
|
-
Mount the engine
|
|
46
|
+
Mount the engine to browse inbox previews in your browser:
|
|
43
47
|
```ruby
|
|
44
48
|
# config/routes.rb
|
|
45
49
|
mount Courrier::Engine => "/courrier"
|
|
@@ -51,14 +55,14 @@ mount Courrier::Engine => "/courrier"
|
|
|
51
55
|
### Send emails
|
|
52
56
|
|
|
53
57
|
```ruby
|
|
54
|
-
OrderEmail.deliver to: "recipient@
|
|
58
|
+
OrderEmail.deliver to: "recipient@railsdesigner.com"
|
|
55
59
|
```
|
|
56
60
|
|
|
57
61
|
|
|
58
62
|
### Deliver later via ActiveJob
|
|
59
63
|
|
|
60
64
|
```ruby
|
|
61
|
-
OrderEmail.deliver_later to: "recipient@
|
|
65
|
+
OrderEmail.deliver_later to: "recipient@railsdesigner.com"
|
|
62
66
|
```
|
|
63
67
|
|
|
64
68
|
Configure queue options in the email class:
|
|
@@ -78,16 +82,11 @@ Preview emails in your browser:
|
|
|
78
82
|
```ruby
|
|
79
83
|
# config/initializers/courrier.rb
|
|
80
84
|
Courrier.configure do |config|
|
|
81
|
-
config.email = { provider: "inbox" }
|
|
85
|
+
config.email = { provider: "inbox", auto_open: true }
|
|
82
86
|
end
|
|
83
87
|
```
|
|
84
88
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
```ruby
|
|
88
|
-
config.email = { provider: "inbox" }
|
|
89
|
-
config.inbox.auto_open = true
|
|
90
|
-
```
|
|
89
|
+
The `auto_open` option opens each sent email in your default browser automatically.
|
|
91
90
|
|
|
92
91
|
Clear inbox files:
|
|
93
92
|
|
|
@@ -108,6 +107,49 @@ end
|
|
|
108
107
|
```
|
|
109
108
|
|
|
110
109
|
|
|
110
|
+
### Monitoring
|
|
111
|
+
|
|
112
|
+
Rails Courrier publishes delivery lifecycle events via `ActiveSupport::Notifications`:
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
ActiveSupport::Notifications.subscribe("delivery.courrier") do |event|
|
|
116
|
+
Rails.logger.info "[Courrier] #{event.payload[:email]} delivered in #{event.duration}ms"
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### Available events
|
|
121
|
+
|
|
122
|
+
| Event | When it fires | Payload |
|
|
123
|
+
|-------|---------------|---------|
|
|
124
|
+
| `delivery.courrier` | Email delivered successfully | `email`, `options` |
|
|
125
|
+
| `delivery_failed.courrier` | Delivery raised an exception | `email`, `options`, `exception` |
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
### I18n
|
|
129
|
+
|
|
130
|
+
Use the `t` helper in email classes and ERB templates, scoped under `courrier.email.<class_name>`:
|
|
131
|
+
```yaml
|
|
132
|
+
# config/locales/courrier/en.yml
|
|
133
|
+
en:
|
|
134
|
+
courrier:
|
|
135
|
+
email:
|
|
136
|
+
order_email:
|
|
137
|
+
subject: "Your order is ready!"
|
|
138
|
+
text:
|
|
139
|
+
greeting: "Hello %{name}, your order is ready."
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
class OrderEmail < Courrier::Email
|
|
144
|
+
def subject = t(".subject")
|
|
145
|
+
|
|
146
|
+
def text = t(".text.greeting", name: options.to)
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Full translation keys (e.g. `t("shared.greeting")`) pass through unscoped.
|
|
151
|
+
|
|
152
|
+
|
|
111
153
|
## Documentation
|
|
112
154
|
|
|
113
155
|
See the [Courrier README](https://github.com/Rails-Designer/courrier) for full configuration, providers, layouts, templates and more.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
@layer reset, base, components;
|
|
2
|
+
|
|
3
|
+
@layer reset {
|
|
4
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
5
|
+
* { margin: 0; padding: 0; }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@layer base {
|
|
9
|
+
:root {
|
|
10
|
+
--gray-50: oklch(98.7% .002 197.1);
|
|
11
|
+
--gray-100: oklch(96.3% .002 197.1);
|
|
12
|
+
--gray-200: oklch(92.5% .005 214.3);
|
|
13
|
+
--gray-300: oklch(87.2% .007 219.6);
|
|
14
|
+
--gray-400: oklch(72.3% .014 214.4);
|
|
15
|
+
--gray-500: oklch(56% .021 213.5);
|
|
16
|
+
--gray-600: oklch(45% .017 213.2);
|
|
17
|
+
--gray-700: oklch(37.8% .015 216);
|
|
18
|
+
--gray-800: oklch(27.5% .011 216.9);
|
|
19
|
+
--gray-900: oklch(21.8% .008 223.9);
|
|
20
|
+
--gray-950: oklch(14.8% .004 228.8);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
body {
|
|
24
|
+
margin: 0;
|
|
25
|
+
padding: 0;
|
|
26
|
+
font-size: 16px;
|
|
27
|
+
line-height: 1.5;
|
|
28
|
+
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";
|
|
29
|
+
background: white;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@layer components {
|
|
34
|
+
.previews-layout {
|
|
35
|
+
display: flex;
|
|
36
|
+
column-gap: 1rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.preview-pane { flex: 1; }
|
|
40
|
+
|
|
41
|
+
.sidebar {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
position: sticky;
|
|
45
|
+
inset-block-start: 0;
|
|
46
|
+
padding: .5rem;
|
|
47
|
+
width: min(350px, 33.3333%);
|
|
48
|
+
height: 100dvh;
|
|
49
|
+
border-inline-end: 1px solid var(--gray-200);
|
|
50
|
+
|
|
51
|
+
.options {
|
|
52
|
+
position: sticky;
|
|
53
|
+
inset-block-end: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.btn-clear {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
justify-content: center;
|
|
60
|
+
column-gap: .375rem;
|
|
61
|
+
width: 100%;
|
|
62
|
+
padding: .5rem;
|
|
63
|
+
font-size: .875rem;
|
|
64
|
+
line-height: 1.25rem;
|
|
65
|
+
font-weight: 400;
|
|
66
|
+
color: var(--gray-800);
|
|
67
|
+
background: var(--gray-100);
|
|
68
|
+
border: none;
|
|
69
|
+
border-radius: .375rem;
|
|
70
|
+
cursor: default;
|
|
71
|
+
|
|
72
|
+
&:hover { background: var(--gray-200); }
|
|
73
|
+
&:active { transform: scale(.98); }
|
|
74
|
+
|
|
75
|
+
[data-slot="icon"] {
|
|
76
|
+
width: .875rem; height: auto;
|
|
77
|
+
aspect-ratio: 1;
|
|
78
|
+
color: oklch(from currentColor l c h / .75);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.email-previews {
|
|
84
|
+
display: flex;
|
|
85
|
+
flex-direction: column;
|
|
86
|
+
flex: 1;
|
|
87
|
+
row-gap: .5rem;
|
|
88
|
+
width: 100%;
|
|
89
|
+
overflow-y: auto;
|
|
90
|
+
|
|
91
|
+
.empty {
|
|
92
|
+
display: none;
|
|
93
|
+
font-size: .875rem;
|
|
94
|
+
font-weight: 300;
|
|
95
|
+
color: var(--gray-500);
|
|
96
|
+
text-align: center;
|
|
97
|
+
|
|
98
|
+
&:only-child { display: block; }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.email-preview {
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-direction: row;
|
|
105
|
+
column-gap: .5rem;
|
|
106
|
+
width: 100%;
|
|
107
|
+
padding: .5rem .75rem;
|
|
108
|
+
text-decoration: none;
|
|
109
|
+
border-radius: .25rem;
|
|
110
|
+
|
|
111
|
+
&:hover { background: var(--gray-100); }
|
|
112
|
+
|
|
113
|
+
.avatar {
|
|
114
|
+
flex-shrink: 0;
|
|
115
|
+
padding: .25rem;
|
|
116
|
+
width: 1.25rem; height: 1.25rem;
|
|
117
|
+
background: var(--gray-300);
|
|
118
|
+
color: var(--gray-700);
|
|
119
|
+
border-radius: 9999px;
|
|
120
|
+
transform: translateY(.25em);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.content {
|
|
124
|
+
line-height: 1.25rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.recipient {
|
|
128
|
+
line-height: 1.5rem;
|
|
129
|
+
font-size: .875rem;
|
|
130
|
+
font-weight: 400;
|
|
131
|
+
color: var(--gray-700);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.subject {
|
|
135
|
+
margin-block-start: .125rem;
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
display: -webkit-box;
|
|
138
|
+
-webkit-box-orient: vertical;
|
|
139
|
+
-webkit-line-clamp: 1;
|
|
140
|
+
font-size: .875rem;
|
|
141
|
+
font-weight: 300;
|
|
142
|
+
color: var(--gray-600);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
{
|
|
3
|
+
"to": "<%= email %>",
|
|
4
|
+
"subject": "<%= @options.subject %>"
|
|
5
|
+
}
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
<!DOCTYPE html>
|
|
9
|
+
<html>
|
|
10
|
+
<head>
|
|
11
|
+
<meta charset="UTF-8">
|
|
12
|
+
<title><%= @options.subject %></title>
|
|
13
|
+
<style>
|
|
14
|
+
:root {
|
|
15
|
+
--gray-50: oklch(98.7% .002 197.1);
|
|
16
|
+
--gray-100: oklch(96.3% .002 197.1);
|
|
17
|
+
--gray-200: oklch(92.5% .005 214.3);
|
|
18
|
+
--gray-300: oklch(87.2% .007 219.6);
|
|
19
|
+
--gray-400: oklch(72.3% .014 214.4);
|
|
20
|
+
--gray-500: oklch(56% .021 213.5);
|
|
21
|
+
--gray-600: oklch(45% .017 213.2);
|
|
22
|
+
--gray-700: oklch(37.8% .015 216);
|
|
23
|
+
--gray-800: oklch(27.5% .011 216.9);
|
|
24
|
+
--gray-900: oklch(21.8% .008 223.9);
|
|
25
|
+
--gray-950: oklch(14.8% .004 228.8);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
29
|
+
|
|
30
|
+
body {
|
|
31
|
+
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
32
|
+
font-size: 1rem;
|
|
33
|
+
line-height: 1.5;
|
|
34
|
+
color: var(--gray-800);
|
|
35
|
+
background: var(--gray-50);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
article {
|
|
39
|
+
max-width: 65rem;
|
|
40
|
+
margin-inline: auto;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.header {
|
|
44
|
+
padding: 1.25rem 1rem 0;
|
|
45
|
+
border-block-end: 1px solid var(--gray-100);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.metadata {
|
|
49
|
+
display: flex;
|
|
50
|
+
gap: .75rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.avatar {
|
|
54
|
+
flex-shrink: 0;
|
|
55
|
+
padding: .25rem;
|
|
56
|
+
width: 2rem;
|
|
57
|
+
height: 2rem;
|
|
58
|
+
background: var(--gray-200);
|
|
59
|
+
color: var(--gray-600);
|
|
60
|
+
border-radius: 9999px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.meta {
|
|
64
|
+
flex: 1;
|
|
65
|
+
min-width: 0;
|
|
66
|
+
|
|
67
|
+
.row {
|
|
68
|
+
display: flex;
|
|
69
|
+
gap: .5rem;
|
|
70
|
+
font-size: .875rem;
|
|
71
|
+
line-height: 1.6;
|
|
72
|
+
|
|
73
|
+
.label {
|
|
74
|
+
flex-shrink: 0;
|
|
75
|
+
width: 3.5rem;
|
|
76
|
+
color: var(--gray-500);
|
|
77
|
+
font-weight: 300;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.value {
|
|
81
|
+
color: var(--gray-700);
|
|
82
|
+
min-width: 0;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
text-overflow: ellipsis;
|
|
85
|
+
white-space: nowrap;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.subject {
|
|
91
|
+
margin-block-start: .75rem;
|
|
92
|
+
padding-block: .75rem;
|
|
93
|
+
border-block-start: 1px solid var(--gray-100);
|
|
94
|
+
display: flex;
|
|
95
|
+
gap: .5rem;
|
|
96
|
+
|
|
97
|
+
.label {
|
|
98
|
+
flex-shrink: 0;
|
|
99
|
+
width: 3.5rem;
|
|
100
|
+
font-size: .875rem;
|
|
101
|
+
color: var(--gray-500);
|
|
102
|
+
font-weight: 400;
|
|
103
|
+
line-height: 1.75rem;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.text {
|
|
107
|
+
flex: 1;
|
|
108
|
+
font-size: 1.125rem;
|
|
109
|
+
font-weight: 500;
|
|
110
|
+
color: var(--gray-900);
|
|
111
|
+
line-height: 1.75rem;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.datetime {
|
|
115
|
+
flex-shrink: 0;
|
|
116
|
+
font-size: .75rem;
|
|
117
|
+
font-weight: 300;
|
|
118
|
+
color: var(--gray-500);
|
|
119
|
+
line-height: 1.75rem;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.email {
|
|
124
|
+
padding-block-start: 1rem;
|
|
125
|
+
padding-inline: 1rem;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.preview-toggle {
|
|
129
|
+
display: none;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.toggle {
|
|
133
|
+
display: flex;
|
|
134
|
+
gap: .25rem;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.toggle-label {
|
|
138
|
+
display: inline-flex;
|
|
139
|
+
padding: .125rem .75rem;
|
|
140
|
+
font-size: .75rem;
|
|
141
|
+
line-height: 1.25rem;
|
|
142
|
+
font-weight: 400;
|
|
143
|
+
color: var(--gray-600);
|
|
144
|
+
background: var(--gray-50);
|
|
145
|
+
border: 1px solid var(--gray-100);
|
|
146
|
+
border-radius: .375rem;
|
|
147
|
+
cursor: pointer;
|
|
148
|
+
transition: all .15s;
|
|
149
|
+
|
|
150
|
+
&:hover { border-color: var(--gray-200); }
|
|
151
|
+
&:active { transform: scale(.98); }
|
|
152
|
+
|
|
153
|
+
.view-html { display: inline; }
|
|
154
|
+
.view-text { display: none; }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.preview-toggle:checked ~ .toggle .toggle-label {
|
|
158
|
+
background: var(--gray-100);
|
|
159
|
+
border-color: var(--gray-300);
|
|
160
|
+
|
|
161
|
+
.view-html { display: none; }
|
|
162
|
+
.view-text { display: inline; }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.preview-toggle[disabled] ~ .toggle .toggle-label {
|
|
166
|
+
opacity: .5;
|
|
167
|
+
cursor: not-allowed;
|
|
168
|
+
pointer-events: none;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.preview-text {
|
|
172
|
+
margin-block-start: 1.25rem;
|
|
173
|
+
|
|
174
|
+
pre {
|
|
175
|
+
margin: 0;
|
|
176
|
+
white-space: pre-wrap;
|
|
177
|
+
font-size: .875rem;
|
|
178
|
+
line-height: 1.6;
|
|
179
|
+
color: var(--gray-700);
|
|
180
|
+
font-family: ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", Menlo, monospace;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.preview-html {
|
|
185
|
+
display: none;
|
|
186
|
+
margin-block-start: 1rem;
|
|
187
|
+
|
|
188
|
+
p + p { margin-block-start: 1rem; }
|
|
189
|
+
ul, ol { padding-inline-start: 1.5rem; }
|
|
190
|
+
li + li { margin-block-start: .375rem; }
|
|
191
|
+
h1, h2, h3, h4 { line-height: 1.3; color: var(--gray-900); }
|
|
192
|
+
h1 { font-size: 1.5rem; margin-block: 1rem .5rem; }
|
|
193
|
+
h2 { font-size: 1.25rem; margin-block: .875rem .5rem; }
|
|
194
|
+
h3 { font-size: 1.125rem; margin-block: .75rem .375rem; }
|
|
195
|
+
blockquote {
|
|
196
|
+
margin-inline-start: 0;
|
|
197
|
+
padding-inline-start: 1rem;
|
|
198
|
+
border-inline-start: 3px solid var(--gray-300);
|
|
199
|
+
color: var(--gray-600);
|
|
200
|
+
}
|
|
201
|
+
a { color: var(--gray-700); text-decoration: underline; }
|
|
202
|
+
hr { margin-block: 1.5rem; border: none; border-block-start: 1px solid var(--gray-200); }
|
|
203
|
+
pre { overflow-x: auto; }
|
|
204
|
+
img { max-width: 100%; height: auto; }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.preview-toggle:checked ~ .preview-text { display: none; }
|
|
208
|
+
.preview-toggle:checked ~ .preview-html { display: block; }
|
|
209
|
+
</style>
|
|
210
|
+
</head>
|
|
211
|
+
|
|
212
|
+
<body>
|
|
213
|
+
<article>
|
|
214
|
+
<header class="header">
|
|
215
|
+
<div class="metadata">
|
|
216
|
+
<svg class="avatar" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
217
|
+
<path 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"/>
|
|
218
|
+
</svg>
|
|
219
|
+
|
|
220
|
+
<div class="meta">
|
|
221
|
+
<div class="row">
|
|
222
|
+
<span class="label">From</span>
|
|
223
|
+
<span class="value"><%= @options.respond_to?(:from) ? @options.from : "—" %></span>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="row">
|
|
226
|
+
<span class="label">To</span>
|
|
227
|
+
<span class="value"><%= name ? "#{name} <#{email}>" : email %></span>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<div class="subject">
|
|
233
|
+
<span class="label">Subject</span>
|
|
234
|
+
<span class="text"><%= @options.subject %></span>
|
|
235
|
+
<time class="datetime" datetime="<%= Time.now.strftime("%Y-%m-%d %H:%M:%S %z") %>">
|
|
236
|
+
<%= Time.now.strftime("%Y-%m-%d %H:%M:%S %z") %>
|
|
237
|
+
</time>
|
|
238
|
+
</div>
|
|
239
|
+
</header>
|
|
240
|
+
|
|
241
|
+
<div class="email">
|
|
242
|
+
<input type="checkbox" id="preview-toggle" class="preview-toggle" <%= html ? "" : "disabled" %>>
|
|
243
|
+
<div class="toggle">
|
|
244
|
+
<label for="preview-toggle" class="toggle-label">
|
|
245
|
+
<span class="view-html">View HTML</span>
|
|
246
|
+
<span class="view-text">View Text</span>
|
|
247
|
+
</label>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<div class="preview-text">
|
|
251
|
+
<pre><%= text || "(no text content)" %></pre>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<div class="preview-html">
|
|
255
|
+
<%= html || "<p>(no HTML content)</p>" %>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</article>
|
|
259
|
+
</body>
|
|
260
|
+
</html>
|
|
@@ -4,112 +4,15 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<title>Courrier Inbox</title>
|
|
6
6
|
|
|
7
|
-
|
|
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: .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"] { width: .875rem; height: .875rem; }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.email-previews {
|
|
51
|
-
display: flex;
|
|
52
|
-
flex-direction: column;
|
|
53
|
-
flex: 1;
|
|
54
|
-
row-gap: .5rem;
|
|
55
|
-
overflow-y: auto;
|
|
56
|
-
width: 100%;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.email-preview {
|
|
60
|
-
display: flex;
|
|
61
|
-
flex-direction: row;
|
|
62
|
-
column-gap: .5rem;
|
|
63
|
-
width: 100%;
|
|
64
|
-
padding: .5rem .75rem;
|
|
65
|
-
text-decoration: none;
|
|
66
|
-
border-radius: .5rem;
|
|
67
|
-
|
|
68
|
-
&:hover { background-color: rgb(226 232 240 / .75); }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.email-preview--empty {
|
|
72
|
-
display: none;
|
|
73
|
-
text-align: center;
|
|
74
|
-
|
|
75
|
-
&:only-child { display: block; }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.email-preview__avatar {
|
|
79
|
-
flex-shrink: 0;
|
|
80
|
-
padding: .25rem;
|
|
81
|
-
width: 1.5rem; height: 1.5rem;
|
|
82
|
-
background-color: rgb(203 213 225);
|
|
83
|
-
color: rgb(51, 65, 85);
|
|
84
|
-
border-radius: 9999px;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.email-preview__content {
|
|
88
|
-
font-size: .875rem;
|
|
89
|
-
line-height: 1.25rem;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.email-preview__recipient {
|
|
93
|
-
font-size: 1rem; line-height: 1.5rem;
|
|
94
|
-
font-weight: 600;
|
|
95
|
-
letter-spacing: -.025em;
|
|
96
|
-
color: rgb(51 65 85);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.email-preview__subject {
|
|
100
|
-
margin-top: .125rem;
|
|
101
|
-
overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1;
|
|
102
|
-
color: rgb(100 116 139);
|
|
103
|
-
}
|
|
104
|
-
</style>
|
|
7
|
+
<%= stylesheet_link_tag "courrier/application", media: "all" %>
|
|
105
8
|
</head>
|
|
106
9
|
|
|
107
10
|
<body>
|
|
108
|
-
<div
|
|
11
|
+
<div class="previews-layout">
|
|
109
12
|
<aside class="sidebar">
|
|
110
13
|
<ul class="email-previews">
|
|
111
|
-
<li class="
|
|
112
|
-
<p class="
|
|
14
|
+
<li class="empty">
|
|
15
|
+
<p class="subject">
|
|
113
16
|
No emails yet…
|
|
114
17
|
</p>
|
|
115
18
|
</li>
|
|
@@ -117,16 +20,16 @@
|
|
|
117
20
|
<% @emails.each do |email| %>
|
|
118
21
|
<li>
|
|
119
22
|
<%= link_to courrier.preview_path(email.filename), data: {remote: true}, class: "email-preview" do %>
|
|
120
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon" class="
|
|
23
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon" class="avatar">
|
|
121
24
|
<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"/>
|
|
122
25
|
</svg>
|
|
123
26
|
|
|
124
|
-
<div class="
|
|
125
|
-
<small class="
|
|
27
|
+
<div class="content">
|
|
28
|
+
<small class="recipient">
|
|
126
29
|
<%= email.metadata.to %>
|
|
127
30
|
</small>
|
|
128
31
|
|
|
129
|
-
<p class="
|
|
32
|
+
<p class="subject">
|
|
130
33
|
<%= email.metadata.subject || "No subject" %>
|
|
131
34
|
</p>
|
|
132
35
|
</div>
|
|
@@ -135,8 +38,8 @@
|
|
|
135
38
|
<% end %>
|
|
136
39
|
</ul>
|
|
137
40
|
|
|
138
|
-
<div class="
|
|
139
|
-
<%= button_to courrier.cleanup_path, class: "
|
|
41
|
+
<div class="options">
|
|
42
|
+
<%= button_to courrier.cleanup_path, class: "btn-clear" do %>
|
|
140
43
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon">
|
|
141
44
|
<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"/>
|
|
142
45
|
</svg>
|
|
@@ -146,8 +49,7 @@
|
|
|
146
49
|
</div>
|
|
147
50
|
</aside>
|
|
148
51
|
|
|
149
|
-
<
|
|
150
|
-
</article>
|
|
52
|
+
<main id="email"></main>
|
|
151
53
|
</div>
|
|
152
54
|
|
|
153
55
|
<script>
|
|
@@ -158,7 +60,10 @@
|
|
|
158
60
|
fetch(link.href)
|
|
159
61
|
.then(response => response.text())
|
|
160
62
|
.then(html => {
|
|
161
|
-
document.getElementById("email
|
|
63
|
+
const preview = document.getElementById("email");
|
|
64
|
+
|
|
65
|
+
if (!preview.shadowRoot) preview.attachShadow({mode: "open"});
|
|
66
|
+
preview.shadowRoot.innerHTML = html;
|
|
162
67
|
});
|
|
163
68
|
});
|
|
164
69
|
});
|
data/bin/rails
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# This command will automatically be run when you run "rails" with Rails gems
|
|
3
|
+
# installed from the root of your application.
|
|
4
|
+
|
|
5
|
+
ENGINE_ROOT = File.expand_path("..", __dir__)
|
|
6
|
+
ENGINE_PATH = File.expand_path("../lib/fuik/engine", __dir__)
|
|
7
|
+
APP_PATH = File.expand_path("../test/dummy/config/application", __dir__)
|
|
8
|
+
|
|
9
|
+
# Set up gems listed in the Gemfile.
|
|
10
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
11
|
+
require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
|
|
12
|
+
|
|
13
|
+
require "rails/all"
|
|
14
|
+
require "rails/engine/commands"
|
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
module Courrier
|
|
4
4
|
class Configuration
|
|
5
5
|
class Inbox
|
|
6
|
-
attr_accessor :destination, :
|
|
6
|
+
attr_accessor :destination, :template_path
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
9
9
|
@destination = default_destination
|
|
10
|
-
@
|
|
11
|
-
@template_path = File.expand_path("../email/providers/inbox/default.html.erb", __dir__)
|
|
10
|
+
@template_path = File.expand_path("../../../app/views/courrier/previews/default.html.erb", __dir__)
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
private
|
|
@@ -17,5 +16,9 @@ module Courrier
|
|
|
17
16
|
Rails.root.join("tmp", "courrier", "emails").to_s
|
|
18
17
|
end
|
|
19
18
|
end
|
|
19
|
+
|
|
20
|
+
def inbox
|
|
21
|
+
@inbox ||= Inbox.new
|
|
22
|
+
end
|
|
20
23
|
end
|
|
21
24
|
end
|
|
@@ -4,7 +4,12 @@ module Courrier
|
|
|
4
4
|
class Email
|
|
5
5
|
class DeliveryJob < ActiveJob::Base
|
|
6
6
|
def perform(email_class_name, **options)
|
|
7
|
-
Object.const_get(email_class_name).new(**options).deliver_now
|
|
7
|
+
Object.const_get(email_class_name).new(**options).deliver_now.tap do
|
|
8
|
+
ActiveSupport::Notifications.instrument("delivery.courrier", email: email_class_name, options: options)
|
|
9
|
+
end
|
|
10
|
+
rescue => exception
|
|
11
|
+
ActiveSupport::Notifications.instrument("delivery_failed.courrier", email: email_class_name, options: options, exception: [exception.class.name, exception.message])
|
|
12
|
+
raise
|
|
8
13
|
end
|
|
9
14
|
end
|
|
10
15
|
end
|
|
@@ -15,7 +15,7 @@ module Courrier
|
|
|
15
15
|
|
|
16
16
|
File.write(file_path, ERB.new(File.read(config.template_path)).result(binding))
|
|
17
17
|
|
|
18
|
-
Launchy.open(file_path) if
|
|
18
|
+
Launchy.open(file_path) if Courrier.configuration.email[:auto_open]
|
|
19
19
|
|
|
20
20
|
"📮 Email saved to #{file_path} and #{email_destination}"
|
|
21
21
|
end
|
|
@@ -47,7 +47,7 @@ module Courrier
|
|
|
47
47
|
def config = @config ||= Courrier.configuration.inbox
|
|
48
48
|
|
|
49
49
|
def email_destination
|
|
50
|
-
return "opened in your default browser" if
|
|
50
|
+
return "opened in your default browser" if Courrier.configuration.email[:auto_open]
|
|
51
51
|
|
|
52
52
|
path = begin
|
|
53
53
|
Rails.application.routes.url_helpers.courrier_path
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Courrier
|
|
4
|
+
class Email
|
|
5
|
+
module Translation
|
|
6
|
+
def t(key, **options)
|
|
7
|
+
key = "#{i18n_scope}#{key}" if key.start_with?(".")
|
|
8
|
+
|
|
9
|
+
I18n.t(key, **options)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def i18n_scope
|
|
15
|
+
"courrier.email.#{self.class.name.underscore.tr("/", ".")}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/courrier/railtie.rb
CHANGED
|
@@ -4,8 +4,18 @@ module Courrier
|
|
|
4
4
|
|
|
5
5
|
source_root File.expand_path("templates", __dir__)
|
|
6
6
|
|
|
7
|
+
class_option :provider, type: :string, desc: "Email delivery provider (#{Courrier::Email::Provider::PROVIDERS.keys.join(", ")})"
|
|
8
|
+
|
|
7
9
|
def copy_initializer_file
|
|
8
10
|
template "initializer.rb", "config/initializers/courrier.rb"
|
|
9
11
|
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def provider_config_options
|
|
16
|
+
provider_class = Courrier::Email::Provider::PROVIDERS[options[:provider]&.to_sym]
|
|
17
|
+
|
|
18
|
+
provider_class ? provider_class.config_options : []
|
|
19
|
+
end
|
|
10
20
|
end
|
|
11
21
|
end
|
|
@@ -2,19 +2,37 @@ Courrier.configure do |config|
|
|
|
2
2
|
include Courrier::Email::Address
|
|
3
3
|
|
|
4
4
|
# Set your email delivery provider
|
|
5
|
+
<% if options[:provider] -%>
|
|
6
|
+
config.email = {
|
|
7
|
+
provider: "<%= options[:provider] %>", # default: `logger`, other options: <%= Courrier::Email::Provider::PROVIDERS.keys.join(", ") %>
|
|
8
|
+
api_key: "" # provider API key
|
|
9
|
+
}
|
|
10
|
+
<% else -%>
|
|
5
11
|
# config.email = {
|
|
6
|
-
# provider: "", # default, `logger`,
|
|
7
|
-
# api_key: ""
|
|
12
|
+
# provider: "", # default, `logger`, other options: <%= Courrier::Email::Provider::PROVIDERS.keys.join(", ") %>
|
|
13
|
+
# api_key: "" # Your transactional email provider's API key
|
|
8
14
|
# }
|
|
15
|
+
<% end -%>
|
|
9
16
|
|
|
17
|
+
<% if options[:provider] && provider_config_options.any? -%>
|
|
10
18
|
|
|
11
19
|
# Configure provider-specific settings
|
|
12
|
-
|
|
13
|
-
# config.providers
|
|
20
|
+
<% provider_config_options.each do |key| -%>
|
|
21
|
+
# config.providers.<%= options[:provider] %>.<%= key %> = ""
|
|
22
|
+
<% end -%>
|
|
23
|
+
<% elsif !options[:provider] -%>
|
|
24
|
+
|
|
25
|
+
# Configure provider-specific settings
|
|
26
|
+
<% Courrier::Email::Provider::PROVIDERS.each do |name, provider_class| -%>
|
|
27
|
+
<% provider_class.config_options.each do |key| -%>
|
|
28
|
+
# config.providers.<%= name %>.<%= key %> = ""
|
|
29
|
+
<% end -%>
|
|
30
|
+
<% end -%>
|
|
31
|
+
<% end -%>
|
|
14
32
|
|
|
15
33
|
|
|
16
34
|
# Set default sender details
|
|
17
|
-
config.from = email_with_name("support@example.com", "Example Support")
|
|
35
|
+
config.from = email_with_name("support@example.com", "Example Support") # => `Example Support <support@example.com>`
|
|
18
36
|
# config.reply_to = ""
|
|
19
37
|
# config.cc = ""
|
|
20
38
|
# config.bcc = ""
|
data/lib/rails_courrier.rb
CHANGED
data/rails_courrier.gemspec
CHANGED
|
@@ -18,9 +18,9 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
|
|
19
19
|
spec.files = Dir["{bin,app,config,lib}/**/*", "Rakefile", "README.md", "rails_courrier.gemspec", "Gemfile", "Gemfile.lock"]
|
|
20
20
|
|
|
21
|
-
spec.required_ruby_version = ">= 4.0
|
|
21
|
+
spec.required_ruby_version = ">= 3.4.0"
|
|
22
22
|
|
|
23
|
-
spec.add_dependency "courrier", "~> 0.
|
|
23
|
+
spec.add_dependency "courrier", "~> 1.0.0"
|
|
24
24
|
spec.add_dependency "launchy", ">= 3.1", "< 4"
|
|
25
25
|
spec.add_dependency "railties", ">= 7.0"
|
|
26
26
|
spec.add_dependency "actionpack", ">= 7.0"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_courrier
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rails Designer
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 1.0.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 1.0.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: launchy
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -111,16 +111,19 @@ files:
|
|
|
111
111
|
- Gemfile.lock
|
|
112
112
|
- README.md
|
|
113
113
|
- Rakefile
|
|
114
|
+
- app/assets/stylesheets/courrier/application.css
|
|
114
115
|
- app/controllers/courrier/previews/cleanups_controller.rb
|
|
115
116
|
- app/controllers/courrier/previews_controller.rb
|
|
117
|
+
- app/views/courrier/previews/default.html.erb
|
|
116
118
|
- app/views/courrier/previews/index.html.erb
|
|
119
|
+
- bin/rails
|
|
117
120
|
- bin/release
|
|
118
121
|
- bin/setup
|
|
119
122
|
- config/routes.rb
|
|
120
123
|
- lib/courrier/configuration/inbox.rb
|
|
121
124
|
- lib/courrier/email/delivery_job.rb
|
|
122
125
|
- lib/courrier/email/providers/inbox.rb
|
|
123
|
-
- lib/courrier/email/
|
|
126
|
+
- lib/courrier/email/translation.rb
|
|
124
127
|
- lib/courrier/engine.rb
|
|
125
128
|
- lib/courrier/railtie.rb
|
|
126
129
|
- lib/courrier/tasks/courrier.rake
|
|
@@ -146,14 +149,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
146
149
|
requirements:
|
|
147
150
|
- - ">="
|
|
148
151
|
- !ruby/object:Gem::Version
|
|
149
|
-
version: 4.0
|
|
152
|
+
version: 3.4.0
|
|
150
153
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
154
|
requirements:
|
|
152
155
|
- - ">="
|
|
153
156
|
- !ruby/object:Gem::Version
|
|
154
157
|
version: '0'
|
|
155
158
|
requirements: []
|
|
156
|
-
rubygems_version:
|
|
159
|
+
rubygems_version: 3.6.7
|
|
157
160
|
specification_version: 4
|
|
158
161
|
summary: Rails integration for Courrier email delivery
|
|
159
162
|
test_files: []
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
{
|
|
3
|
-
"to": "<%= email %>",
|
|
4
|
-
"subject": "<%= @options.subject %>"
|
|
5
|
-
}
|
|
6
|
-
-->
|
|
7
|
-
|
|
8
|
-
<!DOCTYPE html>
|
|
9
|
-
<html>
|
|
10
|
-
<head>
|
|
11
|
-
<meta charset="UTF-8">
|
|
12
|
-
<title><%= @options.subject %></title>
|
|
13
|
-
<style>
|
|
14
|
-
*, *::before, *::after { box-sizing: border-box; }
|
|
15
|
-
* { margin: 0; padding: 0; }
|
|
16
|
-
body { margin: 0; padding: 0; 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); }
|
|
17
|
-
pre { margin: 0; white-space: pre-wrap; }
|
|
18
|
-
.email {
|
|
19
|
-
width: 100%;
|
|
20
|
-
max-width: 65rem;
|
|
21
|
-
margin-left: auto; margin-right: auto;
|
|
22
|
-
background-color: #fff;
|
|
23
|
-
box-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
|
24
|
-
border-bottom-right-radius: 1rem; border-bottom-left-radius: 1rem;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.email__header { display: flex; align-items: center; padding: .75rem; -moz-column-gap: .75rem; column-gap: .75rem; }
|
|
28
|
-
|
|
29
|
-
.email__avatar {
|
|
30
|
-
flex-shrink: 0;
|
|
31
|
-
padding: .25rem;
|
|
32
|
-
width: 2rem; height: 2rem;
|
|
33
|
-
background-color: rgb(203 213 225);
|
|
34
|
-
color: rgb(51 65 85);
|
|
35
|
-
border-radius: 9999px;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.email__recipient { flex-grow: 1; font-size: .875rem; line-height: 1.25rem; }
|
|
39
|
-
|
|
40
|
-
.email__recipient-name { font-weight: 600; color: rgb(51 65 85); }
|
|
41
|
-
|
|
42
|
-
.email__metadata { display: flex; align-items: center; justify-content: space-between; }
|
|
43
|
-
|
|
44
|
-
.email__recipient-email { color: rgb(100 116 139); }
|
|
45
|
-
|
|
46
|
-
.email__datetime { font-size: .75rem; line-height: 1rem; color: rgb(148 163 184); }
|
|
47
|
-
|
|
48
|
-
.email__subject {
|
|
49
|
-
margin-left: 3.5rem;
|
|
50
|
-
font-size: 1.25rem; line-height: 1.75rem;
|
|
51
|
-
font-weight: 700;
|
|
52
|
-
letter-spacing: -.025em;
|
|
53
|
-
color: rgb(30 41 59)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.email__preview { margin-top: .75rem; margin-left: 3.5rem; padding-bottom: 1rem; }
|
|
57
|
-
|
|
58
|
-
.preview-toggle { display: none; }
|
|
59
|
-
.preview-toggle-label {
|
|
60
|
-
display: inline-block;
|
|
61
|
-
padding: .25rem .875rem;
|
|
62
|
-
font-size: .75rem; line-height: 1rem;
|
|
63
|
-
font-weight: 600;
|
|
64
|
-
color: rgb(30 41 59);
|
|
65
|
-
background-color: rgb(255 255 255);
|
|
66
|
-
border: 1px solid rgb(226 232 240);
|
|
67
|
-
border-radius: .375rem;
|
|
68
|
-
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
69
|
-
cursor: pointer;
|
|
70
|
-
|
|
71
|
-
&:hover { border-color: rgb(203 213 225); }
|
|
72
|
-
&:active { transform: scale(.98); }
|
|
73
|
-
}
|
|
74
|
-
.preview-toggle[disabled] + .preview-toggle-label { opacity: 0.5; cursor: not-allowed; }
|
|
75
|
-
|
|
76
|
-
.email__preview-html, .email__preview-text { margin-top: 1.5rem;}
|
|
77
|
-
.email__preview-html { display: none; }
|
|
78
|
-
.preview-toggle:checked ~ .email__preview-text { display: none; }
|
|
79
|
-
.preview-toggle:checked ~ .email__preview-html { display: block; }
|
|
80
|
-
</style>
|
|
81
|
-
</head>
|
|
82
|
-
|
|
83
|
-
<body>
|
|
84
|
-
<article class="email">
|
|
85
|
-
<header class="email__header">
|
|
86
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon" class="email__avatar">
|
|
87
|
-
<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"/>
|
|
88
|
-
</svg>
|
|
89
|
-
|
|
90
|
-
<div class="email__recipient">
|
|
91
|
-
<% if name %>
|
|
92
|
-
<p class="email__recipient-name">
|
|
93
|
-
<%= name %>
|
|
94
|
-
</p>
|
|
95
|
-
<% end %>
|
|
96
|
-
|
|
97
|
-
<div class="email__metadata">
|
|
98
|
-
<p class="email__recipient-email">
|
|
99
|
-
<%= email %>
|
|
100
|
-
</p>
|
|
101
|
-
|
|
102
|
-
<time class="email__datetime" datetime="#{Time.now.strftime("%Y-%m-%d %H:%M:%S %z")}">
|
|
103
|
-
<%= Time.now.strftime("%Y-%m-%d %H:%M:%S %z") %>
|
|
104
|
-
</time>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
</header>
|
|
108
|
-
|
|
109
|
-
<p class="email__subject">
|
|
110
|
-
<%= @options.subject %>
|
|
111
|
-
</p>
|
|
112
|
-
|
|
113
|
-
<div class="email__preview">
|
|
114
|
-
<input type="checkbox" id="preview-toggle" class="preview-toggle" <%= html ? "" : "disabled" %>>
|
|
115
|
-
<label for="preview-toggle" class="preview-toggle-label">Toggle HTML/Text</label>
|
|
116
|
-
|
|
117
|
-
<div class="email__preview-text">
|
|
118
|
-
<pre><%= text || "(no text content)" %></pre>
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
-
<div class="email__preview-html">
|
|
122
|
-
<%= html || "<p>(no HTML content)</p>" %>
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
</article>
|
|
126
|
-
</body>
|
|
127
|
-
</html>
|