telex 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +101 -0
- data/Rakefile +6 -0
- data/app/controllers/action_mailbox/ingresses/lettermint/inbound_emails_controller.rb +51 -0
- data/app/jobs/telex/ingest_job.rb +34 -0
- data/config/routes.rb +5 -0
- data/lib/telex/download.rb +41 -0
- data/lib/telex/engine.rb +12 -0
- data/lib/telex/signature.rb +31 -0
- data/lib/telex/test_helper.rb +24 -0
- data/lib/telex/version.rb +3 -0
- data/lib/telex.rb +35 -0
- metadata +67 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 21555637ece74cdb455b24dddc9dcfd7193b8ec0a2cad21d0da1052a583d778c
|
|
4
|
+
data.tar.gz: e0f91a8b8acfa3f81db1b51bf13cc2bf8af522cc83106676b8c52563ae30b2f2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 852c7dd0cbe3fc1011a99bbf0a98dc54c398390f66717c0a08143626e6c798aa41866d0b11e86618652f18ef26d7d59ade28a4b17df5591f4ad92fb37b396e1a
|
|
7
|
+
data.tar.gz: c563df784e2e03f8ea8521d3566d4f677967bac6ee762f7e9d33497f6825faedc5e9c19a22c56c987fb765110c338a2664e7f61f6defab22975d9c7b6f17766a
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright B.O.X
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Telex
|
|
2
|
+
|
|
3
|
+
Email for our Rails apps through [Lettermint](https://lettermint.co), a Dutch provider with servers in the EU.
|
|
4
|
+
|
|
5
|
+
- **Sending:** a `:lettermint` delivery method for Action Mailer, over Lettermint's SMTP relay. Mailers, previews, `deliver_later` and gems that send mail (Devise…) work as usual.
|
|
6
|
+
- **Receiving:** a Lettermint ingress for Action Mailbox, next to the ones Rails ships for Postmark or Mailgun. Lettermint posts a signed webhook with a link to the original message; Telex checks the signature, downloads the message in a job and hands it to Action Mailbox, which routes it to the app's mailboxes.
|
|
7
|
+
|
|
8
|
+
Each app has its own Lettermint project: its own API token, logs, webhook and receiving subdomain (`reply.hq.box.paris`, `reply.ticket.box.paris`…).
|
|
9
|
+
|
|
10
|
+
## Install in an app
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
# Gemfile
|
|
14
|
+
gem "telex", github: "boxprod/telex"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
# bin/rails credentials:edit
|
|
19
|
+
lettermint:
|
|
20
|
+
api_token: lm_... # the project's API token, also the SMTP password
|
|
21
|
+
webhook_secret: whsec_... # the inbound route's webhook secret, whsec_ included
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or `LETTERMINT_API_TOKEN` and `LETTERMINT_WEBHOOK_SECRET` in the environment.
|
|
25
|
+
|
|
26
|
+
### Sending
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
# config/environments/production.rb
|
|
30
|
+
config.action_mailer.delivery_method = :lettermint
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The sending domain must be verified in the Lettermint project (SPF, DKIM, return path). Lettermint's [SMTP headers](https://lettermint.co/docs/guides/send-email-with-smtp) (`X-Lettermint-Route`, `X-LM-Tag`, `X-LM-Metadata-*`…) can be set in a mailer with `headers[...]`.
|
|
34
|
+
|
|
35
|
+
### Receiving
|
|
36
|
+
|
|
37
|
+
Install Action Mailbox if the app does not have it yet (it needs Active Storage):
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
bin/rails action_mailbox:install db:migrate
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
# config/environments/production.rb
|
|
45
|
+
config.action_mailbox.ingress = :lettermint
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
In the Lettermint project, add an inbound route on the receiving subdomain (its MX records point to Lettermint) with the webhook
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
https://<the app>/rails/action_mailbox/lettermint/inbound_emails
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then route messages in `app/mailboxes/application_mailbox.rb` as with any Action Mailbox app. Inbound needs Lettermint's Starter plan or above.
|
|
55
|
+
|
|
56
|
+
What Telex adds to each message before Action Mailbox sees it:
|
|
57
|
+
|
|
58
|
+
| Header | |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `X-Original-To` | The address it was delivered to, which Action Mailbox routes on even when it is not in To or Cc |
|
|
61
|
+
| `X-Lettermint-Spam` | `yes` or `no`, Lettermint's verdict; nothing is dropped |
|
|
62
|
+
| `X-Lettermint-Spam-Score` | Lettermint's score |
|
|
63
|
+
|
|
64
|
+
### How it behaves
|
|
65
|
+
|
|
66
|
+
- A wrong or missing signature, or one more than 5 minutes old, gets a `401`. Other events (deliveries, bounces) get a `204` and are ignored for now.
|
|
67
|
+
- The webhook answers once the download is queued. If the app cannot queue it, Lettermint gets a `500` and tries again.
|
|
68
|
+
- The job tries again for several hours when Lettermint cannot be reached, and gives up at once if the link has expired (28 days after the message arrived).
|
|
69
|
+
- A message that arrives twice is kept once: Action Mailbox ignores a source it already has.
|
|
70
|
+
- The links are secrets: the webhook's `data` is filtered from the request log and the job does not log its arguments.
|
|
71
|
+
|
|
72
|
+
## Testing in an app
|
|
73
|
+
|
|
74
|
+
To test a mailbox, Action Mailbox's own `receive_inbound_email_from_mail` is the simplest. To test the webhook end to end:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
require "telex/test_helper"
|
|
78
|
+
|
|
79
|
+
class InboundTest < ActionDispatch::IntegrationTest
|
|
80
|
+
include Telex::TestHelper
|
|
81
|
+
|
|
82
|
+
test "a reply reaches its quote" do
|
|
83
|
+
body = lettermint_inbound_payload(recipient: "reply+quote-12@reply.hq.box.paris")
|
|
84
|
+
post rails_lettermint_inbound_emails_path, params: body, headers: lettermint_webhook_headers(body)
|
|
85
|
+
# Telex.download = an object answering #fetch(url) with the message's source, to run the job without the network
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Not done yet
|
|
91
|
+
|
|
92
|
+
- Delivery events (bounces, complaints) are ignored. They would come through the same webhook.
|
|
93
|
+
- Whether Lettermint keeps the Message-ID Rails generates, which replies point to in `In-Reply-To`: Lettermint has an `X-LM-Preserve-Message-ID` header for it, to check against a real reply before relying on threading.
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
bundle install
|
|
99
|
+
bin/rails db:migrate
|
|
100
|
+
bin/rails test
|
|
101
|
+
```
|
data/Rakefile
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module ActionMailbox
|
|
2
|
+
# Ingests inbound emails from Lettermint, whose webhooks carry a signed link to the original
|
|
3
|
+
# message rather than the message itself. The link is fetched by Telex::IngestJob.
|
|
4
|
+
#
|
|
5
|
+
# Authenticates requests by their X-Lettermint-Signature header.
|
|
6
|
+
#
|
|
7
|
+
# Returns:
|
|
8
|
+
#
|
|
9
|
+
# - <tt>204 No Content</tt> once the message is queued for download, and for every other event
|
|
10
|
+
# - <tt>401 Unauthorized</tt> if the signature is wrong or more than 5 minutes old
|
|
11
|
+
# - <tt>404 Not Found</tt> if Action Mailbox is not configured to accept inbound emails from Lettermint
|
|
12
|
+
# - <tt>422 Unprocessable Entity</tt> if an inbound event has no link to the message
|
|
13
|
+
# - <tt>500 Server Error</tt> if the webhook secret is missing, or the Active Job backend is unavailable
|
|
14
|
+
class Ingresses::Lettermint::InboundEmailsController < ActionMailbox::BaseController
|
|
15
|
+
before_action :authenticate
|
|
16
|
+
|
|
17
|
+
def create
|
|
18
|
+
if payload["event"] == "message.inbound"
|
|
19
|
+
data = payload.fetch("data", {})
|
|
20
|
+
url = data.dig("raw", "url")
|
|
21
|
+
return head :unprocessable_entity if url.blank?
|
|
22
|
+
|
|
23
|
+
Telex::IngestJob.perform_later(url, recipient: data["recipient"], spam_score: data["spam_score"], spam: data["is_spam"])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
head :no_content
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
# The payload holds the message, and links Lettermint asks to keep secret: none of it goes to the logs.
|
|
31
|
+
def process_action(...)
|
|
32
|
+
request.set_header "action_dispatch.parameter_filter", Rails.application.config.filter_parameters + [ /\Adata\z/ ]
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def payload
|
|
37
|
+
@payload ||= JSON.parse(request.raw_post)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def authenticate
|
|
41
|
+
head :unauthorized unless Telex::Signature.new(request.headers["X-Lettermint-Signature"], secret: secret).valid?(request.raw_post)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def secret
|
|
45
|
+
Telex.webhook_secret.presence || raise(ArgumentError, <<~MESSAGE.squish)
|
|
46
|
+
Missing the Lettermint webhook secret. Set lettermint.webhook_secret in your application's
|
|
47
|
+
encrypted credentials or provide the LETTERMINT_WEBHOOK_SECRET environment variable.
|
|
48
|
+
MESSAGE
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Telex
|
|
2
|
+
# Downloads an inbound message and hands it to Action Mailbox. Lettermint retries webhooks,
|
|
3
|
+
# and Action Mailbox ignores a message it already has, so running twice is harmless.
|
|
4
|
+
#
|
|
5
|
+
# Not the host's ApplicationJob: none of its callbacks or retries apply here.
|
|
6
|
+
class IngestJob < ActiveJob::Base
|
|
7
|
+
queue_as :default
|
|
8
|
+
|
|
9
|
+
# The first argument is the signed link to the message.
|
|
10
|
+
self.log_arguments = false
|
|
11
|
+
|
|
12
|
+
retry_on Download::Error, wait: :polynomially_longer, attempts: 10
|
|
13
|
+
|
|
14
|
+
discard_on Download::Gone do |job, error|
|
|
15
|
+
Rails.logger.error "[Telex] Inbound email dropped: #{error.message} (job #{job.job_id})"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def perform(url, recipient: nil, spam_score: nil, spam: nil)
|
|
19
|
+
source = Telex.download.fetch(url)
|
|
20
|
+
ActionMailbox::InboundEmail.create_and_extract_message_id! headers(recipient, spam_score, spam) + source
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
# The address the message was delivered to, which may not be in its To or Cc (a Bcc, a list),
|
|
25
|
+
# is what Action Mailbox routes on. Lettermint's spam verdict comes along for mailboxes to use.
|
|
26
|
+
def headers(recipient, spam_score, spam)
|
|
27
|
+
lines = []
|
|
28
|
+
lines << "X-Original-To: #{recipient}" if recipient.present?
|
|
29
|
+
lines << "X-Lettermint-Spam: #{spam ? "yes" : "no"}" unless spam.nil?
|
|
30
|
+
lines << "X-Lettermint-Spam-Score: #{spam_score}" unless spam_score.nil?
|
|
31
|
+
lines.map { |line| "#{line}\r\n" }.join.b
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
|
|
3
|
+
module Telex
|
|
4
|
+
# Fetches a message's original source from the signed URL in the webhook. The URL needs no
|
|
5
|
+
# credentials, so it is a secret until it expires, 28 days after the message arrived.
|
|
6
|
+
class Download
|
|
7
|
+
# Worth trying again later.
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
|
|
10
|
+
# The link has expired or was never valid: trying again will not help.
|
|
11
|
+
class Gone < Error; end
|
|
12
|
+
|
|
13
|
+
MAX_REDIRECTS = 3
|
|
14
|
+
|
|
15
|
+
def fetch(url, redirects: MAX_REDIRECTS)
|
|
16
|
+
uri = URI(url)
|
|
17
|
+
raise Gone, "Lettermint gave an address that is not HTTPS" unless uri.is_a?(URI::HTTPS)
|
|
18
|
+
|
|
19
|
+
case response = perform(uri)
|
|
20
|
+
when Net::HTTPSuccess
|
|
21
|
+
response.body.b
|
|
22
|
+
when Net::HTTPRedirection
|
|
23
|
+
raise Error, "Lettermint redirected too many times" if redirects.zero?
|
|
24
|
+
fetch(URI.join(uri, response["location"]).to_s, redirects: redirects - 1)
|
|
25
|
+
when Net::HTTPForbidden, Net::HTTPNotFound, Net::HTTPGone
|
|
26
|
+
raise Gone, "Lettermint answered #{response.code} for the message's source"
|
|
27
|
+
else
|
|
28
|
+
raise Error, "Lettermint answered #{response.code} for the message's source"
|
|
29
|
+
end
|
|
30
|
+
rescue Timeout::Error, SystemCallError, SocketError, OpenSSL::SSL::SSLError, Net::HTTPBadResponse => error
|
|
31
|
+
raise Error, "Lettermint could not be reached (#{error.class})"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
def perform(uri)
|
|
36
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 10, read_timeout: 60) do |http|
|
|
37
|
+
http.request(Net::HTTP::Get.new(uri))
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/telex/engine.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Telex
|
|
2
|
+
# Not isolated: the ingress lives in Action Mailbox's namespace and its route in the app's,
|
|
3
|
+
# next to the ones Rails ships for other providers.
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
# config.action_mailer.delivery_method = :lettermint
|
|
6
|
+
initializer "telex.delivery_method" do
|
|
7
|
+
ActiveSupport.on_load(:action_mailer) do
|
|
8
|
+
add_delivery_method :lettermint, Mail::SMTP, Telex::SMTP_SETTINGS.merge(password: Telex.api_token)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "openssl"
|
|
2
|
+
|
|
3
|
+
module Telex
|
|
4
|
+
# The X-Lettermint-Signature header: "t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">",
|
|
5
|
+
# keyed with the webhook secret as given, whsec_ prefix included.
|
|
6
|
+
class Signature
|
|
7
|
+
TOLERANCE = 5.minutes
|
|
8
|
+
|
|
9
|
+
def self.generate(body, secret:, at: Time.now)
|
|
10
|
+
timestamp = at.to_i
|
|
11
|
+
"t=#{timestamp},v1=#{digest(timestamp, body, secret)}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.digest(timestamp, body, secret)
|
|
15
|
+
OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{body}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(header, secret:)
|
|
19
|
+
@parts = header.to_s.split(",").to_h { |part| part.split("=", 2).map(&:strip) }
|
|
20
|
+
@secret = secret
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def valid?(body, now: Time.now)
|
|
24
|
+
timestamp = Integer(@parts["t"], exception: false)
|
|
25
|
+
return false unless timestamp && @parts["v1"]
|
|
26
|
+
|
|
27
|
+
(now.to_i - timestamp).abs <= TOLERANCE &&
|
|
28
|
+
ActiveSupport::SecurityUtils.secure_compare(@parts["v1"], self.class.digest(timestamp, body, @secret))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Telex
|
|
2
|
+
# Signs requests the way Lettermint does, for integration tests of the inbound webhook.
|
|
3
|
+
# To test a mailbox, Action Mailbox's own receive_inbound_email_from_mail is simpler.
|
|
4
|
+
module TestHelper
|
|
5
|
+
def lettermint_webhook_headers(body, secret: Telex.webhook_secret, event: JSON.parse(body)["event"], at: Time.now)
|
|
6
|
+
{
|
|
7
|
+
"Content-Type" => "application/json",
|
|
8
|
+
"X-Lettermint-Signature" => Telex::Signature.generate(body, secret: secret, at: at),
|
|
9
|
+
"X-Lettermint-Event" => event,
|
|
10
|
+
"X-Lettermint-Delivery" => at.to_i.to_s,
|
|
11
|
+
"X-Lettermint-Attempt" => "1"
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def lettermint_inbound_payload(recipient:, raw_url: "https://storage.lettermint.co/inbound/raw/test?signature=test", **data)
|
|
16
|
+
{
|
|
17
|
+
id: SecureRandom.uuid,
|
|
18
|
+
event: "message.inbound",
|
|
19
|
+
timestamp: Time.now.utc.iso8601(3),
|
|
20
|
+
data: { recipient: recipient, raw: { url: raw_url, expires_at: 28.days.from_now.iso8601 }, is_spam: false, spam_score: 0.0, **data }
|
|
21
|
+
}.to_json
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/telex.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require "telex/version"
|
|
2
|
+
require "telex/signature"
|
|
3
|
+
require "telex/download"
|
|
4
|
+
require "telex/engine"
|
|
5
|
+
|
|
6
|
+
# Email for our Rails apps through Lettermint: Action Mailer sends over its SMTP relay,
|
|
7
|
+
# Action Mailbox receives from its inbound webhooks.
|
|
8
|
+
module Telex
|
|
9
|
+
SMTP_SETTINGS = {
|
|
10
|
+
address: "smtp.lettermint.co",
|
|
11
|
+
port: 587,
|
|
12
|
+
user_name: "lettermint",
|
|
13
|
+
authentication: :plain,
|
|
14
|
+
enable_starttls: true
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
# The project's API token, which is also the SMTP password.
|
|
19
|
+
def api_token
|
|
20
|
+
Rails.application.credentials.dig(:lettermint, :api_token) || ENV["LETTERMINT_API_TOKEN"]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The inbound route's webhook secret, whsec_ prefix included.
|
|
24
|
+
def webhook_secret
|
|
25
|
+
Rails.application.credentials.dig(:lettermint, :webhook_secret) || ENV["LETTERMINT_WEBHOOK_SECRET"]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Replaced in tests; anything that answers #fetch(url) with the message's source.
|
|
29
|
+
attr_writer :download
|
|
30
|
+
|
|
31
|
+
def download
|
|
32
|
+
@download ||= Download.new
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: telex
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- B.O.X
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '8.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '8.0'
|
|
26
|
+
description: Action Mailer sends through Lettermint's SMTP relay; Action Mailbox receives
|
|
27
|
+
from Lettermint's inbound webhooks.
|
|
28
|
+
executables: []
|
|
29
|
+
extensions: []
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
files:
|
|
32
|
+
- MIT-LICENSE
|
|
33
|
+
- README.md
|
|
34
|
+
- Rakefile
|
|
35
|
+
- app/controllers/action_mailbox/ingresses/lettermint/inbound_emails_controller.rb
|
|
36
|
+
- app/jobs/telex/ingest_job.rb
|
|
37
|
+
- config/routes.rb
|
|
38
|
+
- lib/telex.rb
|
|
39
|
+
- lib/telex/download.rb
|
|
40
|
+
- lib/telex/engine.rb
|
|
41
|
+
- lib/telex/signature.rb
|
|
42
|
+
- lib/telex/test_helper.rb
|
|
43
|
+
- lib/telex/version.rb
|
|
44
|
+
homepage: https://github.com/boxprod/telex
|
|
45
|
+
licenses:
|
|
46
|
+
- MIT
|
|
47
|
+
metadata:
|
|
48
|
+
rubygems_mfa_required: 'true'
|
|
49
|
+
source_code_uri: https://github.com/boxprod/telex
|
|
50
|
+
rdoc_options: []
|
|
51
|
+
require_paths:
|
|
52
|
+
- lib
|
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: '3.3'
|
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
requirements: []
|
|
64
|
+
rubygems_version: 4.0.20
|
|
65
|
+
specification_version: 4
|
|
66
|
+
summary: Email for our Rails apps through Lettermint.
|
|
67
|
+
test_files: []
|