inbox_beam 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/CHANGELOG.md +9 -0
- data/LICENSE +21 -0
- data/README.md +148 -0
- data/lib/inbox_beam/client.rb +97 -0
- data/lib/inbox_beam/delivery_method.rb +34 -0
- data/lib/inbox_beam/message.rb +102 -0
- data/lib/inbox_beam/railtie.rb +15 -0
- data/lib/inbox_beam/version.rb +5 -0
- data/lib/inbox_beam.rb +14 -0
- metadata +73 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 37058947d959f74df792bb57612cc68eafe87a5eebcae8c9481fde1f1db31519
|
|
4
|
+
data.tar.gz: a7378b0b196ee61f516aab55869b66701180e3b84589d2c81aad4a8e09ea0761
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 79eff3ed7f9c5d519d78125b15a09d859270891109bdd3211bdeabc28c8a78ee21939be7c1600294bc72407880af1a07bbf7cee3d3bafb3b0cd204fe5b3f5cb6
|
|
7
|
+
data.tar.gz: 979eafb2c9a9f402bd74720625bde4914a99737a395cc77786842bf764c1d2474126d15822a76ecfcabc4acdce7aae2b869c1ccdfcd18b3dac53e44fc090de7b
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- Initial release.
|
|
6
|
+
- `InboxBeam::Client`: append messages to a mailbox via IMAP `APPEND` (stdlib `net/imap`), app-password or OAuth2 (XOAUTH2) auth.
|
|
7
|
+
- `InboxBeam::Message`: dependency-free, UTF-8-safe RFC 5322 message builder (encoded-word headers, base64 bodies, `multipart/alternative` for html + text).
|
|
8
|
+
- `InboxBeam::DeliveryMethod`: drop-in Action Mailer delivery method (`config.action_mailer.delivery_method = :inbox_beam`), auto-registered via Railtie.
|
|
9
|
+
- Options: `mailbox`, `from`, `to`, `unread`, `subject_prefix`, per-call overrides.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 toyoshi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# inbox_beam
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/inbox_beam)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
**Send notification emails to your own inbox from Ruby and Rails without SMTP, a sending domain, or SPF/DKIM/DMARC setup.**
|
|
7
|
+
|
|
8
|
+
If all you want is for your Rails app to drop a contact-form copy, a sign-up
|
|
9
|
+
alert, or a cron failure into *your own* inbox, configuring SMTP, a sending
|
|
10
|
+
domain, SPF, DKIM, DMARC, and a provider like SES, SendGrid, or Postmark is a lot
|
|
11
|
+
of deliverability overhead for mail only you will read.
|
|
12
|
+
|
|
13
|
+
inbox_beam skips all of it. It uses the IMAP `APPEND` command to write the
|
|
14
|
+
message straight into your mailbox. No SMTP, no sending domain, no
|
|
15
|
+
deliverability — the email shows up unread and searchable, and nothing was sent.
|
|
16
|
+
|
|
17
|
+
It ships as a plain Ruby client and as a drop-in **Action Mailer delivery
|
|
18
|
+
method**, so your existing Rails mailers can land in your inbox unchanged.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
# Gemfile
|
|
24
|
+
gem "inbox_beam"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
bundle install
|
|
29
|
+
# or
|
|
30
|
+
gem install inbox_beam
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Ruby >= 2.7.
|
|
34
|
+
|
|
35
|
+
## Rails / Action Mailer
|
|
36
|
+
|
|
37
|
+
Point Action Mailer at the `:inbox_beam` delivery method. Every mailer you
|
|
38
|
+
already have now appends to your inbox instead of sending over SMTP:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# config/environments/production.rb
|
|
42
|
+
config.action_mailer.delivery_method = :inbox_beam
|
|
43
|
+
config.action_mailer.inbox_beam_settings = {
|
|
44
|
+
host: "imap.gmail.com",
|
|
45
|
+
auth: { user: "you@example.com", pass: ENV["IMAP_APP_PASSWORD"] },
|
|
46
|
+
mailbox: "INBOX"
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
NotificationMailer.contact_form(submission).deliver_now
|
|
52
|
+
# → appended to your inbox. No SMTP, no SPF/DKIM/DMARC.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The delivery method is registered automatically when Rails loads.
|
|
56
|
+
|
|
57
|
+
## Plain Ruby
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
require "inbox_beam"
|
|
61
|
+
|
|
62
|
+
beam = InboxBeam::Client.new(
|
|
63
|
+
host: "imap.gmail.com",
|
|
64
|
+
auth: { user: "you@example.com", pass: ENV["IMAP_APP_PASSWORD"] }
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
beam.beam(subject: "New contact", text: "someone submitted the form")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
HTML plus text produces a `multipart/alternative` message:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
beam.beam(
|
|
74
|
+
subject: "Weekly report",
|
|
75
|
+
text: "Signups: 42",
|
|
76
|
+
html: "<h1>Weekly report</h1><p>Signups: 42</p>"
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Override the target mailbox per call:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
beam.beam(subject: "Cron failed", text: "nightly-export exited 1", mailbox: "Alerts")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## This is not email delivery
|
|
87
|
+
|
|
88
|
+
inbox_beam appends to a mailbox you already control. It does not send mail and
|
|
89
|
+
cannot reach anyone else. To email a user or customer, use SMTP or a delivery
|
|
90
|
+
API. It keeps no send log and the date is set by the client, so don't use it
|
|
91
|
+
where an audit trail matters. It's for your own notifications.
|
|
92
|
+
|
|
93
|
+
## Gmail setup
|
|
94
|
+
|
|
95
|
+
1. Enable IMAP in Gmail settings.
|
|
96
|
+
2. Turn on 2-Step Verification and create an [App Password](https://myaccount.google.com/apppasswords).
|
|
97
|
+
3. Use it as `auth[:pass]`.
|
|
98
|
+
|
|
99
|
+
Workspace admins can disable app passwords. In that case pass an OAuth2 token as
|
|
100
|
+
`auth[:access_token]` instead of `pass` (the client authenticates with XOAUTH2).
|
|
101
|
+
A dedicated `notify@` account that forwards to you is safer than your primary
|
|
102
|
+
account's credentials on a server.
|
|
103
|
+
|
|
104
|
+
## API
|
|
105
|
+
|
|
106
|
+
### `InboxBeam::Client.new(...)`
|
|
107
|
+
|
|
108
|
+
| Option | Default | Notes |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| `host:` | — | IMAP host, e.g. `imap.gmail.com`. |
|
|
111
|
+
| `auth:` | — | `{ user:, pass: }` (app password) or `{ user:, access_token: }` (OAuth2). |
|
|
112
|
+
| `port:` | `993` | |
|
|
113
|
+
| `ssl:` | `true` | Use TLS. |
|
|
114
|
+
| `mailbox:` | `"INBOX"` | Target mailbox or label. |
|
|
115
|
+
| `from:` | `auth[:user]` | Default From. |
|
|
116
|
+
| `to:` | `auth[:user]` | Default To. |
|
|
117
|
+
| `unread:` | `true` | Leave appended messages unread. |
|
|
118
|
+
| `subject_prefix:` | `nil` | Prepended to every subject. |
|
|
119
|
+
|
|
120
|
+
### `client.beam(...)`
|
|
121
|
+
|
|
122
|
+
Keyword args: `subject:` (required), `text:`, `html:`, `from:`, `to:`,
|
|
123
|
+
`mailbox:`, `unread:`, `date:`. Per-call values override the constructor
|
|
124
|
+
defaults. Returns an `InboxBeam::Result` with `mailbox`, `uid`, and
|
|
125
|
+
`uid_validity` (uid comes from the server's `APPENDUID` response).
|
|
126
|
+
|
|
127
|
+
### `InboxBeam::Message.build(...)`
|
|
128
|
+
|
|
129
|
+
The RFC 5322 builder is available on its own if you want the raw message without
|
|
130
|
+
the IMAP connection. Zero dependencies, UTF-8 safe.
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
raw = InboxBeam::Message.build(from: "a@x.com", to: "b@x.com", subject: "Hi", text: "Body")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## How it works
|
|
137
|
+
|
|
138
|
+
IMAP's `APPEND` command (RFC 9051 §6.3.12) adds a message to the end of a
|
|
139
|
+
mailbox. It's the same command mail clients use to save sent copies and drafts.
|
|
140
|
+
inbox_beam builds an RFC 5322 message and appends it over a TLS IMAP connection
|
|
141
|
+
using Ruby's standard `net/imap` — no runtime dependencies of its own.
|
|
142
|
+
|
|
143
|
+
A TypeScript/Node version is at
|
|
144
|
+
[inbox-beam](https://github.com/toyoshi/inbox-beam).
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/imap"
|
|
4
|
+
require_relative "message"
|
|
5
|
+
|
|
6
|
+
module InboxBeam
|
|
7
|
+
Result = Struct.new(:mailbox, :uid, :uid_validity, keyword_init: true)
|
|
8
|
+
|
|
9
|
+
# Writes a message directly into your own mailbox using IMAP APPEND.
|
|
10
|
+
#
|
|
11
|
+
# This is NOT email delivery. Nothing is sent over SMTP and no message leaves
|
|
12
|
+
# for another server — it is appended straight into the target mailbox. Use it
|
|
13
|
+
# to land your own app notifications in your own inbox without SPF/DKIM/DMARC.
|
|
14
|
+
class Client
|
|
15
|
+
DEFAULT_PORT = 993
|
|
16
|
+
DEFAULT_MAILBOX = "INBOX"
|
|
17
|
+
|
|
18
|
+
# @param host [String] IMAP host, e.g. "imap.gmail.com"
|
|
19
|
+
# @param auth [Hash] { user:, pass: } for an app password, or
|
|
20
|
+
# { user:, access_token: } for OAuth2 (XOAUTH2)
|
|
21
|
+
# @param port [Integer]
|
|
22
|
+
# @param ssl [Boolean] use TLS
|
|
23
|
+
# @param mailbox [String] target mailbox / label
|
|
24
|
+
# @param from [String, nil] default From (defaults to auth[:user])
|
|
25
|
+
# @param to [String, nil] default To (defaults to auth[:user])
|
|
26
|
+
# @param unread [Boolean] leave appended messages unread
|
|
27
|
+
# @param subject_prefix [String, nil] prepended to every subject
|
|
28
|
+
def initialize(host:, auth:, port: DEFAULT_PORT, ssl: true,
|
|
29
|
+
mailbox: DEFAULT_MAILBOX, from: nil, to: nil,
|
|
30
|
+
unread: true, subject_prefix: nil)
|
|
31
|
+
@host = host
|
|
32
|
+
@auth = auth
|
|
33
|
+
@port = port
|
|
34
|
+
@ssl = ssl
|
|
35
|
+
@mailbox = mailbox
|
|
36
|
+
@from = from
|
|
37
|
+
@to = to
|
|
38
|
+
@unread = unread
|
|
39
|
+
@subject_prefix = subject_prefix
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Append one message to the mailbox.
|
|
43
|
+
# @return [InboxBeam::Result]
|
|
44
|
+
def beam(subject:, text: nil, html: nil, from: nil, to: nil,
|
|
45
|
+
mailbox: nil, unread: nil, date: nil)
|
|
46
|
+
to ||= @to || @auth[:user]
|
|
47
|
+
from ||= @from || to
|
|
48
|
+
mailbox ||= @mailbox
|
|
49
|
+
unread = @unread if unread.nil?
|
|
50
|
+
subject = "#{@subject_prefix} #{subject}" if @subject_prefix
|
|
51
|
+
|
|
52
|
+
raw = Message.build(from: from, to: to, subject: subject, text: text, html: html, date: date)
|
|
53
|
+
append(mailbox, raw, unread: unread)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Append an already-encoded RFC 5322 message (e.g. Mail::Message#encoded).
|
|
57
|
+
# @return [InboxBeam::Result]
|
|
58
|
+
def append(mailbox, raw, unread: true)
|
|
59
|
+
flags = unread ? [] : [:Seen]
|
|
60
|
+
response = with_connection { |imap| imap.append(mailbox, raw, flags, nil) }
|
|
61
|
+
uid_validity, uid = append_uid(response)
|
|
62
|
+
Result.new(mailbox: mailbox, uid: uid, uid_validity: uid_validity)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def with_connection
|
|
68
|
+
imap = Net::IMAP.new(@host, port: @port, ssl: @ssl)
|
|
69
|
+
begin
|
|
70
|
+
if @auth[:access_token]
|
|
71
|
+
imap.authenticate("XOAUTH2", @auth[:user], @auth[:access_token])
|
|
72
|
+
else
|
|
73
|
+
imap.login(@auth[:user], @auth[:pass])
|
|
74
|
+
end
|
|
75
|
+
yield imap
|
|
76
|
+
ensure
|
|
77
|
+
begin
|
|
78
|
+
imap.logout
|
|
79
|
+
rescue StandardError
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
imap.disconnect
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Parse the APPENDUID response code: [APPENDUID <uidvalidity> <uid>].
|
|
87
|
+
def append_uid(response)
|
|
88
|
+
code = response&.data&.code
|
|
89
|
+
return [nil, nil] unless code && code.name == "APPENDUID"
|
|
90
|
+
|
|
91
|
+
uid_validity, uid = code.data.to_s.split(" ", 2)
|
|
92
|
+
[uid_validity&.to_i, uid&.to_i]
|
|
93
|
+
rescue StandardError
|
|
94
|
+
[nil, nil]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "client"
|
|
4
|
+
|
|
5
|
+
module InboxBeam
|
|
6
|
+
# Action Mailer delivery method. Instead of sending over SMTP, it appends the
|
|
7
|
+
# rendered message straight into your own mailbox via IMAP APPEND.
|
|
8
|
+
#
|
|
9
|
+
# config.action_mailer.delivery_method = :inbox_beam
|
|
10
|
+
# config.action_mailer.inbox_beam_settings = {
|
|
11
|
+
# host: "imap.gmail.com",
|
|
12
|
+
# auth: { user: "you@example.com", pass: ENV["IMAP_APP_PASSWORD"] },
|
|
13
|
+
# mailbox: "INBOX"
|
|
14
|
+
# }
|
|
15
|
+
#
|
|
16
|
+
# Existing mailers then land in your inbox with no sending domain, SPF, DKIM,
|
|
17
|
+
# or DMARC. For notifications you read yourself — not for mailing third parties.
|
|
18
|
+
class DeliveryMethod
|
|
19
|
+
attr_reader :settings
|
|
20
|
+
|
|
21
|
+
def initialize(settings = {})
|
|
22
|
+
@settings = settings
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param mail [Mail::Message]
|
|
26
|
+
def deliver!(mail)
|
|
27
|
+
opts = settings.dup
|
|
28
|
+
mailbox = opts.delete(:mailbox) || Client::DEFAULT_MAILBOX
|
|
29
|
+
unread = opts.key?(:unread) ? opts.delete(:unread) : true
|
|
30
|
+
Client.new(**opts).append(mailbox, mail.encoded, unread: unread)
|
|
31
|
+
mail
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
module InboxBeam
|
|
7
|
+
# Builds an RFC 5322 message ready to hand to IMAP APPEND.
|
|
8
|
+
# Zero dependencies — UTF-8 safe via base64 bodies and encoded-word headers.
|
|
9
|
+
module Message
|
|
10
|
+
CRLF = "\r\n"
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# @param from [String] e.g. "you@example.com" or "Name <you@example.com>"
|
|
15
|
+
# @param to [String]
|
|
16
|
+
# @param subject [String]
|
|
17
|
+
# @param text [String, nil]
|
|
18
|
+
# @param html [String, nil]
|
|
19
|
+
# @param date [Time, nil]
|
|
20
|
+
# @return [String] the raw RFC 5322 message with CRLF line endings
|
|
21
|
+
def build(from:, to:, subject:, text: nil, html: nil, date: nil)
|
|
22
|
+
date ||= Time.now
|
|
23
|
+
message_id = "<#{SecureRandom.hex(16)}@#{domain_of(from)}>"
|
|
24
|
+
|
|
25
|
+
headers = [
|
|
26
|
+
"From: #{encode_header(from)}",
|
|
27
|
+
"To: #{encode_header(to)}",
|
|
28
|
+
"Subject: #{encode_word(subject)}",
|
|
29
|
+
"Date: #{rfc2822_date(date)}",
|
|
30
|
+
"Message-ID: #{message_id}",
|
|
31
|
+
"MIME-Version: 1.0"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
if html && text
|
|
35
|
+
boundary = "inbox_beam_#{SecureRandom.hex(12)}"
|
|
36
|
+
headers << %(Content-Type: multipart/alternative; boundary="#{boundary}")
|
|
37
|
+
body = [
|
|
38
|
+
"--#{boundary}",
|
|
39
|
+
mime_part("text/plain", text),
|
|
40
|
+
"--#{boundary}",
|
|
41
|
+
mime_part("text/html", html),
|
|
42
|
+
"--#{boundary}--",
|
|
43
|
+
""
|
|
44
|
+
].join(CRLF)
|
|
45
|
+
elsif html
|
|
46
|
+
headers << "Content-Type: text/html; charset=UTF-8" << "Content-Transfer-Encoding: base64"
|
|
47
|
+
body = base64_body(html)
|
|
48
|
+
else
|
|
49
|
+
headers << "Content-Type: text/plain; charset=UTF-8" << "Content-Transfer-Encoding: base64"
|
|
50
|
+
body = base64_body(text.to_s)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
raw = headers.join(CRLF) + CRLF + CRLF + body
|
|
54
|
+
raw.gsub(/\r\n|\r|\n/, CRLF)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def ascii?(value)
|
|
58
|
+
value.ascii_only?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# RFC 2047 encoded-word for non-ASCII header values.
|
|
62
|
+
def encode_word(value)
|
|
63
|
+
return value if ascii?(value)
|
|
64
|
+
|
|
65
|
+
"=?UTF-8?B?#{Base64.strict_encode64(value.encode("UTF-8"))}?="
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Encode a header that may contain a display name: `名前 <addr>`.
|
|
69
|
+
def encode_header(value)
|
|
70
|
+
return value if ascii?(value)
|
|
71
|
+
|
|
72
|
+
if (m = value.match(/\A(.*?)\s*<([^>]+)>\s*\z/))
|
|
73
|
+
"#{encode_word(m[1])} <#{m[2]}>"
|
|
74
|
+
else
|
|
75
|
+
encode_word(value)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Base64-encode a body and wrap at 76 columns per RFC 2045.
|
|
80
|
+
def base64_body(content)
|
|
81
|
+
Base64.strict_encode64(content.encode("UTF-8")).scan(/.{1,76}/).join(CRLF)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def rfc2822_date(time)
|
|
85
|
+
time.getutc.strftime("%a, %d %b %Y %H:%M:%S +0000")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def domain_of(address)
|
|
89
|
+
m = address.match(/@([^>\s]+)/)
|
|
90
|
+
m ? m[1] : "localhost"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def mime_part(content_type, content)
|
|
94
|
+
[
|
|
95
|
+
"Content-Type: #{content_type}; charset=UTF-8",
|
|
96
|
+
"Content-Transfer-Encoding: base64",
|
|
97
|
+
"",
|
|
98
|
+
base64_body(content)
|
|
99
|
+
].join(CRLF)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
require_relative "delivery_method"
|
|
5
|
+
|
|
6
|
+
module InboxBeam
|
|
7
|
+
# Registers the :inbox_beam Action Mailer delivery method when Rails is loaded.
|
|
8
|
+
class Railtie < Rails::Railtie
|
|
9
|
+
initializer "inbox_beam.add_delivery_method" do
|
|
10
|
+
ActiveSupport.on_load(:action_mailer) do
|
|
11
|
+
add_delivery_method(:inbox_beam, InboxBeam::DeliveryMethod)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/inbox_beam.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "inbox_beam/version"
|
|
4
|
+
require_relative "inbox_beam/message"
|
|
5
|
+
require_relative "inbox_beam/client"
|
|
6
|
+
require_relative "inbox_beam/delivery_method"
|
|
7
|
+
|
|
8
|
+
# InboxBeam puts notifications into your own mailbox via IMAP APPEND —
|
|
9
|
+
# no SMTP, no sending domain, no SPF/DKIM/DMARC.
|
|
10
|
+
module InboxBeam
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Load the Action Mailer integration only when Rails is present.
|
|
14
|
+
require_relative "inbox_beam/railtie" if defined?(Rails::Railtie)
|
metadata
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: inbox_beam
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- toyoshi
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-06-08 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: net-imap
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.2'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.2'
|
|
27
|
+
description: InboxBeam appends a message straight into your own mailbox using the
|
|
28
|
+
IMAP APPEND command instead of sending email. Useful for landing app notifications,
|
|
29
|
+
contact-form copies, and cron alerts in your own inbox without standing up a sending
|
|
30
|
+
domain. Includes a drop-in Action Mailer delivery method.
|
|
31
|
+
email:
|
|
32
|
+
- toyoshi@tokuiten.jp
|
|
33
|
+
executables: []
|
|
34
|
+
extensions: []
|
|
35
|
+
extra_rdoc_files: []
|
|
36
|
+
files:
|
|
37
|
+
- CHANGELOG.md
|
|
38
|
+
- LICENSE
|
|
39
|
+
- README.md
|
|
40
|
+
- lib/inbox_beam.rb
|
|
41
|
+
- lib/inbox_beam/client.rb
|
|
42
|
+
- lib/inbox_beam/delivery_method.rb
|
|
43
|
+
- lib/inbox_beam/message.rb
|
|
44
|
+
- lib/inbox_beam/railtie.rb
|
|
45
|
+
- lib/inbox_beam/version.rb
|
|
46
|
+
homepage: https://github.com/toyoshi/inbox_beam
|
|
47
|
+
licenses:
|
|
48
|
+
- MIT
|
|
49
|
+
metadata:
|
|
50
|
+
homepage_uri: https://github.com/toyoshi/inbox_beam
|
|
51
|
+
source_code_uri: https://github.com/toyoshi/inbox_beam
|
|
52
|
+
changelog_uri: https://github.com/toyoshi/inbox_beam/blob/main/CHANGELOG.md
|
|
53
|
+
rubygems_mfa_required: 'true'
|
|
54
|
+
post_install_message:
|
|
55
|
+
rdoc_options: []
|
|
56
|
+
require_paths:
|
|
57
|
+
- lib
|
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: 2.7.0
|
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
requirements: []
|
|
69
|
+
rubygems_version: 3.0.3.1
|
|
70
|
+
signing_key:
|
|
71
|
+
specification_version: 4
|
|
72
|
+
summary: Put notifications into your own mailbox via IMAP APPEND — no SMTP, no SPF/DKIM/DMARC.
|
|
73
|
+
test_files: []
|