mailkite 0.4.0 → 0.6.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 +121 -1
  3. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf95721d75808a7648a0d5c5528a5b24cebe649e4e7b4f2aa7cf96309492c4df
4
- data.tar.gz: fd40da8370ad8ad24b538613a29a8b623226901a8db8e5bc6a4b0b3ebd1ec709
3
+ metadata.gz: 79f557c742e7377043c6fbbfc62c788bde380e6c75f647a354c4182346275a4d
4
+ data.tar.gz: be02421caccae364a81433fd239d043e19aeb546817c0acbf3c9727850894785
5
5
  SHA512:
6
- metadata.gz: 96e4a3c87afd08886338448d1edf8bfd83d7433733bba220cb892647249c923d46dde87df440ce78c84f1301b734fec480b89d9ee2e54f8eef2e528fec4af4dc
7
- data.tar.gz: d7b4ccb09e09a382e7d9bef520115c50aa06c635646763b73cea18bc133326b6080371a56037b3f05ccb5461b53b9d2369d4f5bfde84dedd1e5770d0fe0443d2
6
+ metadata.gz: 94b4ec4ac99c756f5887d9f5526ac931df163675e0dcd7a460a9703847b253a5c2852d744f227704f847791bb183f6a1226178ee0714b26eb6fc4249d68a3e17
7
+ data.tar.gz: 501cce3bfee6edd4123a24f337325668e2a4009523814b5d3989fa197a4d4fc2185587dfd3bdf694f2ceed3ba0330e14ceeeac54f351f4f8e30c91b4310c7a97
data/lib/mailkite.rb CHANGED
@@ -15,7 +15,7 @@ require "base64"
15
15
  require "cgi"
16
16
 
17
17
  module Mailkite
18
- VERSION = "0.4.0"
18
+ VERSION = "0.6.0"
19
19
  DEFAULT_BASE_URL = "https://api.mailkite.dev"
20
20
  # Reject webhook events older than this (ms) to block replays. Pass 0 to disable.
21
21
  DEFAULT_TOLERANCE_MS = 5 * 60 * 1000
@@ -61,6 +61,24 @@ module Mailkite
61
61
  '{"status":"ok"}'
62
62
  end
63
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
+
64
82
  # Encrypt a UTF-8 string to a customer RSA public key (SPKI/PEM), producing the
65
83
  # MailKite at-rest envelope as a compact JSON string. Hybrid scheme: a fresh
66
84
  # AES-256-GCM content key encrypts the data, then the raw key is wrapped with
@@ -147,6 +165,27 @@ module Mailkite
147
165
  req.body = JSON.generate(body)
148
166
  end
149
167
 
168
+ perform(req, uri)
169
+ end
170
+
171
+ # Raw-binary request used by uploadAttachment for `bytes`/`path` uploads:
172
+ # the body is the file bytes themselves (not JSON, not multipart) and the
173
+ # Content-Type names the file's media type. Same auth + response parsing.
174
+ def request_binary(bytes, filename:, content_type:, retention_days: nil)
175
+ query = { "filename" => filename }
176
+ query["retentionDays"] = retention_days unless retention_days.nil?
177
+ qs = query.map { |k, v| "#{CGI.escape(k)}=#{CGI.escape(v.to_s)}" }.join("&")
178
+ uri = URI("#{@base_url}/v1/attachments?#{qs}")
179
+ req = Net::HTTP::Post.new(uri)
180
+ req["Authorization"] = "Bearer #{@api_key}"
181
+ req["Content-Type"] = content_type
182
+ req.body = bytes
183
+ perform(req, uri)
184
+ end
185
+
186
+ # Send a prepared Net::HTTP request and parse the JSON response (shared by
187
+ # `request` and `request_binary`).
188
+ def perform(req, uri)
150
189
  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }
151
190
  text = res.body
152
191
  data = text && !text.empty? ? JSON.parse(text) : nil
@@ -166,6 +205,75 @@ module Mailkite
166
205
  request("POST", "/v1/send", message)
167
206
  end
168
207
 
208
+ # Extension → MIME map for raw-binary attachment uploads (default
209
+ # application/octet-stream when an extension is unknown).
210
+ ATTACHMENT_MIME = {
211
+ "pdf" => "application/pdf",
212
+ "png" => "image/png",
213
+ "jpg" => "image/jpeg",
214
+ "jpeg" => "image/jpeg",
215
+ "gif" => "image/gif",
216
+ "webp" => "image/webp",
217
+ "svg" => "image/svg+xml",
218
+ "csv" => "text/csv",
219
+ "txt" => "text/plain",
220
+ "html" => "text/html",
221
+ "json" => "application/json",
222
+ "zip" => "application/zip",
223
+ "doc" => "application/msword",
224
+ "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
225
+ "xls" => "application/vnd.ms-excel",
226
+ "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
227
+ "ics" => "text/calendar",
228
+ "ical" => "text/calendar",
229
+ }.freeze
230
+
231
+ # Upload a file and get back a secure, time-limited URL to reference as a
232
+ # send() attachment ({ filename, url }) or link inline — instead of
233
+ # base64-inlining large files on every send. Provide the file ONE of four
234
+ # ways via the `file` hash (priority order):
235
+ # 1. url — MailKite fetches & re-hosts the remote file
236
+ # 2. bytes — raw binary string, uploaded directly
237
+ # 3. path — local file read off disk, then uploaded as bytes
238
+ # 4. content — base64 string (the original behavior)
239
+ # Plus optional filename, contentType, and retentionDays.
240
+ def uploadAttachment(file)
241
+ url = file["url"] || file[:url]
242
+ bytes = file["bytes"] || file[:bytes]
243
+ path = file["path"] || file[:path]
244
+ content = file["content"] || file[:content]
245
+ filename = file["filename"] || file[:filename]
246
+ content_type = file["contentType"] || file[:contentType]
247
+ retention_days = file["retentionDays"] || file[:retentionDays]
248
+
249
+ if url
250
+ body = { "url" => url }
251
+ body["filename"] = filename unless filename.nil?
252
+ body["contentType"] = content_type unless content_type.nil?
253
+ body["retentionDays"] = retention_days unless retention_days.nil?
254
+ return request("POST", "/v1/attachments", body)
255
+ end
256
+
257
+ if bytes || path
258
+ if path
259
+ bytes = File.binread(path)
260
+ filename ||= File.basename(path)
261
+ content_type ||= ATTACHMENT_MIME[File.extname(path).downcase.delete_prefix(".")]
262
+ end
263
+ content_type ||= "application/octet-stream"
264
+ return request_binary(bytes, filename: filename, content_type: content_type, retention_days: retention_days)
265
+ end
266
+
267
+ if content
268
+ body = { "content" => content, "filename" => filename }
269
+ body["contentType"] = content_type unless content_type.nil?
270
+ body["retentionDays"] = retention_days unless retention_days.nil?
271
+ return request("POST", "/v1/attachments", body)
272
+ end
273
+
274
+ raise ArgumentError, "uploadAttachment requires one of: path, bytes, url, or content"
275
+ end
276
+
169
277
  # Run an inbound message through an AI agent. message keys: text (required),
170
278
  # plus optional subject/from/html/routeId/address/model.
171
279
  def agent(message)
@@ -270,6 +378,18 @@ module Mailkite
270
378
  Mailkite.reply_ok
271
379
  end
272
380
 
381
+ def reply_spam
382
+ Mailkite.reply_spam
383
+ end
384
+
385
+ def reply_drop
386
+ Mailkite.reply_drop
387
+ end
388
+
389
+ def reply_block_sender
390
+ Mailkite.reply_block_sender
391
+ end
392
+
273
393
  def encrypt(plaintext, public_key)
274
394
  Mailkite.encrypt(plaintext, public_key)
275
395
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mailkite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - MailKite