mailkite 0.3.0 → 0.5.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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mailkite.rb +112 -1
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42cf7ba9f50876399828e00154066736c0210841c052f13c985d55fdce4f1d44
4
- data.tar.gz: 56fc0377973778fd28f55386045df4df9b160813e4f601e86694d5393eef91c5
3
+ metadata.gz: 16ccb6cd1fe7bcc80237e945bd42ea0f5f0f69d6fc618b955d8b0e93f70c04ef
4
+ data.tar.gz: 198dd419d07e5dda9fb2023ec653b600098f63f6837f11344db3e156d28c37e3
5
5
  SHA512:
6
- metadata.gz: d9682dccd33207a1fdf67ac6ec85461c897bdf245c1f8bae43c3363c231e431434bd75c433ffa2e8f66ee5619b878e5672af3b598d32732989222173e0105391
7
- data.tar.gz: cff4c8b4683f22ef86abe586238a6d44367658667f6dded3a7257900760fb202a68e6fec2fc81c8d0b483b657d93004962a2d6162fc3ed23a30f56cba2558002
6
+ metadata.gz: d61fd1fe911b68d90fb8359eb62fca139a8858d8dd3752d4930420e6ea2fdb6ed59c9264936a3582359ae407d5320946621c7ddcca151e691aeb24a6bf8977a5
7
+ data.tar.gz: 88ff488ba25e4ffccf1fdc4c26b5bcaf38b2a7bf7fb3d795a0f8165c08f53e6166bcb01420ec826c370895b163b83cd427ae7e984c1b97f4953eece037499d69
data/lib/mailkite.rb CHANGED
@@ -11,10 +11,11 @@ require "net/http"
11
11
  require "uri"
12
12
  require "json"
13
13
  require "openssl"
14
+ require "base64"
14
15
  require "cgi"
15
16
 
16
17
  module Mailkite
17
- VERSION = "0.3.0"
18
+ VERSION = "0.5.0"
18
19
  DEFAULT_BASE_URL = "https://api.mailkite.dev"
19
20
  # Reject webhook events older than this (ms) to block replays. Pass 0 to disable.
20
21
  DEFAULT_TOLERANCE_MS = 5 * 60 * 1000
@@ -54,6 +55,83 @@ module Mailkite
54
55
  diff.zero?
55
56
  end
56
57
 
58
+ # The canonical body to return from a webhook handler so MailKite marks the
59
+ # delivery acknowledged. Returns the exact string `{"status":"ok"}`.
60
+ def self.reply_ok
61
+ '{"status":"ok"}'
62
+ end
63
+
64
+ # Control-mode reply telling MailKite to mark the message as spam.
65
+ # Returns the exact string `{"status":"spam"}`.
66
+ def self.reply_spam
67
+ '{"status":"spam"}'
68
+ end
69
+
70
+ # Control-mode reply telling MailKite to drop (discard) the message.
71
+ # Returns the exact string `{"status":"drop"}`.
72
+ def self.reply_drop
73
+ '{"status":"drop"}'
74
+ end
75
+
76
+ # Control-mode reply telling MailKite to block the sender.
77
+ # Returns the exact string `{"status":"ok","actions":[{"type":"block-sender"}]}`.
78
+ def self.reply_block_sender
79
+ '{"status":"ok","actions":[{"type":"block-sender"}]}'
80
+ end
81
+
82
+ # Encrypt a UTF-8 string to a customer RSA public key (SPKI/PEM), producing the
83
+ # MailKite at-rest envelope as a compact JSON string. Hybrid scheme: a fresh
84
+ # AES-256-GCM content key encrypts the data, then the raw key is wrapped with
85
+ # RSA-OAEP (SHA-256). Local only — no network.
86
+ def self.encrypt(plaintext, public_key)
87
+ pub = OpenSSL::PKey.read(public_key)
88
+ fp = OpenSSL::Digest::SHA256.hexdigest(pub.to_der)
89
+
90
+ raw_key = OpenSSL::Random.random_bytes(32)
91
+ iv = OpenSSL::Random.random_bytes(12)
92
+
93
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
94
+ cipher.encrypt
95
+ cipher.key = raw_key
96
+ cipher.iv = iv
97
+ body = cipher.update(plaintext.to_s.dup.force_encoding("UTF-8")) + cipher.final
98
+ ciphertext = body + cipher.auth_tag # GCM ct with 16-byte tag appended
99
+
100
+ wrapped = pub.encrypt(raw_key, rsa_padding_mode: "oaep", rsa_oaep_md: "sha256", rsa_mgf1_md: "sha256")
101
+
102
+ JSON.generate({
103
+ "v" => 1,
104
+ "keyAlg" => "RSA-OAEP-256",
105
+ "fp" => fp,
106
+ "enc" => "A256GCM",
107
+ "iv" => Base64.strict_encode64(iv),
108
+ "wrappedKey" => Base64.strict_encode64(wrapped),
109
+ "ciphertext" => Base64.strict_encode64(ciphertext),
110
+ })
111
+ end
112
+
113
+ # Decrypt a MailKite at-rest envelope (JSON string) with the matching RSA
114
+ # private key (PEM), returning the original UTF-8 plaintext. Local only.
115
+ def self.decrypt(envelope, private_key)
116
+ env = envelope.is_a?(String) ? JSON.parse(envelope) : envelope
117
+ priv = OpenSSL::PKey.read(private_key)
118
+
119
+ iv = Base64.strict_decode64(env["iv"])
120
+ wrapped = Base64.strict_decode64(env["wrappedKey"])
121
+ ct = Base64.strict_decode64(env["ciphertext"])
122
+ body = ct[0...-16]
123
+ tag = ct[-16..]
124
+
125
+ raw_key = priv.decrypt(wrapped, rsa_padding_mode: "oaep", rsa_oaep_md: "sha256", rsa_mgf1_md: "sha256")
126
+
127
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
128
+ cipher.decrypt
129
+ cipher.key = raw_key
130
+ cipher.iv = iv
131
+ cipher.auth_tag = tag
132
+ (cipher.update(body) + cipher.final).force_encoding("UTF-8")
133
+ end
134
+
57
135
  class Error < StandardError
58
136
  attr_reader :status, :body
59
137
 
@@ -106,6 +184,14 @@ module Mailkite
106
184
  request("POST", "/v1/send", message)
107
185
  end
108
186
 
187
+ # Upload a file (base64 `content`) and get back a secure, time-limited URL to
188
+ # reference as a send() attachment ({ filename, url }) or link inline —
189
+ # instead of base64-inlining large files on every send. `file` keys:
190
+ # filename, content (base64), plus optional contentType and retentionDays.
191
+ def uploadAttachment(file)
192
+ request("POST", "/v1/attachments", file)
193
+ end
194
+
109
195
  # Run an inbound message through an AI agent. message keys: text (required),
110
196
  # plus optional subject/from/html/routeId/address/model.
111
197
  def agent(message)
@@ -204,5 +290,30 @@ module Mailkite
204
290
  def verifyWebhook(signature, payload, secret, tolerance_ms = DEFAULT_TOLERANCE_MS)
205
291
  Mailkite.verify_webhook(signature, payload, secret, tolerance_ms)
206
292
  end
293
+
294
+ # Instance wrappers around the module-level crypto helpers. No network call.
295
+ def reply_ok
296
+ Mailkite.reply_ok
297
+ end
298
+
299
+ def reply_spam
300
+ Mailkite.reply_spam
301
+ end
302
+
303
+ def reply_drop
304
+ Mailkite.reply_drop
305
+ end
306
+
307
+ def reply_block_sender
308
+ Mailkite.reply_block_sender
309
+ end
310
+
311
+ def encrypt(plaintext, public_key)
312
+ Mailkite.encrypt(plaintext, public_key)
313
+ end
314
+
315
+ def decrypt(envelope, private_key)
316
+ Mailkite.decrypt(envelope, private_key)
317
+ end
207
318
  end
208
319
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mailkite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - MailKite
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-24 00:00:00.000000000 Z
11
+ date: 2026-06-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Send and manage email over your own authenticated domain with MailKite.
14
14
  email: