ms-graph-mailer 1.0.0 → 1.1.1
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/CHANGELOG.md +27 -0
- data/README.md +18 -0
- data/lib/ms/graph/mailer/delivery_method.rb +74 -15
- data/lib/ms/graph/mailer/errors.rb +62 -0
- data/lib/ms/graph/mailer/version.rb +1 -1
- data/lib/ms/graph/mailer.rb +7 -6
- metadata +15 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc38df909076fbf11baa641699ca2e813f683ab2dc95dc6c69c27b897bc85cd3
|
|
4
|
+
data.tar.gz: f48660fa9212b12343c92ef9a8786965fa8dfc3b86705c7c46cf86e339991557
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '09014fecd0b2d3a1d5fdd417c887acdd9b6580cccf5a8e19c38ee92e9a44634785ccb2cab4865e96cb2814c61da89bc9ba6bbe9826cde8b30bbd452a25154743'
|
|
7
|
+
data.tar.gz: 6ea5c55727621b5fad871add5d91dc18ec55fa31648718a33531227cfcac7162b3fa20cd14a46a2038d3d5e2cb7e614ebc95cf5670680d9baf8ab55c4d058855
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.1] - 2026-11-06
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- `In-Reply-To` and `References` headers are no longer dropped when sending.
|
|
13
|
+
The JSON `sendMail` payload cannot carry standard internet message headers,
|
|
14
|
+
so they are now mapped to their MAPI extended properties
|
|
15
|
+
(`PidTagInReplyToId` / `PidTagInternetReferences`), keeping replies threaded
|
|
16
|
+
in the recipient's mail client.
|
|
17
|
+
|
|
18
|
+
## [1.1.0] - 2026-11-06
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `MsGraphMailer::DeliveryError` exposes `status`, `code`, `retry_after`, `request_id` and
|
|
23
|
+
`client_request_id`, plus `throttled?` (429/503 — rejected, safe to retry) and
|
|
24
|
+
`outcome_unknown?` (other 5xx and network errors — the mail may have been sent).
|
|
25
|
+
- Every sendMail request carries a generated `client-request-id`; request ids and
|
|
26
|
+
`Retry-After` are logged on success and failure.
|
|
27
|
+
- `open_timeout` / `timeout` configuration options for the sendMail request.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Error classes moved to `lib/ms/graph/mailer/errors.rb`. Error messages are unchanged.
|
|
32
|
+
|
|
8
33
|
## [1.0.0] - 2025-12-15
|
|
9
34
|
|
|
10
35
|
### Added
|
|
@@ -43,5 +68,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
43
68
|
- Configuration system for tenant, client credentials
|
|
44
69
|
- Integration with Rails logger
|
|
45
70
|
|
|
71
|
+
[1.1.1]: https://github.com/zauberware/ms-graph-mailer/releases/tag/v1.1.1
|
|
72
|
+
[1.1.0]: https://github.com/zauberware/ms-graph-mailer/releases/tag/v1.1.0
|
|
46
73
|
[1.0.0]: https://github.com/zauberware/ms-graph-mailer/releases/tag/v1.0.0
|
|
47
74
|
[0.1.0]: https://github.com/zauberware/ms-graph-mailer/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -48,6 +48,9 @@ MsGraphMailer.configure do |config|
|
|
|
48
48
|
config.client_id = ENV['AZURE_MAIL_APP_CLIENT_ID']
|
|
49
49
|
config.client_secret = ENV['AZURE_MAIL_APP_CLIENT_SECRET']
|
|
50
50
|
config.logger = Rails.logger
|
|
51
|
+
# Optional: request timeouts in seconds (default: HTTP adapter defaults)
|
|
52
|
+
config.open_timeout = 10
|
|
53
|
+
config.timeout = 120
|
|
51
54
|
end
|
|
52
55
|
```
|
|
53
56
|
|
|
@@ -106,6 +109,21 @@ The gem raises specific exceptions for different error scenarios:
|
|
|
106
109
|
- `MsGraphMailer::AuthenticationError` - OAuth authentication failures
|
|
107
110
|
- `MsGraphMailer::DeliveryError` - Email delivery failures
|
|
108
111
|
|
|
112
|
+
`DeliveryError` carries the Graph response details so callers can decide whether to retry:
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
rescue MsGraphMailer::DeliveryError => e
|
|
116
|
+
e.status # => 503
|
|
117
|
+
e.code # => "CommandConcurrencyLimitReached"
|
|
118
|
+
e.retry_after # => 7 (seconds, from the Retry-After header; nil if absent)
|
|
119
|
+
e.request_id # => Graph request-id, for Microsoft support
|
|
120
|
+
e.client_request_id # => id the gem sent with the request (also set on timeouts)
|
|
121
|
+
|
|
122
|
+
e.throttled? # 429/503: Graph rejected the request, nothing was sent - safe to retry
|
|
123
|
+
e.outcome_unknown? # other 5xx or network error/timeout - the mail may have been sent
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
109
127
|
## Development
|
|
110
128
|
|
|
111
129
|
After checking out the repo, run:
|
|
@@ -4,6 +4,10 @@ module MsGraphMailer
|
|
|
4
4
|
class DeliveryMethod
|
|
5
5
|
include Dry::Monads[:result]
|
|
6
6
|
|
|
7
|
+
# PidTagInReplyToId (0x1042) / PidTagInternetReferences (0x1039)
|
|
8
|
+
IN_REPLY_TO_PROPERTY_ID = 'String 0x1042'
|
|
9
|
+
REFERENCES_PROPERTY_ID = 'String 0x1039'
|
|
10
|
+
|
|
7
11
|
attr_reader :settings
|
|
8
12
|
|
|
9
13
|
# INFO: settings is passed from config.action_mailer.<environment>
|
|
@@ -28,31 +32,56 @@ module MsGraphMailer
|
|
|
28
32
|
saveToSentItems: true
|
|
29
33
|
}
|
|
30
34
|
|
|
35
|
+
# Sent with the request and echoed back by Graph, so a failure can be
|
|
36
|
+
# traced with Microsoft even when no response arrives (timeouts).
|
|
37
|
+
client_request_id = SecureRandom.uuid
|
|
38
|
+
|
|
31
39
|
response = connection(token_result.value!).post("users/#{CGI.escape(sender_email)}/sendMail") do |req|
|
|
32
40
|
req.headers['Content-Type'] = 'application/json'
|
|
41
|
+
req.headers['client-request-id'] = client_request_id
|
|
42
|
+
req.headers['return-client-request-id'] = 'true'
|
|
33
43
|
req.body = message_payload.to_json
|
|
34
44
|
end
|
|
35
45
|
|
|
36
|
-
unless response.status == 202
|
|
37
|
-
error_body = begin
|
|
38
|
-
JSON.parse(response.body)
|
|
39
|
-
rescue StandardError
|
|
40
|
-
{}
|
|
41
|
-
end
|
|
42
|
-
error_msg = error_body.dig('error', 'message') || 'Unknown error'
|
|
43
|
-
logger.error("[MsGraphMailer] sendMail failed: [status: #{response.status}] - [body: #{response.body}]")
|
|
44
|
-
raise DeliveryError, "Microsoft Graph sendEmail failed with status #{response.status}: #{error_msg}"
|
|
45
|
-
end
|
|
46
|
+
raise build_delivery_error(response, client_request_id) unless response.status == 202
|
|
46
47
|
|
|
47
|
-
logger.info('[MsGraphMailer] Email sent successfully via Microsoft Graph'
|
|
48
|
+
logger.info('[MsGraphMailer] Email sent successfully via Microsoft Graph ' \
|
|
49
|
+
"[request-id: #{response.headers['request-id']}] [client-request-id: #{client_request_id}]")
|
|
48
50
|
response
|
|
49
51
|
rescue Faraday::Error => e
|
|
50
|
-
logger.error("[MsGraphMailer] Network error: #{e.message}")
|
|
51
|
-
raise DeliveryError
|
|
52
|
+
logger.error("[MsGraphMailer] Network error: #{e.message} [client-request-id: #{client_request_id}]")
|
|
53
|
+
raise DeliveryError.new("Network error sending email: #{e.message}",
|
|
54
|
+
client_request_id: client_request_id, network_error: true)
|
|
52
55
|
end
|
|
53
56
|
|
|
54
57
|
private
|
|
55
58
|
|
|
59
|
+
def build_delivery_error(response, client_request_id)
|
|
60
|
+
error = parse_error_body(response.body)
|
|
61
|
+
request_id = response.headers['request-id']
|
|
62
|
+
retry_after = DeliveryError.parse_retry_after(response.headers['Retry-After'])
|
|
63
|
+
|
|
64
|
+
logger.error("[MsGraphMailer] sendMail failed: [status: #{response.status}] [code: #{error['code']}] " \
|
|
65
|
+
"[request-id: #{request_id}] [client-request-id: #{client_request_id}] " \
|
|
66
|
+
"[retry-after: #{retry_after.inspect}] - [body: #{response.body}]")
|
|
67
|
+
|
|
68
|
+
DeliveryError.new(
|
|
69
|
+
"Microsoft Graph sendEmail failed with status #{response.status}: #{error['message'] || 'Unknown error'}",
|
|
70
|
+
status: response.status,
|
|
71
|
+
code: error['code'],
|
|
72
|
+
retry_after: retry_after,
|
|
73
|
+
request_id: request_id,
|
|
74
|
+
client_request_id: client_request_id
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def parse_error_body(body)
|
|
79
|
+
parsed = JSON.parse(body.to_s)
|
|
80
|
+
parsed.is_a?(Hash) && parsed['error'].is_a?(Hash) ? parsed['error'] : {}
|
|
81
|
+
rescue JSON::ParserError
|
|
82
|
+
{}
|
|
83
|
+
end
|
|
84
|
+
|
|
56
85
|
def validate_configuration!
|
|
57
86
|
config = MsGraphMailer.configuration
|
|
58
87
|
return if config&.valid?
|
|
@@ -76,14 +105,42 @@ module MsGraphMailer
|
|
|
76
105
|
ccRecipients: build_recipients(mail.cc),
|
|
77
106
|
bccRecipients: build_recipients(mail.bcc),
|
|
78
107
|
replyTo: build_recipients(mail.reply_to),
|
|
79
|
-
attachments: build_attachments(mail)
|
|
108
|
+
attachments: build_attachments(mail),
|
|
109
|
+
singleValueExtendedProperties: build_threading_properties(mail)
|
|
80
110
|
}.tap do |msg|
|
|
81
|
-
%i[toRecipients ccRecipients bccRecipients replyTo attachments].each do |field|
|
|
111
|
+
%i[toRecipients ccRecipients bccRecipients replyTo attachments singleValueExtendedProperties].each do |field|
|
|
82
112
|
msg.delete(field) if msg[field].blank?
|
|
83
113
|
end
|
|
84
114
|
end
|
|
85
115
|
end
|
|
86
116
|
|
|
117
|
+
# Graph's sendMail only accepts x-* custom headers via
|
|
118
|
+
# internetMessageHeaders, so the standard threading headers
|
|
119
|
+
# (In-Reply-To/References) would silently get lost. Mapping them to their
|
|
120
|
+
# MAPI extended properties makes Exchange emit them on the wire, keeping
|
|
121
|
+
# replies threaded in the recipient's client.
|
|
122
|
+
def build_threading_properties(mail)
|
|
123
|
+
properties = []
|
|
124
|
+
|
|
125
|
+
in_reply_to = format_message_ids(mail.in_reply_to)
|
|
126
|
+
references = format_message_ids(mail.references)
|
|
127
|
+
|
|
128
|
+
properties << { id: IN_REPLY_TO_PROPERTY_ID, value: in_reply_to } if in_reply_to
|
|
129
|
+
properties << { id: REFERENCES_PROPERTY_ID, value: references } if references
|
|
130
|
+
properties
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The mail gem parses message-id fields to bare ids (no angle brackets);
|
|
134
|
+
# the RFC 5322 header format Exchange writes needs them re-added.
|
|
135
|
+
def format_message_ids(ids)
|
|
136
|
+
formatted = Array(ids).compact.map(&:to_s).reject(&:empty?).map do |id|
|
|
137
|
+
id.start_with?('<') ? id : "<#{id}>"
|
|
138
|
+
end
|
|
139
|
+
return nil if formatted.empty?
|
|
140
|
+
|
|
141
|
+
formatted.join(' ')
|
|
142
|
+
end
|
|
143
|
+
|
|
87
144
|
def build_body(mail)
|
|
88
145
|
if mail.multipart?
|
|
89
146
|
html_part = mail.html_part
|
|
@@ -133,6 +190,8 @@ module MsGraphMailer
|
|
|
133
190
|
def connection(access_token)
|
|
134
191
|
@connection ||= Faraday.new(url: 'https://graph.microsoft.com/v1.0/') do |f|
|
|
135
192
|
f.headers['Authorization'] = "Bearer #{access_token}"
|
|
193
|
+
f.options.open_timeout = MsGraphMailer.configuration&.open_timeout
|
|
194
|
+
f.options.timeout = MsGraphMailer.configuration&.timeout
|
|
136
195
|
f.adapter Faraday.default_adapter
|
|
137
196
|
# SSL verification configuration
|
|
138
197
|
f.ssl.verify = ssl_verify?
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
module MsGraphMailer
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
class AuthenticationError < Error; end
|
|
8
|
+
class ConfigurationError < Error; end
|
|
9
|
+
|
|
10
|
+
# Raised when Microsoft Graph does not accept a sendMail request.
|
|
11
|
+
#
|
|
12
|
+
# Besides the message (kept stable for callers that persist or match it),
|
|
13
|
+
# the error exposes the response details a caller needs to decide whether a
|
|
14
|
+
# retry is safe:
|
|
15
|
+
#
|
|
16
|
+
# - throttled? Graph rejected the request (429, or 503 such as
|
|
17
|
+
# "CommandConcurrencyLimitReached"). Nothing was sent;
|
|
18
|
+
# retry after #retry_after seconds.
|
|
19
|
+
# - outcome_unknown? Gateway errors and network failures/timeouts. The
|
|
20
|
+
# mail may or may not have been sent — retrying blindly
|
|
21
|
+
# risks a duplicate.
|
|
22
|
+
class DeliveryError < Error
|
|
23
|
+
THROTTLED_STATUSES = [429, 503].freeze
|
|
24
|
+
|
|
25
|
+
attr_reader :status, :code, :retry_after, :request_id, :client_request_id
|
|
26
|
+
|
|
27
|
+
def initialize(message = nil, status: nil, code: nil, retry_after: nil, request_id: nil, # rubocop:disable Metrics/ParameterLists
|
|
28
|
+
client_request_id: nil, network_error: false)
|
|
29
|
+
super(message)
|
|
30
|
+
@status = status
|
|
31
|
+
@code = code
|
|
32
|
+
@retry_after = retry_after
|
|
33
|
+
@request_id = request_id
|
|
34
|
+
@client_request_id = client_request_id
|
|
35
|
+
@network_error = network_error
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def throttled?
|
|
39
|
+
THROTTLED_STATUSES.include?(status)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def outcome_unknown?
|
|
43
|
+
return true if @network_error
|
|
44
|
+
return false if status.nil? || throttled?
|
|
45
|
+
|
|
46
|
+
status >= 500
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Retry-After is either delta-seconds or an HTTP date (RFC 9110 §10.2.3).
|
|
50
|
+
# Returns whole seconds, or nil when the header is absent or unparsable.
|
|
51
|
+
def self.parse_retry_after(value, now: Time.now)
|
|
52
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
53
|
+
|
|
54
|
+
raw = value.to_s.strip
|
|
55
|
+
return raw.to_i if raw.match?(/\A\d+\z/)
|
|
56
|
+
|
|
57
|
+
[(Time.httpdate(raw) - now).ceil, 0].max
|
|
58
|
+
rescue ArgumentError
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/ms/graph/mailer.rb
CHANGED
|
@@ -6,17 +6,14 @@ require 'dry/monads'
|
|
|
6
6
|
require 'json'
|
|
7
7
|
require 'base64'
|
|
8
8
|
require 'cgi'
|
|
9
|
+
require 'securerandom'
|
|
9
10
|
|
|
10
11
|
require_relative 'mailer/version'
|
|
12
|
+
require_relative 'mailer/errors'
|
|
11
13
|
require_relative 'mailer/token_service'
|
|
12
14
|
require_relative 'mailer/delivery_method'
|
|
13
15
|
|
|
14
16
|
module MsGraphMailer
|
|
15
|
-
class Error < StandardError; end
|
|
16
|
-
class AuthenticationError < Error; end
|
|
17
|
-
class DeliveryError < Error; end
|
|
18
|
-
class ConfigurationError < Error; end
|
|
19
|
-
|
|
20
17
|
class << self
|
|
21
18
|
attr_accessor :configuration
|
|
22
19
|
|
|
@@ -27,13 +24,17 @@ module MsGraphMailer
|
|
|
27
24
|
end
|
|
28
25
|
|
|
29
26
|
class Configuration
|
|
30
|
-
|
|
27
|
+
# open_timeout / timeout (seconds) apply to the sendMail request. nil keeps
|
|
28
|
+
# the HTTP adapter's defaults.
|
|
29
|
+
attr_accessor :tenant_id, :client_id, :client_secret, :logger, :open_timeout, :timeout
|
|
31
30
|
|
|
32
31
|
def initialize
|
|
33
32
|
@tenant_id = nil
|
|
34
33
|
@client_id = nil
|
|
35
34
|
@client_secret = nil
|
|
36
35
|
@logger = nil
|
|
36
|
+
@open_timeout = nil
|
|
37
|
+
@timeout = nil
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def valid?
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ms-graph-mailer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Zauberware Technologies GmbH
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-11-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: actionmailer
|
|
@@ -80,104 +80,90 @@ dependencies:
|
|
|
80
80
|
- - ">="
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '0.3'
|
|
83
|
-
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: bundler
|
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - "~>"
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: '2.0'
|
|
90
|
-
type: :development
|
|
91
|
-
prerelease: false
|
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
-
requirements:
|
|
94
|
-
- - "~>"
|
|
95
|
-
- !ruby/object:Gem::Version
|
|
96
|
-
version: '2.0'
|
|
97
83
|
- !ruby/object:Gem::Dependency
|
|
98
84
|
name: rake
|
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
|
100
86
|
requirements:
|
|
101
87
|
- - "~>"
|
|
102
88
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '13.
|
|
89
|
+
version: '13.4'
|
|
104
90
|
type: :development
|
|
105
91
|
prerelease: false
|
|
106
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
93
|
requirements:
|
|
108
94
|
- - "~>"
|
|
109
95
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '13.
|
|
96
|
+
version: '13.4'
|
|
111
97
|
- !ruby/object:Gem::Dependency
|
|
112
98
|
name: rspec
|
|
113
99
|
requirement: !ruby/object:Gem::Requirement
|
|
114
100
|
requirements:
|
|
115
101
|
- - "~>"
|
|
116
102
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: '3.
|
|
103
|
+
version: '3.13'
|
|
118
104
|
type: :development
|
|
119
105
|
prerelease: false
|
|
120
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
107
|
requirements:
|
|
122
108
|
- - "~>"
|
|
123
109
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: '3.
|
|
110
|
+
version: '3.13'
|
|
125
111
|
- !ruby/object:Gem::Dependency
|
|
126
112
|
name: rubocop
|
|
127
113
|
requirement: !ruby/object:Gem::Requirement
|
|
128
114
|
requirements:
|
|
129
115
|
- - "~>"
|
|
130
116
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: '1.
|
|
117
|
+
version: '1.92'
|
|
132
118
|
type: :development
|
|
133
119
|
prerelease: false
|
|
134
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
121
|
requirements:
|
|
136
122
|
- - "~>"
|
|
137
123
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: '1.
|
|
124
|
+
version: '1.92'
|
|
139
125
|
- !ruby/object:Gem::Dependency
|
|
140
126
|
name: rubocop-rake
|
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
|
142
128
|
requirements:
|
|
143
129
|
- - "~>"
|
|
144
130
|
- !ruby/object:Gem::Version
|
|
145
|
-
version: '0.
|
|
131
|
+
version: '0.7'
|
|
146
132
|
type: :development
|
|
147
133
|
prerelease: false
|
|
148
134
|
version_requirements: !ruby/object:Gem::Requirement
|
|
149
135
|
requirements:
|
|
150
136
|
- - "~>"
|
|
151
137
|
- !ruby/object:Gem::Version
|
|
152
|
-
version: '0.
|
|
138
|
+
version: '0.7'
|
|
153
139
|
- !ruby/object:Gem::Dependency
|
|
154
140
|
name: rubocop-rspec
|
|
155
141
|
requirement: !ruby/object:Gem::Requirement
|
|
156
142
|
requirements:
|
|
157
143
|
- - "~>"
|
|
158
144
|
- !ruby/object:Gem::Version
|
|
159
|
-
version: '
|
|
145
|
+
version: '3.10'
|
|
160
146
|
type: :development
|
|
161
147
|
prerelease: false
|
|
162
148
|
version_requirements: !ruby/object:Gem::Requirement
|
|
163
149
|
requirements:
|
|
164
150
|
- - "~>"
|
|
165
151
|
- !ruby/object:Gem::Version
|
|
166
|
-
version: '
|
|
152
|
+
version: '3.10'
|
|
167
153
|
- !ruby/object:Gem::Dependency
|
|
168
154
|
name: webmock
|
|
169
155
|
requirement: !ruby/object:Gem::Requirement
|
|
170
156
|
requirements:
|
|
171
157
|
- - "~>"
|
|
172
158
|
- !ruby/object:Gem::Version
|
|
173
|
-
version: '3.
|
|
159
|
+
version: '3.34'
|
|
174
160
|
type: :development
|
|
175
161
|
prerelease: false
|
|
176
162
|
version_requirements: !ruby/object:Gem::Requirement
|
|
177
163
|
requirements:
|
|
178
164
|
- - "~>"
|
|
179
165
|
- !ruby/object:Gem::Version
|
|
180
|
-
version: '3.
|
|
166
|
+
version: '3.34'
|
|
181
167
|
description: A custom delivery method for ActionMailer that sends emails via Microsoft
|
|
182
168
|
Graph API using OAuth 2.0 client credentials flow
|
|
183
169
|
email:
|
|
@@ -191,6 +177,7 @@ files:
|
|
|
191
177
|
- README.md
|
|
192
178
|
- lib/ms/graph/mailer.rb
|
|
193
179
|
- lib/ms/graph/mailer/delivery_method.rb
|
|
180
|
+
- lib/ms/graph/mailer/errors.rb
|
|
194
181
|
- lib/ms/graph/mailer/token_service.rb
|
|
195
182
|
- lib/ms/graph/mailer/version.rb
|
|
196
183
|
homepage: https://github.com/zauberware/ms-graph-mailer
|