mailkite 0.2.0 → 0.4.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 +106 -1
  3. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 341bee09333eb85ced853ec72ae0eb3fe8276cd45f7eb1f92c3877b77d4f05c7
4
- data.tar.gz: f35eea300a83cb603ad5e5704bcca28023f2990ee10ac6c757ef035505927056
3
+ metadata.gz: cf95721d75808a7648a0d5c5528a5b24cebe649e4e7b4f2aa7cf96309492c4df
4
+ data.tar.gz: fd40da8370ad8ad24b538613a29a8b623226901a8db8e5bc6a4b0b3ebd1ec709
5
5
  SHA512:
6
- metadata.gz: ac63d4cfbbf5d28dd1a03cb0fd2f1162b2d29cb2488cfef580fb4aa8be781bab6265816da7d00ff525ad58d27e7f3ebd30397ceefd3ebb7a722f2b9dcf3c35f5
7
- data.tar.gz: 855a400b66eed816f3d79d125725989fcf72fce62e43b288bf8d96c715de0e85e342e0578a63e584c442ac6366ed2a8b78f89f7cc49ec8a340454732cee16459
6
+ metadata.gz: 96e4a3c87afd08886338448d1edf8bfd83d7433733bba220cb892647249c923d46dde87df440ce78c84f1301b734fec480b89d9ee2e54f8eef2e528fec4af4dc
7
+ data.tar.gz: d7b4ccb09e09a382e7d9bef520115c50aa06c635646763b73cea18bc133326b6080371a56037b3f05ccb5461b53b9d2369d4f5bfde84dedd1e5770d0fe0443d2
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.2.0"
18
+ VERSION = "0.4.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,65 @@ 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
+ # Encrypt a UTF-8 string to a customer RSA public key (SPKI/PEM), producing the
65
+ # MailKite at-rest envelope as a compact JSON string. Hybrid scheme: a fresh
66
+ # AES-256-GCM content key encrypts the data, then the raw key is wrapped with
67
+ # RSA-OAEP (SHA-256). Local only — no network.
68
+ def self.encrypt(plaintext, public_key)
69
+ pub = OpenSSL::PKey.read(public_key)
70
+ fp = OpenSSL::Digest::SHA256.hexdigest(pub.to_der)
71
+
72
+ raw_key = OpenSSL::Random.random_bytes(32)
73
+ iv = OpenSSL::Random.random_bytes(12)
74
+
75
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
76
+ cipher.encrypt
77
+ cipher.key = raw_key
78
+ cipher.iv = iv
79
+ body = cipher.update(plaintext.to_s.dup.force_encoding("UTF-8")) + cipher.final
80
+ ciphertext = body + cipher.auth_tag # GCM ct with 16-byte tag appended
81
+
82
+ wrapped = pub.encrypt(raw_key, rsa_padding_mode: "oaep", rsa_oaep_md: "sha256", rsa_mgf1_md: "sha256")
83
+
84
+ JSON.generate({
85
+ "v" => 1,
86
+ "keyAlg" => "RSA-OAEP-256",
87
+ "fp" => fp,
88
+ "enc" => "A256GCM",
89
+ "iv" => Base64.strict_encode64(iv),
90
+ "wrappedKey" => Base64.strict_encode64(wrapped),
91
+ "ciphertext" => Base64.strict_encode64(ciphertext),
92
+ })
93
+ end
94
+
95
+ # Decrypt a MailKite at-rest envelope (JSON string) with the matching RSA
96
+ # private key (PEM), returning the original UTF-8 plaintext. Local only.
97
+ def self.decrypt(envelope, private_key)
98
+ env = envelope.is_a?(String) ? JSON.parse(envelope) : envelope
99
+ priv = OpenSSL::PKey.read(private_key)
100
+
101
+ iv = Base64.strict_decode64(env["iv"])
102
+ wrapped = Base64.strict_decode64(env["wrappedKey"])
103
+ ct = Base64.strict_decode64(env["ciphertext"])
104
+ body = ct[0...-16]
105
+ tag = ct[-16..]
106
+
107
+ raw_key = priv.decrypt(wrapped, rsa_padding_mode: "oaep", rsa_oaep_md: "sha256", rsa_mgf1_md: "sha256")
108
+
109
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
110
+ cipher.decrypt
111
+ cipher.key = raw_key
112
+ cipher.iv = iv
113
+ cipher.auth_tag = tag
114
+ (cipher.update(body) + cipher.final).force_encoding("UTF-8")
115
+ end
116
+
57
117
  class Error < StandardError
58
118
  attr_reader :status, :body
59
119
 
@@ -99,10 +159,25 @@ module Mailkite
99
159
  end
100
160
 
101
161
  # --- Sending --------------------------------------------------------
162
+ # message keys: from, to, text/html, etc. `subject` is optional when a
163
+ # template supplies it. Pass `templateId` to render a stored template and
164
+ # `templateData` (a hash) to fill its variables.
102
165
  def send(message)
103
166
  request("POST", "/v1/send", message)
104
167
  end
105
168
 
169
+ # Run an inbound message through an AI agent. message keys: text (required),
170
+ # plus optional subject/from/html/routeId/address/model.
171
+ def agent(message)
172
+ request("POST", "/v1/agent", message)
173
+ end
174
+
175
+ # Route an inbound message to its configured destination. message keys: from
176
+ # (required), plus optional routeId/address/subject/text/html.
177
+ def route(message)
178
+ request("POST", "/v1/route", message)
179
+ end
180
+
106
181
  # --- Domains --------------------------------------------------------
107
182
  def listDomains
108
183
  request("GET", "/api/domains")
@@ -153,6 +228,23 @@ module Mailkite
153
228
  request("POST", "/api/routes", body)
154
229
  end
155
230
 
231
+ # --- Templates ------------------------------------------------------
232
+ def listTemplates
233
+ request("GET", "/api/templates")
234
+ end
235
+
236
+ def listBaseTemplates
237
+ request("GET", "/api/templates/base")
238
+ end
239
+
240
+ def getTemplate(id)
241
+ request("GET", "/api/templates/#{id}")
242
+ end
243
+
244
+ def createTemplate(body)
245
+ request("POST", "/api/templates", body)
246
+ end
247
+
156
248
  # --- Messages & deliveries -----------------------------------------
157
249
  def listMessages
158
250
  request("GET", "/api/messages")
@@ -172,5 +264,18 @@ module Mailkite
172
264
  def verifyWebhook(signature, payload, secret, tolerance_ms = DEFAULT_TOLERANCE_MS)
173
265
  Mailkite.verify_webhook(signature, payload, secret, tolerance_ms)
174
266
  end
267
+
268
+ # Instance wrappers around the module-level crypto helpers. No network call.
269
+ def reply_ok
270
+ Mailkite.reply_ok
271
+ end
272
+
273
+ def encrypt(plaintext, public_key)
274
+ Mailkite.encrypt(plaintext, public_key)
275
+ end
276
+
277
+ def decrypt(envelope, private_key)
278
+ Mailkite.decrypt(envelope, private_key)
279
+ end
175
280
  end
176
281
  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.2.0
4
+ version: 0.4.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-23 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:
@@ -21,7 +21,7 @@ homepage: https://mailkite.dev/docs/libraries
21
21
  licenses:
22
22
  - MIT
23
23
  metadata:
24
- source_code_uri: https://github.com/fijiwebdesign/mailkite
24
+ source_code_uri: https://github.com/mailkite/mailkite
25
25
  post_install_message:
26
26
  rdoc_options: []
27
27
  require_paths: