Class: Mailkite::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/mailkite.rb

Constant Summary collapse

VERBS =
{
  "GET" => Net::HTTP::Get,
  "POST" => Net::HTTP::Post,
  "PUT" => Net::HTTP::Put,
  "PATCH" => Net::HTTP::Patch,
  "DELETE" => Net::HTTP::Delete,
}.freeze
ATTACHMENT_MIME =

Extension → MIME map for raw-binary attachment uploads (default application/octet-stream when an extension is unknown).

{
  "pdf" => "application/pdf",
  "png" => "image/png",
  "jpg" => "image/jpeg",
  "jpeg" => "image/jpeg",
  "gif" => "image/gif",
  "webp" => "image/webp",
  "svg" => "image/svg+xml",
  "csv" => "text/csv",
  "txt" => "text/plain",
  "html" => "text/html",
  "json" => "application/json",
  "zip" => "application/zip",
  "doc" => "application/msword",
  "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  "xls" => "application/vnd.ms-excel",
  "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  "ics" => "text/calendar",
  "ical" => "text/calendar",
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(api_key = nil, base_url = DEFAULT_BASE_URL, access_token: nil, get_token: nil) ⇒ Client

Authenticate with a Bearer token. api_key (mk_live_…) and the access_token: keyword (an OAuth access token) are equivalent — both are Bearer credentials. For short-lived OAuth tokens pass get_token: — a callable invoked before each request for a fresh token (it wins):

MailKite::Client.new("mk_live_...")
MailKite::Client.new(access_token: my_oauth_token)
MailKite::Client.new(get_token: -> { current_session.access_token })


160
161
162
163
164
# File 'lib/mailkite.rb', line 160

def initialize(api_key = nil, base_url = DEFAULT_BASE_URL, access_token: nil, get_token: nil)
  @api_key = api_key.nil? ? access_token : api_key
  @get_token = get_token
  @base_url = base_url.sub(%r{/+\z}, "")
end

Instance Method Details

#addListContacts(id, body) ⇒ Object



426
427
428
# File 'lib/mailkite.rb', line 426

def addListContacts(id, body)
  request("POST", "/api/lists/#{id}/contacts", body)
end

#agent(message) ⇒ Object

Run an inbound message through an AI agent. message keys: text (required), plus optional subject/from/html/routeId/address/model.



292
293
294
# File 'lib/mailkite.rb', line 292

def agent(message)
  request("POST", "/v1/agent", message)
end

#checkDomainAvailability(domain) ⇒ Object



339
340
341
# File 'lib/mailkite.rb', line 339

def checkDomainAvailability(domain)
  request("GET", "/api/domains/register/check?domain=#{CGI.escape(domain)}")
end

#createBroadcast(body) ⇒ Object



439
440
441
# File 'lib/mailkite.rb', line 439

def createBroadcast(body)
  request("POST", "/api/broadcasts", body)
end

#createDomain(body) ⇒ Object



307
308
309
# File 'lib/mailkite.rb', line 307

def createDomain(body)
  request("POST", "/api/domains", body)
end

#createList(body) ⇒ Object



401
402
403
# File 'lib/mailkite.rb', line 401

def createList(body)
  request("POST", "/api/lists", body)
end

#createRoute(body) ⇒ Object



352
353
354
# File 'lib/mailkite.rb', line 352

def createRoute(body)
  request("POST", "/api/routes", body)
end

#createTemplate(body) ⇒ Object



373
374
375
# File 'lib/mailkite.rb', line 373

def createTemplate(body)
  request("POST", "/api/templates", body)
end

#decrypt(envelope, private_key) ⇒ Object



494
495
496
# File 'lib/mailkite.rb', line 494

def decrypt(envelope, private_key)
  Mailkite.decrypt(envelope, private_key)
end

#deleteBroadcast(id) ⇒ Object



451
452
453
# File 'lib/mailkite.rb', line 451

def deleteBroadcast(id)
  request("DELETE", "/api/broadcasts/#{id}")
end

#deleteDomain(id) ⇒ Object



319
320
321
# File 'lib/mailkite.rb', line 319

def deleteDomain(id)
  request("DELETE", "/api/domains/#{id}")
end

#deleteList(id) ⇒ Object



413
414
415
# File 'lib/mailkite.rb', line 413

def deleteList(id)
  request("DELETE", "/api/lists/#{id}")
end

#deleteRoute(id) ⇒ Object



356
357
358
# File 'lib/mailkite.rb', line 356

def deleteRoute(id)
  request("DELETE", "/api/routes/#{id}")
end

#deleteWebhook(id) ⇒ Object



331
332
333
# File 'lib/mailkite.rb', line 331

def deleteWebhook(id)
  request("DELETE", "/api/domains/#{id}/webhook")
end

#encrypt(plaintext, public_key) ⇒ Object



490
491
492
# File 'lib/mailkite.rb', line 490

def encrypt(plaintext, public_key)
  Mailkite.encrypt(plaintext, public_key)
end

#getBroadcast(id) ⇒ Object



443
444
445
# File 'lib/mailkite.rb', line 443

def getBroadcast(id)
  request("GET", "/api/broadcasts/#{id}")
end

#getDomain(id) ⇒ Object



311
312
313
# File 'lib/mailkite.rb', line 311

def getDomain(id)
  request("GET", "/api/domains/#{id}")
end

#getList(id) ⇒ Object



405
406
407
# File 'lib/mailkite.rb', line 405

def getList(id)
  request("GET", "/api/lists/#{id}")
end

#getMessage(id) ⇒ Object



388
389
390
# File 'lib/mailkite.rb', line 388

def getMessage(id)
  request("GET", "/api/messages/#{id}")
end

#getTemplate(id) ⇒ Object



369
370
371
# File 'lib/mailkite.rb', line 369

def getTemplate(id)
  request("GET", "/api/templates/#{id}")
end

#getWebhookSecret(id) ⇒ Object



315
316
317
# File 'lib/mailkite.rb', line 315

def getWebhookSecret(id)
  request("GET", "/api/domains/#{id}/webhook/secret")
end

#listBaseTemplatesObject



365
366
367
# File 'lib/mailkite.rb', line 365

def listBaseTemplates
  request("GET", "/api/templates/base")
end

#listBroadcastsObject

--- Broadcasts -----------------------------------------------------



435
436
437
# File 'lib/mailkite.rb', line 435

def listBroadcasts
  request("GET", "/api/broadcasts")
end

#listDomainsObject

--- Domains --------------------------------------------------------



303
304
305
# File 'lib/mailkite.rb', line 303

def listDomains
  request("GET", "/api/domains")
end

#listListContacts(id, before = nil, limit = nil) ⇒ Object



417
418
419
420
421
422
423
424
# File 'lib/mailkite.rb', line 417

def listListContacts(id, before = nil, limit = nil)
  params = []
  params << "before=#{CGI.escape(before.to_s)}" unless before.nil?
  params << "limit=#{CGI.escape(limit.to_s)}" unless limit.nil?
  path = "/api/lists/#{id}/contacts"
  path += "?#{params.join('&')}" unless params.empty?
  request("GET", path)
end

#listListsObject

--- Lists ----------------------------------------------------------



397
398
399
# File 'lib/mailkite.rb', line 397

def listLists
  request("GET", "/api/lists")
end

#listMessages(before = nil, limit = nil, search = nil) ⇒ Object

--- Messages & deliveries -----------------------------------------



378
379
380
381
382
383
384
385
386
# File 'lib/mailkite.rb', line 378

def listMessages(before = nil, limit = nil, search = nil)
  params = []
  params << "before=#{CGI.escape(before.to_s)}" unless before.nil?
  params << "limit=#{CGI.escape(limit.to_s)}" unless limit.nil?
  params << "search=#{CGI.escape(search.to_s)}" unless search.nil?
  path = "/api/messages"
  path += "?#{params.join('&')}" unless params.empty?
  request("GET", path)
end

#listRoutesObject

--- Routes ---------------------------------------------------------



348
349
350
# File 'lib/mailkite.rb', line 348

def listRoutes
  request("GET", "/api/routes")
end

#listTemplatesObject

--- Templates ------------------------------------------------------



361
362
363
# File 'lib/mailkite.rb', line 361

def listTemplates
  request("GET", "/api/templates")
end

#perform(req, uri) ⇒ Object

Send a prepared Net::HTTP request and parse the JSON response (shared by request and request_binary).



201
202
203
204
205
206
207
208
209
210
211
# File 'lib/mailkite.rb', line 201

def perform(req, uri)
  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }
  text = res.body
  data = text && !text.empty? ? JSON.parse(text) : nil
  code = res.code.to_i
  unless code >= 200 && code < 300
    message = data.is_a?(Hash) ? data["error"] : nil
    raise Error.new(code, message || res.message || "HTTP #{code}", data)
  end
  data
end

#registerDomain(body) ⇒ Object



343
344
345
# File 'lib/mailkite.rb', line 343

def registerDomain(body)
  request("POST", "/api/domains/register", body)
end

#removeListContact(id, contactId) ⇒ Object



430
431
432
# File 'lib/mailkite.rb', line 430

def removeListContact(id, contactId)
  request("DELETE", "/api/lists/#{id}/contacts/#{contactId}")
end

#reply_block_senderObject



486
487
488
# File 'lib/mailkite.rb', line 486

def reply_block_sender
  Mailkite.reply_block_sender
end

#reply_dropObject



482
483
484
# File 'lib/mailkite.rb', line 482

def reply_drop
  Mailkite.reply_drop
end

#reply_okObject

Instance wrappers around the module-level crypto helpers. No network call.



474
475
476
# File 'lib/mailkite.rb', line 474

def reply_ok
  Mailkite.reply_ok
end

#reply_spamObject



478
479
480
# File 'lib/mailkite.rb', line 478

def reply_spam
  Mailkite.reply_spam
end

#request(method, path, body = nil) ⇒ Object

Low-level request. Every method below is a one-liner on top of this.



172
173
174
175
176
177
178
179
180
181
182
# File 'lib/mailkite.rb', line 172

def request(method, path, body = nil)
  uri = URI(@base_url + path)
  req = VERBS.fetch(method).new(uri)
  req["Authorization"] = "Bearer #{token}"
  unless body.nil?
    req["Content-Type"] = "application/json"
    req.body = JSON.generate(body)
  end

  perform(req, uri)
end

#request_binary(bytes, filename:, content_type:, retention_days: nil) ⇒ Object

Raw-binary request used by uploadAttachment for bytes/path uploads: the body is the file bytes themselves (not JSON, not multipart) and the Content-Type names the file's media type. Same auth + response parsing.



187
188
189
190
191
192
193
194
195
196
197
# File 'lib/mailkite.rb', line 187

def request_binary(bytes, filename:, content_type:, retention_days: nil)
  query = { "filename" => filename }
  query["retentionDays"] = retention_days unless retention_days.nil?
  qs = query.map { |k, v| "#{CGI.escape(k)}=#{CGI.escape(v.to_s)}" }.join("&")
  uri = URI("#{@base_url}/v1/attachments?#{qs}")
  req = Net::HTTP::Post.new(uri)
  req["Authorization"] = "Bearer #{token}"
  req["Content-Type"] = content_type
  req.body = bytes
  perform(req, uri)
end

#retryDelivery(id) ⇒ Object



392
393
394
# File 'lib/mailkite.rb', line 392

def retryDelivery(id)
  request("POST", "/api/deliveries/#{id}/retry")
end

#route(message) ⇒ Object

Route an inbound message to its configured destination. message keys: from (required), plus optional routeId/address/subject/text/html.



298
299
300
# File 'lib/mailkite.rb', line 298

def route(message)
  request("POST", "/v1/route", message)
end

#semanticSearch(query) ⇒ Object

--- Docs ----------------------------------------------------------- Semantic search over the MailKite docs. PUBLIC — no auth required. Returns { "query" => ..., "matches" => [...] }.



462
463
464
# File 'lib/mailkite.rb', line 462

def semanticSearch(query)
  request("GET", "/v1/docs/search?query=#{CGI.escape(query)}")
end

#send(message) ⇒ Object

--- Sending -------------------------------------------------------- message keys: from, to, text/html, etc. subject is optional when a template supplies it. Pass templateId to render a stored template and templateData (a hash) to fill its variables.



217
218
219
# File 'lib/mailkite.rb', line 217

def send(message)
  request("POST", "/v1/send", message)
end

#sendBroadcast(id, body) ⇒ Object



455
456
457
# File 'lib/mailkite.rb', line 455

def sendBroadcast(id, body)
  request("POST", "/api/broadcasts/#{id}/send", body)
end

#setWebhook(id, body) ⇒ Object



327
328
329
# File 'lib/mailkite.rb', line 327

def setWebhook(id, body)
  request("PUT", "/api/domains/#{id}/webhook", body)
end

#testWebhook(id) ⇒ Object



335
336
337
# File 'lib/mailkite.rb', line 335

def testWebhook(id)
  request("POST", "/api/domains/#{id}/webhook/test")
end

#tokenObject

The Bearer token for one request: a fresh one from get_token if set, else the static key/token.



167
168
169
# File 'lib/mailkite.rb', line 167

def token
  @get_token ? @get_token.call : @api_key
end

#updateBroadcast(id, body) ⇒ Object



447
448
449
# File 'lib/mailkite.rb', line 447

def updateBroadcast(id, body)
  request("PATCH", "/api/broadcasts/#{id}", body)
end

#updateList(id, body) ⇒ Object



409
410
411
# File 'lib/mailkite.rb', line 409

def updateList(id, body)
  request("PATCH", "/api/lists/#{id}", body)
end

#uploadAttachment(file) ⇒ Object

Upload a file and get back a secure, time-limited URL to reference as a send() attachment ({ filename, url }) or link inline — instead of base64-inlining large files on every send. Provide the file ONE of four ways via the file hash (priority order):

1. url            — MailKite fetches & re-hosts the remote file
2. bytes          — raw binary string, uploaded directly
3. path           — local file read off disk, then uploaded as bytes
4. content        — base64 string (the original behavior)

Plus optional filename, contentType, and retentionDays.

Raises:

  • (ArgumentError)


253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/mailkite.rb', line 253

def uploadAttachment(file)
  url = file["url"] || file[:url]
  bytes = file["bytes"] || file[:bytes]
  path = file["path"] || file[:path]
  content = file["content"] || file[:content]
  filename = file["filename"] || file[:filename]
  content_type = file["contentType"] || file[:contentType]
  retention_days = file["retentionDays"] || file[:retentionDays]

  if url
    body = { "url" => url }
    body["filename"] = filename unless filename.nil?
    body["contentType"] = content_type unless content_type.nil?
    body["retentionDays"] = retention_days unless retention_days.nil?
    return request("POST", "/v1/attachments", body)
  end

  if bytes || path
    if path
      bytes = File.binread(path)
      filename ||= File.basename(path)
      content_type ||= ATTACHMENT_MIME[File.extname(path).downcase.delete_prefix(".")]
    end
    content_type ||= "application/octet-stream"
    return request_binary(bytes, filename: filename, content_type: content_type, retention_days: retention_days)
  end

  if content
    body = { "content" => content, "filename" => filename }
    body["contentType"] = content_type unless content_type.nil?
    body["retentionDays"] = retention_days unless retention_days.nil?
    return request("POST", "/v1/attachments", body)
  end

  raise ArgumentError, "uploadAttachment requires one of: path, bytes, url, or content"
end

#verifyDomain(id) ⇒ Object



323
324
325
# File 'lib/mailkite.rb', line 323

def verifyDomain(id)
  request("POST", "/api/domains/#{id}/verify")
end

#verifyWebhook(signature, payload, secret, tolerance_ms = DEFAULT_TOLERANCE_MS) ⇒ Object

--- Webhooks ------------------------------------------------------- Instance wrapper around Mailkite.verify_webhook, so you can verify on an existing client. No network call; no API key required.



469
470
471
# File 'lib/mailkite.rb', line 469

def verifyWebhook(signature, payload, secret, tolerance_ms = DEFAULT_TOLERANCE_MS)
  Mailkite.verify_webhook(signature, payload, secret, tolerance_ms)
end