mailkite 0.10.0 → 0.12.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.
- checksums.yaml +4 -4
- data/lib/mailkite/delivery_method.rb +150 -0
- data/lib/mailkite/railtie.rb +16 -0
- data/lib/mailkite.rb +12 -2
- metadata +19 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 26eb50d856d1ba16356a5fc399a84414774dccf715a0dc6291b451030ea27272
|
|
4
|
+
data.tar.gz: 3472e6b74b0772e5c18906fb66e9fd8c39dcb6de194fbf1a78b131086d676d3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca926ae3006b0fde3931988b8e1cb62f196831e60bf9e3d77cf5b0b10ba8dd724858ef1428a3decc422787e166e876c471605e04384880c1030e209f6dca5420
|
|
7
|
+
data.tar.gz: ae7bcbefece087b52e9936df73b885a63a4e264b6b05d0b6b362fb086dcc0bd4413df2b5b60c598680120e8e4c2adc7358564e453d7293fb970b4e35feb93e09
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Docs: docs/integrations/rails-delivery-method.md
|
|
2
|
+
#
|
|
3
|
+
# ActionMailer / Mail delivery method: deliver Mail::Message objects through
|
|
4
|
+
# the MailKite API (POST /v1/send) instead of SMTP.
|
|
5
|
+
#
|
|
6
|
+
# # Rails — the Railtie registers :mailkite automatically:
|
|
7
|
+
# config.action_mailer.delivery_method = :mailkite
|
|
8
|
+
# config.action_mailer.mailkite_settings = { api_key: ENV["MAILKITE_API_KEY"] }
|
|
9
|
+
#
|
|
10
|
+
# # Plain Mail (no Rails):
|
|
11
|
+
# Mail.defaults { delivery_method Mailkite::DeliveryMethod, api_key: ENV["MAILKITE_API_KEY"] }
|
|
12
|
+
#
|
|
13
|
+
# The `mail` gem is NOT a runtime dependency of this gem — it is required
|
|
14
|
+
# lazily here, on instantiation, so `require "mailkite"` stays zero-dependency.
|
|
15
|
+
|
|
16
|
+
module Mailkite
|
|
17
|
+
class DeliveryMethod
|
|
18
|
+
# Raised when a Mail::Message uses a feature the send API has no
|
|
19
|
+
# equivalent for (custom headers, inline cid: attachments, …). Refusing
|
|
20
|
+
# loudly beats silently dropping part of the message.
|
|
21
|
+
class UnsupportedFeatureError < StandardError; end
|
|
22
|
+
|
|
23
|
+
# Top-level headers the conversion consumes, plus the structural headers
|
|
24
|
+
# that only describe the MIME document (the API composes its own MIME).
|
|
25
|
+
# Anything else raises UnsupportedFeatureError — POST /v1/send takes no
|
|
26
|
+
# custom headers.
|
|
27
|
+
HANDLED_HEADERS = %w[
|
|
28
|
+
from to cc bcc subject reply-to in-reply-to
|
|
29
|
+
date message-id mime-version content-type content-transfer-encoding
|
|
30
|
+
].freeze
|
|
31
|
+
|
|
32
|
+
attr_reader :settings
|
|
33
|
+
|
|
34
|
+
# settings: api_key (falls back to ENV["MAILKITE_API_KEY"]), optional
|
|
35
|
+
# base_url, or bring your own `client` — anything with `send(message)`
|
|
36
|
+
# (e.g. a Mailkite::Client, or a stub in tests). Takes precedence over
|
|
37
|
+
# api_key.
|
|
38
|
+
def initialize(settings = {})
|
|
39
|
+
require "mail" # lazy — only delivery-method users need the mail gem
|
|
40
|
+
@settings = settings
|
|
41
|
+
@client = settings[:client]
|
|
42
|
+
return if @client
|
|
43
|
+
|
|
44
|
+
api_key = settings[:api_key] || ENV["MAILKITE_API_KEY"]
|
|
45
|
+
if api_key.nil? || api_key.empty?
|
|
46
|
+
raise ArgumentError, "MailKite API key missing — pass api_key: in mailkite_settings or set MAILKITE_API_KEY"
|
|
47
|
+
end
|
|
48
|
+
@client = Client.new(api_key, settings[:base_url] || DEFAULT_BASE_URL)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Deliver a Mail::Message via POST /v1/send. Returns the API response hash
|
|
52
|
+
# ({ "id" => …, "status" => … }); API failures raise Mailkite::Error.
|
|
53
|
+
def deliver!(mail)
|
|
54
|
+
@client.send(payload_for(mail))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Convert a Mail::Message into a send-request body
|
|
60
|
+
# (sdks/spec/schemas/send-request.json). Raises on anything the API can't
|
|
61
|
+
# express rather than delivering a lossy message.
|
|
62
|
+
def payload_for(mail)
|
|
63
|
+
reject_custom_headers(mail)
|
|
64
|
+
|
|
65
|
+
from = addresses(mail[:from]).first
|
|
66
|
+
raise ArgumentError, "mail is missing a from address" if from.nil?
|
|
67
|
+
to = addresses(mail[:to])
|
|
68
|
+
raise ArgumentError, "mail is missing to recipients" if to.empty?
|
|
69
|
+
|
|
70
|
+
payload = { "from" => from, "to" => to }
|
|
71
|
+
cc = addresses(mail[:cc])
|
|
72
|
+
payload["cc"] = cc unless cc.empty?
|
|
73
|
+
bcc = addresses(mail[:bcc])
|
|
74
|
+
payload["bcc"] = bcc unless bcc.empty?
|
|
75
|
+
payload["subject"] = mail.subject unless mail.subject.nil?
|
|
76
|
+
|
|
77
|
+
reply_to = addresses(mail[:reply_to])
|
|
78
|
+
payload["replyTo"] = reply_to.join(", ") unless reply_to.empty?
|
|
79
|
+
in_reply_to = Array(mail.in_reply_to)
|
|
80
|
+
unless in_reply_to.empty?
|
|
81
|
+
payload["inReplyTo"] = in_reply_to.map { |id| id.start_with?("<") ? id : "<#{id}>" }.join(" ")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
payload.merge(body_fields(mail)).merge(attachment_fields(mail))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# html/text from the message body: html_part/text_part when multipart,
|
|
88
|
+
# else the body under its own mime type. Any leftover MIME part that is
|
|
89
|
+
# neither the body nor an attachment has no API equivalent → raise.
|
|
90
|
+
def body_fields(mail)
|
|
91
|
+
fields = {}
|
|
92
|
+
if mail.multipart?
|
|
93
|
+
html = mail.html_part
|
|
94
|
+
text = mail.text_part
|
|
95
|
+
fields["html"] = html.decoded if html
|
|
96
|
+
fields["text"] = text.decoded if text
|
|
97
|
+
consumed = [html, text].compact + mail.attachments.to_a
|
|
98
|
+
leftover = mail.all_parts.reject { |p| p.multipart? || consumed.any? { |c| c.equal?(p) } }
|
|
99
|
+
unless leftover.empty?
|
|
100
|
+
raise UnsupportedFeatureError,
|
|
101
|
+
"the MailKite send API cannot express the #{leftover.first.mime_type} MIME part — only html/text bodies and attachments are supported"
|
|
102
|
+
end
|
|
103
|
+
else
|
|
104
|
+
body = mail.body.decoded
|
|
105
|
+
unless body.empty?
|
|
106
|
+
fields[mail.mime_type == "text/html" ? "html" : "text"] = body
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
fields
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Attachments become { filename, content: base64, contentType }. Inline
|
|
113
|
+
# (cid:) parts raise — the API has no Content-ID support, so embedded
|
|
114
|
+
# images would render broken.
|
|
115
|
+
def attachment_fields(mail)
|
|
116
|
+
return {} if mail.attachments.empty?
|
|
117
|
+
|
|
118
|
+
list = mail.attachments.map do |part|
|
|
119
|
+
if part.inline?
|
|
120
|
+
raise UnsupportedFeatureError,
|
|
121
|
+
"inline (cid:) attachments are not supported by the MailKite send API — attach the file normally or host the image at a URL"
|
|
122
|
+
end
|
|
123
|
+
{
|
|
124
|
+
"filename" => part.filename,
|
|
125
|
+
"content" => Base64.strict_encode64(part.body.decoded),
|
|
126
|
+
"contentType" => part.mime_type,
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
{ "attachments" => list }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# POST /v1/send takes no custom headers (the schema is
|
|
133
|
+
# additionalProperties: false) — refuse them instead of silently dropping.
|
|
134
|
+
def reject_custom_headers(mail)
|
|
135
|
+
mail.header.fields.each do |field|
|
|
136
|
+
name = field.name.to_s.downcase
|
|
137
|
+
next if HANDLED_HEADERS.include?(name)
|
|
138
|
+
|
|
139
|
+
raise UnsupportedFeatureError,
|
|
140
|
+
"the MailKite send API has no support for the #{field.name} header — remove it, or deliver this message over SMTP relay instead"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Formatted addresses for a header field (display names kept — the API
|
|
145
|
+
# parses RFC 5322 mailboxes); [] when the field is absent.
|
|
146
|
+
def addresses(field)
|
|
147
|
+
field.nil? ? [] : Array(field.formatted)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Docs: docs/integrations/rails-delivery-method.md
|
|
2
|
+
#
|
|
3
|
+
# Rails glue: registers :mailkite as an ActionMailer delivery method, so
|
|
4
|
+
# config.action_mailer.delivery_method = :mailkite
|
|
5
|
+
# just works. Loaded from lib/mailkite.rb only when Rails is present; outside
|
|
6
|
+
# Rails, register Mailkite::DeliveryMethod on `mail` directly.
|
|
7
|
+
|
|
8
|
+
module Mailkite
|
|
9
|
+
class Railtie < ::Rails::Railtie
|
|
10
|
+
initializer "mailkite.add_delivery_method", before: "action_mailer.set_configs" do
|
|
11
|
+
ActiveSupport.on_load(:action_mailer) do
|
|
12
|
+
ActionMailer::Base.add_delivery_method :mailkite, Mailkite::DeliveryMethod
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/mailkite.rb
CHANGED
|
@@ -15,7 +15,7 @@ require "base64"
|
|
|
15
15
|
require "cgi"
|
|
16
16
|
|
|
17
17
|
module Mailkite
|
|
18
|
-
VERSION = "0.
|
|
18
|
+
VERSION = "0.12.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
|
|
@@ -337,6 +337,10 @@ module Mailkite
|
|
|
337
337
|
request("POST", "/api/routes", body)
|
|
338
338
|
end
|
|
339
339
|
|
|
340
|
+
def deleteRoute(id)
|
|
341
|
+
request("DELETE", "/api/routes/#{id}")
|
|
342
|
+
end
|
|
343
|
+
|
|
340
344
|
# --- Templates ------------------------------------------------------
|
|
341
345
|
def listTemplates
|
|
342
346
|
request("GET", "/api/templates")
|
|
@@ -355,10 +359,11 @@ module Mailkite
|
|
|
355
359
|
end
|
|
356
360
|
|
|
357
361
|
# --- Messages & deliveries -----------------------------------------
|
|
358
|
-
def listMessages(before = nil, limit = nil)
|
|
362
|
+
def listMessages(before = nil, limit = nil, search = nil)
|
|
359
363
|
params = []
|
|
360
364
|
params << "before=#{CGI.escape(before.to_s)}" unless before.nil?
|
|
361
365
|
params << "limit=#{CGI.escape(limit.to_s)}" unless limit.nil?
|
|
366
|
+
params << "search=#{CGI.escape(search.to_s)}" unless search.nil?
|
|
362
367
|
path = "/api/messages"
|
|
363
368
|
path += "?#{params.join('&')}" unless params.empty?
|
|
364
369
|
request("GET", path)
|
|
@@ -475,3 +480,8 @@ module Mailkite
|
|
|
475
480
|
end
|
|
476
481
|
end
|
|
477
482
|
end
|
|
483
|
+
|
|
484
|
+
# ActionMailer / Mail integration (config.action_mailer.delivery_method = :mailkite).
|
|
485
|
+
# The `mail` gem is not a runtime dependency — DeliveryMethod requires it lazily.
|
|
486
|
+
require_relative "mailkite/delivery_method"
|
|
487
|
+
require_relative "mailkite/railtie" if defined?(::Rails::Railtie)
|
metadata
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mailkite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.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-
|
|
12
|
-
dependencies:
|
|
11
|
+
date: 2026-07-04 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: mail
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.7'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.7'
|
|
13
27
|
description: Send and manage email over your own authenticated domain with MailKite.
|
|
14
28
|
email:
|
|
15
29
|
executables: []
|
|
@@ -17,6 +31,8 @@ extensions: []
|
|
|
17
31
|
extra_rdoc_files: []
|
|
18
32
|
files:
|
|
19
33
|
- lib/mailkite.rb
|
|
34
|
+
- lib/mailkite/delivery_method.rb
|
|
35
|
+
- lib/mailkite/railtie.rb
|
|
20
36
|
homepage: https://mailkite.dev/docs/libraries
|
|
21
37
|
licenses:
|
|
22
38
|
- MIT
|