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

#addSuppression(body) ⇒ Object

Suppress an address so this account never sends to it again. body is a hash with "email" (required) and optional "reason" (defaults to manual) and "note".



560
561
562
# File 'lib/mailkite.rb', line 560

def addSuppression(body)
  request("POST", "/api/contacts/suppressions", 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

#createScopedKey(body) ⇒ Object

Create a key scoped to one domain. body is a hash with "domainId" (required) and optional "name". Ideal for per-site installs (e.g. a WordPress plugin) — the site never holds the account master key.



534
535
536
# File 'lib/mailkite.rb', line 534

def createScopedKey(body)
  request("POST", "/api/keys/scoped", 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



605
606
607
# File 'lib/mailkite.rb', line 605

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

#deleteScopedKey(id) ⇒ Object

Revoke a domain-scoped key. Takes effect immediately.



539
540
541
# File 'lib/mailkite.rb', line 539

def deleteScopedKey(id)
  request("DELETE", "/api/keys/scoped/#{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



601
602
603
# File 'lib/mailkite.rb', line 601

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

#getApiKeyObject

Get the account's unrestricted API key (mk_live_…). Read-or-create: the first call mints it.



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

def getApiKey
  request("GET", "/api/keys")
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

#getUsageObject

Current billing-period usage: emails used vs the plan's included bucket (null = unlimited), AI actions, and the overage state that gates sending. Powers quota meters in dashboards and integrations.



546
547
548
# File 'lib/mailkite.rb', line 546

def getUsage
  request("GET", "/api/billing/usage")
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

#listScopedKeysObject

List the account's domain-scoped API keys. A scoped key can send and manage only its one domain — hand one to each site or CI job so a leak burns only that surface.



527
528
529
# File 'lib/mailkite.rb', line 527

def listScopedKeys
  request("GET", "/api/keys/scoped")
end

#listSuppressionsObject

--- Suppressions ----------------------------------------------------- List suppressed addresses (unsubscribes, hard bounces, spam complaints, manual). Sends to a suppressed address are dropped before delivery.



553
554
555
# File 'lib/mailkite.rb', line 553

def listSuppressions
  request("GET", "/api/contacts/suppressions")
end

#listTemplatesObject

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



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

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

#meObject

The account behind this credential: email, whether it is verified (sending is blocked until it is), and plan. Use to poll verification state after register().



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

def me
  request("GET", "/v1/me")
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

#register(body) ⇒ Object

--- Account ---------------------------------------------------------- Create a MailKite account from just an email — no password. body is a hash with "email" (required) and optional "channel", "ref" and "referrer". Returns the new account's API key immediately; a verification link is emailed, and SENDING stays blocked until the address is verified (poll me()). An existing email returns 409 account_exists with no credentials. PUBLIC — no API key required.



501
502
503
# File 'lib/mailkite.rb', line 501

def register(body)
  request("POST", "/api/v1/provision", body)
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

#removeSuppression(email) ⇒ Object

Remove an address from the suppression list. Removing an unsuppressed address is a no-op success.



566
567
568
# File 'lib/mailkite.rb', line 566

def removeSuppression(email)
  request("DELETE", "/api/contacts/suppressions/#{CGI.escape(email)}")
end

#reply_block_senderObject



597
598
599
# File 'lib/mailkite.rb', line 597

def reply_block_sender
  Mailkite.reply_block_sender
end

#reply_dropObject



593
594
595
# File 'lib/mailkite.rb', line 593

def reply_drop
  Mailkite.reply_drop
end

#reply_okObject

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



585
586
587
# File 'lib/mailkite.rb', line 585

def reply_ok
  Mailkite.reply_ok
end

#reply_spamObject



589
590
591
# File 'lib/mailkite.rb', line 589

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

#rotateApiKeyObject

Rotate the account API key: the old key stops working immediately and a fresh one is returned. The plaintext is only shown here.



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

def rotateApiKey
  request("POST", "/api/keys/rotate")
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" => [...] }.



573
574
575
# File 'lib/mailkite.rb', line 573

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.



580
581
582
# File 'lib/mailkite.rb', line 580

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