actionmailbox 0.1.0 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/{LICENSE → MIT-LICENSE} +1 -1
- data/README.md +2 -267
- data/app/controllers/action_mailbox/base_controller.rb +23 -32
- data/app/controllers/action_mailbox/ingresses/mailgun/inbound_emails_controller.rb +88 -84
- data/app/controllers/action_mailbox/ingresses/mandrill/inbound_emails_controller.rb +64 -60
- data/app/controllers/action_mailbox/ingresses/postmark/inbound_emails_controller.rb +62 -0
- data/app/controllers/action_mailbox/ingresses/relay/inbound_emails_controller.rb +65 -0
- data/app/controllers/action_mailbox/ingresses/sendgrid/inbound_emails_controller.rb +51 -47
- data/app/controllers/rails/conductor/action_mailbox/inbound_emails_controller.rb +28 -20
- data/app/controllers/rails/conductor/action_mailbox/reroutes_controller.rb +15 -11
- data/app/controllers/rails/conductor/base_controller.rb +12 -8
- data/app/jobs/action_mailbox/incineration_job.rb +20 -13
- data/app/jobs/action_mailbox/routing_job.rb +10 -6
- data/app/models/action_mailbox/inbound_email.rb +43 -37
- data/app/models/action_mailbox/inbound_email/incineratable.rb +6 -4
- data/app/models/action_mailbox/inbound_email/incineratable/incineration.rb +21 -17
- data/app/models/action_mailbox/inbound_email/message_id.rb +22 -20
- data/app/models/action_mailbox/inbound_email/routable.rb +7 -5
- data/app/views/layouts/rails/conductor.html.erb +1 -0
- data/app/views/rails/conductor/action_mailbox/inbound_emails/new.html.erb +5 -0
- data/config/routes.rb +2 -2
- data/db/migrate/20180917164000_create_action_mailbox_tables.rb +6 -4
- data/lib/action_mailbox.rb +3 -1
- data/lib/action_mailbox/base.rb +100 -93
- data/lib/action_mailbox/callbacks.rb +3 -1
- data/lib/action_mailbox/engine.rb +12 -13
- data/lib/action_mailbox/gem_version.rb +17 -0
- data/lib/action_mailbox/mail_ext.rb +2 -0
- data/lib/action_mailbox/mail_ext/address_equality.rb +7 -3
- data/lib/action_mailbox/mail_ext/address_wrapping.rb +7 -3
- data/lib/action_mailbox/mail_ext/addresses.rb +22 -18
- data/lib/action_mailbox/mail_ext/from_source.rb +2 -0
- data/lib/action_mailbox/mail_ext/recipients.rb +7 -3
- data/lib/action_mailbox/{postfix_relayer.rb → relayer.rb} +18 -10
- data/lib/action_mailbox/router.rb +30 -26
- data/lib/action_mailbox/router/route.rb +34 -30
- data/lib/action_mailbox/routing.rb +3 -1
- data/lib/action_mailbox/test_case.rb +4 -0
- data/lib/action_mailbox/test_helper.rb +17 -11
- data/lib/action_mailbox/version.rb +8 -1
- data/lib/rails/generators/installer.rb +10 -0
- data/lib/rails/generators/mailbox/USAGE +12 -0
- data/lib/rails/generators/mailbox/mailbox_generator.rb +32 -0
- data/lib/{templates/mailboxes/application_mailbox.rb → rails/generators/mailbox/templates/application_mailbox.rb.tt} +1 -1
- data/lib/rails/generators/mailbox/templates/mailbox.rb.tt +4 -0
- data/lib/rails/generators/test_unit/mailbox_generator.rb +20 -0
- data/lib/rails/generators/test_unit/templates/mailbox_test.rb.tt +11 -0
- data/lib/tasks/ingress.rake +53 -5
- data/lib/tasks/install.rake +1 -1
- metadata +64 -236
- data/.gitignore +0 -2
- data/Gemfile +0 -8
- data/Gemfile.lock +0 -159
- data/Rakefile +0 -27
- data/actionmailbox.gemspec +0 -27
- data/app/controllers/action_mailbox/ingresses/amazon/inbound_emails_controller.rb +0 -50
- data/app/controllers/action_mailbox/ingresses/postfix/inbound_emails_controller.rb +0 -55
- data/bin/test +0 -5
- data/lib/templates/installer.rb +0 -4
- data/test/controllers/ingresses/amazon/inbound_emails_controller_test.rb +0 -20
- data/test/controllers/ingresses/mailgun/inbound_emails_controller_test.rb +0 -89
- data/test/controllers/ingresses/mandrill/inbound_emails_controller_test.rb +0 -58
- data/test/controllers/ingresses/postfix/inbound_emails_controller_test.rb +0 -54
- data/test/controllers/ingresses/sendgrid/inbound_emails_controller_test.rb +0 -44
- data/test/dummy/.babelrc +0 -18
- data/test/dummy/.gitignore +0 -3
- data/test/dummy/.postcssrc.yml +0 -3
- data/test/dummy/Rakefile +0 -6
- data/test/dummy/app/assets/config/manifest.js +0 -3
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/stylesheets/application.css +0 -15
- data/test/dummy/app/assets/stylesheets/scaffold.css +0 -80
- data/test/dummy/app/channels/application_cable/channel.rb +0 -4
- data/test/dummy/app/channels/application_cable/connection.rb +0 -4
- data/test/dummy/app/controllers/application_controller.rb +0 -2
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/javascript/packs/application.js +0 -0
- data/test/dummy/app/jobs/application_job.rb +0 -2
- data/test/dummy/app/mailboxes/application_mailbox.rb +0 -2
- data/test/dummy/app/mailboxes/messages_mailbox.rb +0 -4
- data/test/dummy/app/mailers/application_mailer.rb +0 -4
- data/test/dummy/app/models/application_record.rb +0 -3
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/app/views/layouts/mailer.html.erb +0 -13
- data/test/dummy/app/views/layouts/mailer.text.erb +0 -1
- data/test/dummy/bin/bundle +0 -3
- data/test/dummy/bin/rails +0 -4
- data/test/dummy/bin/rake +0 -4
- data/test/dummy/bin/setup +0 -36
- data/test/dummy/bin/update +0 -31
- data/test/dummy/bin/yarn +0 -11
- data/test/dummy/config.ru +0 -5
- data/test/dummy/config/application.rb +0 -19
- data/test/dummy/config/boot.rb +0 -5
- data/test/dummy/config/cable.yml +0 -10
- data/test/dummy/config/database.yml +0 -25
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -63
- data/test/dummy/config/environments/production.rb +0 -96
- data/test/dummy/config/environments/test.rb +0 -46
- data/test/dummy/config/initializers/application_controller_renderer.rb +0 -8
- data/test/dummy/config/initializers/assets.rb +0 -14
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/content_security_policy.rb +0 -22
- data/test/dummy/config/initializers/cookies_serializer.rb +0 -5
- data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
- data/test/dummy/config/initializers/inflections.rb +0 -16
- data/test/dummy/config/initializers/mime_types.rb +0 -4
- data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/test/dummy/config/locales/en.yml +0 -33
- data/test/dummy/config/puma.rb +0 -34
- data/test/dummy/config/routes.rb +0 -4
- data/test/dummy/config/spring.rb +0 -6
- data/test/dummy/config/storage.yml +0 -35
- data/test/dummy/config/webpack/development.js +0 -3
- data/test/dummy/config/webpack/environment.js +0 -3
- data/test/dummy/config/webpack/production.js +0 -3
- data/test/dummy/config/webpack/test.js +0 -3
- data/test/dummy/config/webpacker.yml +0 -65
- data/test/dummy/db/migrate/20180208205311_create_action_mailroom_tables.rb +0 -11
- data/test/dummy/db/migrate/20180212164506_create_active_storage_tables.active_storage.rb +0 -26
- data/test/dummy/db/schema.rb +0 -43
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/package.json +0 -11
- data/test/dummy/public/404.html +0 -67
- data/test/dummy/public/422.html +0 -67
- data/test/dummy/public/500.html +0 -66
- data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/test/dummy/public/apple-touch-icon.png +0 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/storage/.keep +0 -0
- data/test/dummy/yarn.lock +0 -6071
- data/test/fixtures/files/welcome.eml +0 -631
- data/test/jobs/incineration_job_test.rb +0 -17
- data/test/test_helper.rb +0 -54
- data/test/unit/inbound_email/incineration_test.rb +0 -45
- data/test/unit/inbound_email/message_id_test.rb +0 -13
- data/test/unit/inbound_email_test.rb +0 -13
- data/test/unit/mail_ext/address_equality_test.rb +0 -9
- data/test/unit/mail_ext/address_wrapping_test.rb +0 -11
- data/test/unit/mail_ext/recipients_test.rb +0 -33
- data/test/unit/mailbox/bouncing_test.rb +0 -29
- data/test/unit/mailbox/callbacks_test.rb +0 -75
- data/test/unit/mailbox/routing_test.rb +0 -30
- data/test/unit/mailbox/state_test.rb +0 -49
- data/test/unit/postfix_relayer_test.rb +0 -90
- data/test/unit/router_test.rb +0 -137
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d73a94090d155c671b5beee6e20ef185787acb97cfda037847566d969cfaac35
|
4
|
+
data.tar.gz: d8cf0465ec49fb877f65aeaa11b4247f1039644d361da05a9f496207b0fff7f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc61055abaa0d10c34c37a97dcab707a89a87a31e810033832b78aae03a2212b705d1bde9fcdb05a0ad5ccd822e0989ba6fbea748ebe839671dfdacbf8f2c368
|
7
|
+
data.tar.gz: e7a2831fc8273b23ca3daf1048607f32cefc2785ad238cecc74b79379a8b4ec2350d9b73d77f42cf8e26746e57eabe161f6b685a774697f517cda1cc2f53cb83
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
## Rails 6.0.0 (August 16, 2019) ##
|
2
|
+
|
3
|
+
* Fix Bcc header not being included with emails from `create_inbound_email_from` test helpers.
|
4
|
+
|
5
|
+
*jduff*
|
6
|
+
|
7
|
+
|
8
|
+
## Rails 6.0.0.rc2 (July 22, 2019) ##
|
9
|
+
|
10
|
+
* No changes.
|
11
|
+
|
12
|
+
|
13
|
+
## Rails 6.0.0.rc1 (April 24, 2019) ##
|
14
|
+
|
15
|
+
* No changes.
|
16
|
+
|
17
|
+
|
18
|
+
## Rails 6.0.0.beta3 (March 11, 2019) ##
|
19
|
+
|
20
|
+
* No changes.
|
21
|
+
|
22
|
+
|
23
|
+
## Rails 6.0.0.beta2 (February 25, 2019) ##
|
24
|
+
|
25
|
+
* Allow skipping incineration of processed emails.
|
26
|
+
|
27
|
+
This can be done by setting `config.action_mailbox.incinerate` to `false`.
|
28
|
+
|
29
|
+
*Pratik Naik*
|
30
|
+
|
31
|
+
|
32
|
+
## Rails 6.0.0.beta1 (January 18, 2019) ##
|
33
|
+
|
34
|
+
* Added to Rails.
|
35
|
+
|
36
|
+
*DHH*
|
data/{LICENSE → MIT-LICENSE}
RENAMED
data/README.md
CHANGED
@@ -1,277 +1,12 @@
|
|
1
1
|
# Action Mailbox
|
2
2
|
|
3
|
-
Action Mailbox routes incoming emails to controller-like mailboxes for processing in Rails. It ships with ingresses for
|
3
|
+
Action Mailbox routes incoming emails to controller-like mailboxes for processing in Rails. It ships with ingresses for Mailgun, Mandrill, Postmark, and SendGrid. You can also handle inbound mails directly via the built-in Exim, Postfix, and Qmail ingresses.
|
4
4
|
|
5
5
|
The inbound emails are turned into `InboundEmail` records using Active Record and feature lifecycle tracking, storage of the original email on cloud storage via Active Storage, and responsible data handling with on-by-default incineration.
|
6
6
|
|
7
7
|
These inbound emails are routed asynchronously using Active Job to one or several dedicated mailboxes, which are capable of interacting directly with the rest of your domain model.
|
8
8
|
|
9
|
-
|
10
|
-
## How does this compare to Action Mailer's inbound processing?
|
11
|
-
|
12
|
-
Rails has long had an anemic way of [receiving emails using Action Mailer](https://guides.rubyonrails.org/action_mailer_basics.html#receiving-emails), but it was poorly flushed out, lacked cohesion with the task of sending emails, and offered no help on integrating with popular inbound email processing platforms. Action Mailbox supersedes the receiving part of Action Mailer, which will be deprecated in due course.
|
13
|
-
|
14
|
-
|
15
|
-
## Installing
|
16
|
-
|
17
|
-
Assumes a Rails 5.2+ application:
|
18
|
-
|
19
|
-
1. Install the gem:
|
20
|
-
|
21
|
-
```ruby
|
22
|
-
# Gemfile
|
23
|
-
gem "actionmailbox", github: "rails/actionmailbox", require: "action_mailbox"
|
24
|
-
```
|
25
|
-
|
26
|
-
1. Install migrations needed for InboundEmail (and ensure Active Storage is setup)
|
27
|
-
|
28
|
-
```
|
29
|
-
./bin/rails action_mailbox:install
|
30
|
-
./bin/rails db:migrate
|
31
|
-
```
|
32
|
-
|
33
|
-
## Configuring
|
34
|
-
|
35
|
-
### Amazon SES
|
36
|
-
|
37
|
-
1. Install the [`aws-sdk-sns`](https://rubygems.org/gems/aws-sdk-sns) gem:
|
38
|
-
|
39
|
-
```ruby
|
40
|
-
# Gemfile
|
41
|
-
gem "aws-sdk-sns", ">= 1.9.0", require: false
|
42
|
-
```
|
43
|
-
|
44
|
-
2. Tell Action Mailbox to accept emails from SES:
|
45
|
-
|
46
|
-
```ruby
|
47
|
-
# config/environments/production.rb
|
48
|
-
config.action_mailbox.ingress = :amazon
|
49
|
-
```
|
50
|
-
|
51
|
-
3. [Configure SES][ses-docs] to deliver emails to your application via POST requests
|
52
|
-
to `/rails/action_mailbox/amazon/inbound_emails`. If your application lived at `https://example.com`, you would specify
|
53
|
-
the fully-qualified URL `https://example.com/rails/action_mailbox/amazon/inbound_emails`.
|
54
|
-
|
55
|
-
[ses-docs]: https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-notifications.html
|
56
|
-
|
57
|
-
### Mailgun
|
58
|
-
|
59
|
-
1. Give Action Mailbox your [Mailgun API key][mailgun-api-key] so it can authenticate requests to the Mailgun ingress.
|
60
|
-
|
61
|
-
Use `rails credentials:edit` to add your API key to your application's encrypted credentials under
|
62
|
-
`action_mailbox.mailgun_api_key`, where Action Mailbox will automatically find it:
|
63
|
-
|
64
|
-
```yaml
|
65
|
-
action_mailbox:
|
66
|
-
mailgun_api_key: ...
|
67
|
-
```
|
68
|
-
|
69
|
-
Alternatively, provide your API key in the `MAILGUN_INGRESS_API_KEY` environment variable.
|
70
|
-
|
71
|
-
2. Tell Action Mailbox to accept emails from Mailgun:
|
72
|
-
|
73
|
-
```ruby
|
74
|
-
# config/environments/production.rb
|
75
|
-
config.action_mailbox.ingress = :mailgun
|
76
|
-
```
|
77
|
-
|
78
|
-
3. [Configure Mailgun][mailgun-forwarding] to forward inbound emails to `/rails/action_mailbox/mailgun/inbound_emails/mime`.
|
79
|
-
If your application lived at `https://example.com`, you would specify the fully-qualified URL
|
80
|
-
`https://example.com/rails/action_mailbox/mailgun/inbound_emails/mime`.
|
81
|
-
|
82
|
-
[mailgun-api-key]: https://help.mailgun.com/hc/en-us/articles/203380100-Where-can-I-find-my-API-key-and-SMTP-credentials-
|
83
|
-
[mailgun-forwarding]: https://documentation.mailgun.com/en/latest/user_manual.html#receiving-forwarding-and-storing-messages
|
84
|
-
|
85
|
-
### Mandrill
|
86
|
-
|
87
|
-
1. Give Action Mailbox your Mandrill API key so it can authenticate requests to the Mandrill ingress.
|
88
|
-
|
89
|
-
Use `rails credentials:edit` to add your API key to your application's encrypted credentials under
|
90
|
-
`action_mailbox.mandrill_api_key`, where Action Mailbox will automatically find it:
|
91
|
-
|
92
|
-
```yaml
|
93
|
-
action_mailbox:
|
94
|
-
mandrill_api_key: ...
|
95
|
-
```
|
96
|
-
|
97
|
-
Alternatively, provide your API key in the `MANDRILL_INGRESS_API_KEY` environment variable.
|
98
|
-
|
99
|
-
2. Tell Action Mailbox to accept emails from Mandrill:
|
100
|
-
|
101
|
-
```ruby
|
102
|
-
# config/environments/production.rb
|
103
|
-
config.action_mailbox.ingress = :mandrill
|
104
|
-
```
|
105
|
-
|
106
|
-
3. [Configure Mandrill][mandrill-routing] to route inbound emails to `/rails/action_mailbox/mandrill/inbound_emails`.
|
107
|
-
If your application lived at `https://example.com`, you would specify the fully-qualified URL
|
108
|
-
`https://example.com/rails/action_mailbox/mandrill/inbound_emails`.
|
109
|
-
|
110
|
-
[mandrill-routing]: https://mandrill.zendesk.com/hc/en-us/articles/205583197-Inbound-Email-Processing-Overview
|
111
|
-
|
112
|
-
### Postfix
|
113
|
-
|
114
|
-
1. Tell Action Mailbox to accept emails from Postfix:
|
115
|
-
|
116
|
-
```ruby
|
117
|
-
# config/environments/production.rb
|
118
|
-
config.action_mailbox.ingress = :postfix
|
119
|
-
```
|
120
|
-
|
121
|
-
2. Generate a strong password that Action Mailbox can use to authenticate requests to the Postfix ingress.
|
122
|
-
|
123
|
-
Use `rails credentials:edit` to add the password to your application's encrypted credentials under
|
124
|
-
`action_mailbox.ingress_password`, where Action Mailbox will automatically find it:
|
125
|
-
|
126
|
-
```yaml
|
127
|
-
action_mailbox:
|
128
|
-
ingress_password: ...
|
129
|
-
```
|
130
|
-
|
131
|
-
Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` environment variable.
|
132
|
-
|
133
|
-
3. [Configure Postfix][postfix-config] to pipe inbound emails to `bin/rails action_mailbox:ingress:postfix`, providing
|
134
|
-
the `URL` of the Postfix ingress and the `INGRESS_PASSWORD` you previously generated. If your application lived at
|
135
|
-
`https://example.com`, the full command would look like this:
|
136
|
-
|
137
|
-
```
|
138
|
-
URL=https://example.com/rails/action_mailbox/postfix/inbound_emails INGRESS_PASSWORD=... bin/rails action_mailbox:ingress:postfix
|
139
|
-
```
|
140
|
-
|
141
|
-
[postfix-config]: https://serverfault.com/questions/258469/how-to-configure-postfix-to-pipe-all-incoming-email-to-a-script
|
142
|
-
|
143
|
-
### SendGrid
|
144
|
-
|
145
|
-
1. Tell Action Mailbox to accept emails from SendGrid:
|
146
|
-
|
147
|
-
```ruby
|
148
|
-
# config/environments/production.rb
|
149
|
-
config.action_mailbox.ingress = :sendgrid
|
150
|
-
```
|
151
|
-
|
152
|
-
2. Generate a strong password that Action Mailbox can use to authenticate requests to the SendGrid ingress.
|
153
|
-
|
154
|
-
Use `rails credentials:edit` to add the password to your application's encrypted credentials under
|
155
|
-
`action_mailbox.ingress_password`, where Action Mailbox will automatically find it:
|
156
|
-
|
157
|
-
```yaml
|
158
|
-
action_mailbox:
|
159
|
-
ingress_password: ...
|
160
|
-
```
|
161
|
-
|
162
|
-
Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` environment variable.
|
163
|
-
|
164
|
-
3. [Configure SendGrid Inbound Parse][sendgrid-config] to forward inbound emails to
|
165
|
-
`/rails/action_mailbox/sendgrid/inbound_emails` with the username `actionmailbox` and the password you previously
|
166
|
-
generated. If your application lived at `https://example.com`, you would configure SendGrid with the following URL:
|
167
|
-
|
168
|
-
```
|
169
|
-
https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/sendgrid/inbound_emails
|
170
|
-
```
|
171
|
-
|
172
|
-
**⚠️ Note:** When configuring your SendGrid Inbound Parse webhook, be sure to check the box labeled **“Post the raw,
|
173
|
-
full MIME message.”** Action Mailbox needs the raw MIME message to work.
|
174
|
-
|
175
|
-
[sendgrid-config]: https://sendgrid.com/docs/for-developers/parsing-email/setting-up-the-inbound-parse-webhook/
|
176
|
-
|
177
|
-
|
178
|
-
## Examples
|
179
|
-
|
180
|
-
Configure basic routing:
|
181
|
-
|
182
|
-
```ruby
|
183
|
-
# app/models/message.rb
|
184
|
-
class ApplicationMailbox < ActionMailbox::Base
|
185
|
-
routing /^save@/i => :forwards
|
186
|
-
routing /@replies\./i => :replies
|
187
|
-
end
|
188
|
-
```
|
189
|
-
|
190
|
-
Then setup a mailbox:
|
191
|
-
|
192
|
-
```ruby
|
193
|
-
# app/mailboxes/forwards_mailbox.rb
|
194
|
-
class ForwardsMailbox < ApplicationMailbox
|
195
|
-
# Callbacks specify prerequisites to processing
|
196
|
-
before_processing :require_forward
|
197
|
-
|
198
|
-
def process
|
199
|
-
if forwarder.buckets.one?
|
200
|
-
record_forward
|
201
|
-
else
|
202
|
-
stage_forward_and_request_more_details
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
private
|
207
|
-
def require_forward
|
208
|
-
unless message.forward?
|
209
|
-
# Use Action Mailers to bounce incoming emails back to sender – this halts processing
|
210
|
-
bounce_with Forwards::BounceMailer.missing_forward(
|
211
|
-
inbound_email, forwarder: forwarder
|
212
|
-
)
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
def forwarder
|
217
|
-
@forwarder ||= Person.where(email_address: mail.from)
|
218
|
-
end
|
219
|
-
|
220
|
-
def record_forward
|
221
|
-
forwarder.buckets.first.record \
|
222
|
-
Forward.new forwarder: forwarder, subject: message.subject, content: mail.content
|
223
|
-
end
|
224
|
-
|
225
|
-
def stage_forward_and_request_more_details
|
226
|
-
Forwards::RoutingMailer.choose_project(mail).deliver_now
|
227
|
-
end
|
228
|
-
end
|
229
|
-
```
|
230
|
-
|
231
|
-
## Incineration of InboundEmails
|
232
|
-
|
233
|
-
By default, an InboundEmail that has been successfully processed will be incinerated after 30 days. This ensures you're not holding on to people's data willy-nilly after they may have canceled their accounts or deleted their content. The intention is that after you've processed an email, you should have extracted all the data you needed and turned it into domain models and content on your side of the application. The InboundEmail simply stays in the system for the extra time to provide debugging and forensics options.
|
234
|
-
|
235
|
-
The actual incineration is done via the `IncinerationJob` that's scheduled to run after `config.action_mailbox.incinerate_after` time. This value is by default set to `30.days`, but you can change it in your production.rb configuration. (Note that this far-future incineration scheduling relies on your job queue being able to hold jobs for that long.)
|
236
|
-
|
237
|
-
|
238
|
-
## Working with Action Mailbox in development
|
239
|
-
|
240
|
-
It's helpful to be able to test incoming emails in development without actually sending and receiving real emails. To accomplish this, there's a conductor controller mounted at `/rails/conductor/action_mailbox/inbound_emails`, which gives you an index of all the InboundEmails in the system, their state of processing, and a form to create a new InboundEmail as well.
|
241
|
-
|
242
|
-
|
243
|
-
## Testing mailboxes
|
244
|
-
|
245
|
-
Example:
|
246
|
-
|
247
|
-
```ruby
|
248
|
-
class ForwardsMailboxTest < ActionMailbox::TestCase
|
249
|
-
test "directly recording a client forward for a forwarder and forwardee corresponding to one project" do
|
250
|
-
assert_difference -> { people(:david).buckets.first.recordings.count } do
|
251
|
-
receive_inbound_email_from_mail \
|
252
|
-
to: 'save@example.com',
|
253
|
-
from: people(:david).email_address,
|
254
|
-
subject: "Fwd: Status update?",
|
255
|
-
body: <<~BODY
|
256
|
-
--- Begin forwarded message ---
|
257
|
-
From: Frank Holland <frank@microsoft.com>
|
258
|
-
|
259
|
-
What's the status?
|
260
|
-
BODY
|
261
|
-
end
|
262
|
-
|
263
|
-
recording = people(:david).buckets.first.recordings.last
|
264
|
-
assert_equal people(:david), recording.creator
|
265
|
-
assert_equal "Status update?", recording.forward.subject
|
266
|
-
assert_match "What's the status?", recording.forward.content.to_s
|
267
|
-
end
|
268
|
-
end
|
269
|
-
```
|
270
|
-
|
271
|
-
|
272
|
-
## Development road map
|
273
|
-
|
274
|
-
Action Mailbox is destined for inclusion in Rails 6, which is due to be released some time in 2019. We will refine the framework in this separate rails/actionmailbox repository until we're ready to promote it via a pull request to rails/rails.
|
9
|
+
You can read more about Action Mailbox in the [Action Mailbox Basics](https://edgeguides.rubyonrails.org/action_mailbox_basics.html) guide.
|
275
10
|
|
276
11
|
## License
|
277
12
|
|
@@ -1,43 +1,34 @@
|
|
1
|
-
#
|
2
|
-
class ActionMailbox::BaseController < ActionController::Base
|
3
|
-
skip_forgery_protection
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module ActionMailbox
|
4
|
+
# The base class for all Action Mailbox ingress controllers.
|
5
|
+
class BaseController < ActionController::Base
|
6
|
+
skip_forgery_protection if default_protect_from_forgery
|
8
7
|
|
9
|
-
|
8
|
+
before_action :ensure_configured
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
private
|
11
|
+
def ensure_configured
|
12
|
+
unless ActionMailbox.ingress == ingress_name
|
13
|
+
head :not_found
|
14
|
+
end
|
15
15
|
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def ingress_name
|
19
|
-
self.class.name.remove(/\AActionMailbox::Ingresses::/, /::InboundEmailsController\z/).underscore.to_sym
|
20
|
-
end
|
21
|
-
|
22
16
|
|
23
|
-
|
24
|
-
|
25
|
-
http_basic_authenticate_or_request_with username: "actionmailbox", password: password, realm: "Action Mailbox"
|
26
|
-
else
|
27
|
-
raise ArgumentError, "Missing required ingress credentials"
|
17
|
+
def ingress_name
|
18
|
+
self.class.name.remove(/\AActionMailbox::Ingresses::/, /::InboundEmailsController\z/).underscore.to_sym
|
28
19
|
end
|
29
|
-
end
|
30
20
|
|
31
|
-
def password
|
32
|
-
Rails.application.credentials.dig(:action_mailbox, :ingress_password) || ENV["RAILS_INBOUND_EMAIL_PASSWORD"]
|
33
|
-
end
|
34
21
|
|
22
|
+
def authenticate_by_password
|
23
|
+
if password.present?
|
24
|
+
http_basic_authenticate_or_request_with name: "actionmailbox", password: password, realm: "Action Mailbox"
|
25
|
+
else
|
26
|
+
raise ArgumentError, "Missing required ingress credentials"
|
27
|
+
end
|
28
|
+
end
|
35
29
|
|
36
|
-
|
37
|
-
|
38
|
-
authenticate_or_request_with_http_basic(realm || "Application") do |given_username, given_password|
|
39
|
-
ActiveSupport::SecurityUtils.secure_compare(given_username, username) &
|
40
|
-
ActiveSupport::SecurityUtils.secure_compare(given_password, password)
|
30
|
+
def password
|
31
|
+
Rails.application.credentials.dig(:action_mailbox, :ingress_password) || ENV["RAILS_INBOUND_EMAIL_PASSWORD"]
|
41
32
|
end
|
42
|
-
|
33
|
+
end
|
43
34
|
end
|
@@ -1,99 +1,103 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# - +body-mime+: The full RFC 822 message
|
4
|
-
# - +timestamp+: The current time according to Mailgun as the number of seconds passed since the UNIX epoch
|
5
|
-
# - +token+: A randomly-generated, 50-character string
|
6
|
-
# - +signature+: A hexadecimal HMAC-SHA256 of the timestamp concatenated with the token, generated using the Mailgun API key
|
7
|
-
#
|
8
|
-
# Authenticates requests by validating their signatures.
|
9
|
-
#
|
10
|
-
# Returns:
|
11
|
-
#
|
12
|
-
# - <tt>204 No Content</tt> if an inbound email is successfully recorded and enqueued for routing to the appropriate mailbox
|
13
|
-
# - <tt>401 Unauthorized</tt> if the request's signature could not be validated, or if its timestamp is more than 2 minutes old
|
14
|
-
# - <tt>404 Not Found</tt> if Action Mailbox is not configured to accept inbound emails from Mailgun
|
15
|
-
# - <tt>422 Unprocessable Entity</tt> if the request is missing required parameters
|
16
|
-
# - <tt>500 Server Error</tt> if the Mailgun API key is missing, or one of the Active Record database,
|
17
|
-
# the Active Storage service, or the Active Job backend is misconfigured or unavailable
|
18
|
-
#
|
19
|
-
# == Usage
|
20
|
-
#
|
21
|
-
# 1. Give Action Mailbox your {Mailgun API key}[https://help.mailgun.com/hc/en-us/articles/203380100-Where-can-I-find-my-API-key-and-SMTP-credentials-]
|
22
|
-
# so it can authenticate requests to the Mailgun ingress.
|
23
|
-
#
|
24
|
-
# Use <tt>rails credentials:edit</tt> to add your API key to your application's encrypted credentials under
|
25
|
-
# +action_mailbox.mailgun_api_key+, where Action Mailbox will automatically find it:
|
26
|
-
#
|
27
|
-
# action_mailbox:
|
28
|
-
# mailgun_api_key: ...
|
29
|
-
#
|
30
|
-
# Alternatively, provide your API key in the +MAILGUN_INGRESS_API_KEY+ environment variable.
|
31
|
-
#
|
32
|
-
# 2. Tell Action Mailbox to accept emails from Mailgun:
|
33
|
-
#
|
34
|
-
# # config/environments/production.rb
|
35
|
-
# config.action_mailbox.ingress = :mailgun
|
36
|
-
#
|
37
|
-
# 3. {Configure Mailgun}[https://documentation.mailgun.com/en/latest/user_manual.html#receiving-forwarding-and-storing-messages]
|
38
|
-
# to forward inbound emails to `/rails/action_mailbox/mailgun/inbound_emails/mime`.
|
39
|
-
#
|
40
|
-
# If your application lived at <tt>https://example.com</tt>, you would specify the fully-qualified URL
|
41
|
-
# <tt>https://example.com/rails/action_mailbox/mailgun/inbound_emails/mime</tt>.
|
42
|
-
class ActionMailbox::Ingresses::Mailgun::InboundEmailsController < ActionMailbox::BaseController
|
43
|
-
before_action :authenticate
|
1
|
+
# frozen_string_literal: true
|
44
2
|
|
45
|
-
|
46
|
-
|
47
|
-
|
3
|
+
module ActionMailbox
|
4
|
+
# Ingests inbound emails from Mailgun. Requires the following parameters:
|
5
|
+
#
|
6
|
+
# - +body-mime+: The full RFC 822 message
|
7
|
+
# - +timestamp+: The current time according to Mailgun as the number of seconds passed since the UNIX epoch
|
8
|
+
# - +token+: A randomly-generated, 50-character string
|
9
|
+
# - +signature+: A hexadecimal HMAC-SHA256 of the timestamp concatenated with the token, generated using the Mailgun API key
|
10
|
+
#
|
11
|
+
# Authenticates requests by validating their signatures.
|
12
|
+
#
|
13
|
+
# Returns:
|
14
|
+
#
|
15
|
+
# - <tt>204 No Content</tt> if an inbound email is successfully recorded and enqueued for routing to the appropriate mailbox
|
16
|
+
# - <tt>401 Unauthorized</tt> if the request's signature could not be validated, or if its timestamp is more than 2 minutes old
|
17
|
+
# - <tt>404 Not Found</tt> if Action Mailbox is not configured to accept inbound emails from Mailgun
|
18
|
+
# - <tt>422 Unprocessable Entity</tt> if the request is missing required parameters
|
19
|
+
# - <tt>500 Server Error</tt> if the Mailgun API key is missing, or one of the Active Record database,
|
20
|
+
# the Active Storage service, or the Active Job backend is misconfigured or unavailable
|
21
|
+
#
|
22
|
+
# == Usage
|
23
|
+
#
|
24
|
+
# 1. Give Action Mailbox your {Mailgun API key}[https://help.mailgun.com/hc/en-us/articles/203380100-Where-can-I-find-my-API-key-and-SMTP-credentials-]
|
25
|
+
# so it can authenticate requests to the Mailgun ingress.
|
26
|
+
#
|
27
|
+
# Use <tt>rails credentials:edit</tt> to add your API key to your application's encrypted credentials under
|
28
|
+
# +action_mailbox.mailgun_api_key+, where Action Mailbox will automatically find it:
|
29
|
+
#
|
30
|
+
# action_mailbox:
|
31
|
+
# mailgun_api_key: ...
|
32
|
+
#
|
33
|
+
# Alternatively, provide your API key in the +MAILGUN_INGRESS_API_KEY+ environment variable.
|
34
|
+
#
|
35
|
+
# 2. Tell Action Mailbox to accept emails from Mailgun:
|
36
|
+
#
|
37
|
+
# # config/environments/production.rb
|
38
|
+
# config.action_mailbox.ingress = :mailgun
|
39
|
+
#
|
40
|
+
# 3. {Configure Mailgun}[https://documentation.mailgun.com/en/latest/user_manual.html#receiving-forwarding-and-storing-messages]
|
41
|
+
# to forward inbound emails to +/rails/action_mailbox/mailgun/inbound_emails/mime+.
|
42
|
+
#
|
43
|
+
# If your application lived at <tt>https://example.com</tt>, you would specify the fully-qualified URL
|
44
|
+
# <tt>https://example.com/rails/action_mailbox/mailgun/inbound_emails/mime</tt>.
|
45
|
+
class Ingresses::Mailgun::InboundEmailsController < ActionMailbox::BaseController
|
46
|
+
before_action :authenticate
|
48
47
|
|
49
|
-
|
50
|
-
|
51
|
-
head :unauthorized unless authenticated?
|
48
|
+
def create
|
49
|
+
ActionMailbox::InboundEmail.create_and_extract_message_id! params.require("body-mime")
|
52
50
|
end
|
53
51
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
key: key,
|
58
|
-
timestamp: params.require(:timestamp),
|
59
|
-
token: params.require(:token),
|
60
|
-
signature: params.require(:signature)
|
61
|
-
).authenticated?
|
62
|
-
else
|
63
|
-
raise ArgumentError, <<~MESSAGE.squish
|
64
|
-
Missing required Mailgun API key. Set action_mailbox.mailgun_api_key in your application's
|
65
|
-
encrypted credentials or provide the MAILGUN_INGRESS_API_KEY environment variable.
|
66
|
-
MESSAGE
|
52
|
+
private
|
53
|
+
def authenticate
|
54
|
+
head :unauthorized unless authenticated?
|
67
55
|
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def key
|
71
|
-
Rails.application.credentials.dig(:action_mailbox, :mailgun_api_key) || ENV["MAILGUN_INGRESS_API_KEY"]
|
72
|
-
end
|
73
56
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
57
|
+
def authenticated?
|
58
|
+
if key.present?
|
59
|
+
Authenticator.new(
|
60
|
+
key: key,
|
61
|
+
timestamp: params.require(:timestamp),
|
62
|
+
token: params.require(:token),
|
63
|
+
signature: params.require(:signature)
|
64
|
+
).authenticated?
|
65
|
+
else
|
66
|
+
raise ArgumentError, <<~MESSAGE.squish
|
67
|
+
Missing required Mailgun API key. Set action_mailbox.mailgun_api_key in your application's
|
68
|
+
encrypted credentials or provide the MAILGUN_INGRESS_API_KEY environment variable.
|
69
|
+
MESSAGE
|
70
|
+
end
|
79
71
|
end
|
80
72
|
|
81
|
-
def
|
82
|
-
|
73
|
+
def key
|
74
|
+
Rails.application.credentials.dig(:action_mailbox, :mailgun_api_key) || ENV["MAILGUN_INGRESS_API_KEY"]
|
83
75
|
end
|
84
76
|
|
85
|
-
|
86
|
-
|
87
|
-
ActiveSupport::SecurityUtils.secure_compare signature, expected_signature
|
88
|
-
end
|
77
|
+
class Authenticator
|
78
|
+
attr_reader :key, :timestamp, :token, :signature
|
89
79
|
|
90
|
-
|
91
|
-
|
92
|
-
Time.at(timestamp) >= 2.minutes.ago
|
80
|
+
def initialize(key:, timestamp:, token:, signature:)
|
81
|
+
@key, @timestamp, @token, @signature = key, Integer(timestamp), token, signature
|
93
82
|
end
|
94
83
|
|
95
|
-
def
|
96
|
-
|
84
|
+
def authenticated?
|
85
|
+
signed? && recent?
|
97
86
|
end
|
98
|
-
|
87
|
+
|
88
|
+
private
|
89
|
+
def signed?
|
90
|
+
ActiveSupport::SecurityUtils.secure_compare signature, expected_signature
|
91
|
+
end
|
92
|
+
|
93
|
+
# Allow for 2 minutes of drift between Mailgun time and local server time.
|
94
|
+
def recent?
|
95
|
+
Time.at(timestamp) >= 2.minutes.ago
|
96
|
+
end
|
97
|
+
|
98
|
+
def expected_signature
|
99
|
+
OpenSSL::HMAC.hexdigest OpenSSL::Digest::SHA256.new, key, "#{timestamp}#{token}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
99
103
|
end
|