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



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

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.



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

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

#checkDomainAvailability(domain) ⇒ Object



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

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

#createBroadcast(body) ⇒ Object



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

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

#createDomain(body) ⇒ Object



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

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

#createList(body) ⇒ Object



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

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

#createRoute(body) ⇒ Object



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

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

#createTemplate(body) ⇒ Object



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

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

#decrypt(envelope, private_key) ⇒ Object



529
530
531
# File 'lib/mailkite.rb', line 529

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

#deleteBroadcast(id) ⇒ Object



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

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

#deleteDomain(id) ⇒ Object



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

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

#deleteList(id) ⇒ Object



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

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

#deleteRoute(id) ⇒ Object



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

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

#deleteTrackingWebhook(id) ⇒ Object



358
359
360
# File 'lib/mailkite.rb', line 358

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

#deleteWebhook(id) ⇒ Object



350
351
352
# File 'lib/mailkite.rb', line 350

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

#deleteWebhookEvents(id) ⇒ Object



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

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

#encrypt(plaintext, public_key) ⇒ Object



525
526
527
# File 'lib/mailkite.rb', line 525

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

#getBroadcast(id) ⇒ Object



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

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

#getDomain(id) ⇒ Object



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

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

#getList(id) ⇒ Object



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

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

#getMessage(id) ⇒ Object



423
424
425
# File 'lib/mailkite.rb', line 423

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

#getTemplate(id) ⇒ Object



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

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

#getWebhookSecret(id) ⇒ Object



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

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

#listBaseTemplatesObject



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

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

#listBroadcastsObject

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



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

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

#listDomainsObject

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



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

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

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



452
453
454
455
456
457
458
459
# File 'lib/mailkite.rb', line 452

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 ----------------------------------------------------------



432
433
434
# File 'lib/mailkite.rb', line 432

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

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

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



413
414
415
416
417
418
419
420
421
# File 'lib/mailkite.rb', line 413

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 ---------------------------------------------------------



383
384
385
# File 'lib/mailkite.rb', line 383

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

#listTemplatesObject

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



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

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



378
379
380
# File 'lib/mailkite.rb', line 378

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

#removeListContact(id, contactId) ⇒ Object



465
466
467
# File 'lib/mailkite.rb', line 465

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

#reply_block_senderObject



521
522
523
# File 'lib/mailkite.rb', line 521

def reply_block_sender
  Mailkite.reply_block_sender
end

#reply_dropObject



517
518
519
# File 'lib/mailkite.rb', line 517

def reply_drop
  Mailkite.reply_drop
end

#reply_okObject

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



509
510
511
# File 'lib/mailkite.rb', line 509

def reply_ok
  Mailkite.reply_ok
end

#reply_spamObject



513
514
515
# File 'lib/mailkite.rb', line 513

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



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

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.



317
318
319
# File 'lib/mailkite.rb', line 317

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" => [...] }.



497
498
499
# File 'lib/mailkite.rb', line 497

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. Send an email. message is a hash with "from", "to" and a body ("text" and/or "html"). Also accepted: "subject" (optional when a template supplies it), "cc"/"bcc" (string or array), "replyTo", "inReplyTo" (thread a reply), "attachments" (array of url|content, contentType), "headers" (hash of extra raw MIME headers, e.g. List-Unsubscribe — immediate sends only), and "templateId" + "templateData" to render a stored template.



224
225
226
# File 'lib/mailkite.rb', line 224

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

#sendBatch(batch) ⇒ Object

Send one personalized message per recipient (up to 50) in a single call. batch is a hash with "from" and "recipients" (array of "templateData", "headers" — exactly one address each). Shared fields ("subject", "html"/"text", "templateId", "templateData", "headers", …) form the base message; per-recipient "templateData" and "headers" are merged over the shared ones (the recipient wins). Every message passes the same gates as send() and gets its own id — the response reports each recipient's outcome in order, so a batch can partially succeed.



236
237
238
# File 'lib/mailkite.rb', line 236

def sendBatch(batch)
  request("POST", "/v1/send/batch", batch)
end

#sendBroadcast(id, body) ⇒ Object



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

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

#setTrackingWebhook(id, body) ⇒ Object



354
355
356
# File 'lib/mailkite.rb', line 354

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

#setWebhook(id, body) ⇒ Object



346
347
348
# File 'lib/mailkite.rb', line 346

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

#setWebhookEvents(id, body) ⇒ Object



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

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

#testWebhook(id) ⇒ Object



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

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



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

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

#updateList(id, body) ⇒ Object



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

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)


272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/mailkite.rb', line 272

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



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

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.



504
505
506
# File 'lib/mailkite.rb', line 504

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