ms-graph-mailer 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6b3570e71f901eb9a4113918ff258b9027f1cf1c06fe8cd01f479761aec65ab4
4
+ data.tar.gz: 26faf075b68963e20d478fd5813eb384a848a6beeb1467ed2eae16da1c166199
5
+ SHA512:
6
+ metadata.gz: ba486608590ae404c6bc89056b1792664e23023de730776241f0c4ee53d858cbc72aaba869bf76722c66c78eadd7ebc2d68fda4712d0a44c9ee00377dbf72265
7
+ data.tar.gz: '097c8be2986508a0c0cf8182358135d9ca0490c6a81380ac3a9c66d668c4dee4f9e498c1e230f429fc389dac022cb9dcb955c12e5d8d2e0e500d2e4f84e59aee'
data/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2025-12-15
9
+
10
+ ### Added
11
+
12
+ - Initial release of ms-graph-mailer gem
13
+ - ActionMailer delivery method for Microsoft Graph API
14
+ - OAuth 2.0 client credentials flow authentication
15
+ - Support for HTML and plain text emails
16
+ - Support for attachments (file attachments)
17
+ - Support for CC, BCC, and Reply-To recipients
18
+ - Comprehensive error handling with custom exception classes
19
+ - Configurable SSL verification
20
+ - Logging support
21
+ - Complete documentation and examples
22
+
23
+ ### Features
24
+
25
+ - `MsGraphMailer::DeliveryMethod` - Main delivery method class
26
+ - `MsGraphMailer::TokenService` - OAuth token management
27
+ - Configuration system for tenant, client credentials
28
+ - Integration with Rails logger
29
+
30
+ [0.1.0]: https://github.com/zauberware/ms-graph-mailer/releases/tag/v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Zauberware Technologies GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # Ms Graph Mailer
2
+
3
+ A custom ActionMailer delivery method that sends emails via Microsoft Graph API using OAuth 2.0 client credentials flow.
4
+
5
+ ## Features
6
+
7
+ - 🚀 Send emails through Microsoft Graph API (v1.0)
8
+ - 🔐 OAuth 2.0 authentication with client credentials flow
9
+ - 📎 Support for attachments
10
+ - 📧 Support for HTML and plain text emails
11
+ - 📬 Support for CC, BCC, and Reply-To recipients
12
+ - 🔍 Comprehensive error handling and logging
13
+ - ⚙️ Configurable SSL verification
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'ms-graph-mailer', git: 'https://github.com/zauberware/ms-graph-mailer'
21
+ ```
22
+
23
+ Then execute:
24
+
25
+ ```bash
26
+ bundle install
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ ### Azure AD App Setup
32
+
33
+ 1. Register an application in Azure Active Directory
34
+ 2. Grant the application `Mail.Send` API permission (Application permission, not delegated)
35
+ 3. Generate a client secret
36
+ 4. Note down:
37
+ - Tenant ID
38
+ - Client ID (Application ID)
39
+ - Client Secret
40
+
41
+ ### Rails Configuration
42
+
43
+ Create an initializer file `config/initializers/ms_graph_mailer.rb`:
44
+
45
+ ```ruby
46
+ MsGraphMailer.configure do |config|
47
+ config.tenant_id = ENV['AZURE_MAIL_APP_TENANT_ID']
48
+ config.client_id = ENV['AZURE_MAIL_APP_CLIENT_ID']
49
+ config.client_secret = ENV['AZURE_MAIL_APP_CLIENT_SECRET']
50
+ config.logger = Rails.logger
51
+ end
52
+ ```
53
+
54
+ Configure ActionMailer to use the Microsoft Graph delivery method:
55
+
56
+ ```ruby
57
+ # config/environments/production.rb
58
+ config.action_mailer.delivery_method = :microsoft_graph
59
+ config.action_mailer.microsoft_graph_settings = {
60
+ ssl_verify: true # Set to false to disable SSL verification (not recommended for production)
61
+ }
62
+ ```
63
+
64
+ ### Environment Variables
65
+
66
+ Set the following environment variables:
67
+
68
+ ```bash
69
+ AZURE_MAIL_APP_TENANT_ID=your-tenant-id
70
+ AZURE_MAIL_APP_CLIENT_ID=your-client-id
71
+ AZURE_MAIL_APP_CLIENT_SECRET=your-client-secret
72
+ ```
73
+
74
+ ## Usage
75
+
76
+ Once configured, the gem works automatically with ActionMailer. Just send emails as you normally would:
77
+
78
+ ```ruby
79
+ require 'ms/graph/mailer'
80
+
81
+ class UserMailer < ApplicationMailer
82
+ def welcome_email(user)
83
+ mail(
84
+ to: user.email,
85
+ subject: 'Welcome to Our App',
86
+ from: 'noreply@example.com'
87
+ )
88
+ end
89
+ end
90
+
91
+ # Send the email
92
+ UserMailer.welcome_email(user).deliver_now
93
+ ```
94
+
95
+ ### Important Notes
96
+
97
+ - The `from` email address must be a valid mailbox in your Microsoft 365 tenant
98
+ - The Azure AD application must have the necessary permissions to send emails on behalf of the sender
99
+ - Emails are sent using the `/users/{sender}/sendMail` endpoint
100
+
101
+ ## Error Handling
102
+
103
+ The gem raises specific exceptions for different error scenarios:
104
+
105
+ - `MsGraphMailer::ConfigurationError` - Invalid or missing configuration
106
+ - `MsGraphMailer::AuthenticationError` - OAuth authentication failures
107
+ - `MsGraphMailer::DeliveryError` - Email delivery failures
108
+
109
+ ## Development
110
+
111
+ After checking out the repo, run:
112
+
113
+ ```bash
114
+ bundle install
115
+ ```
116
+
117
+ Run tests:
118
+
119
+ ```bash
120
+ bundle exec rspec
121
+ ```
122
+
123
+ Run RuboCop:
124
+
125
+ ```bash
126
+ bundle exec rubocop
127
+ ```
128
+
129
+ ## Contributing
130
+
131
+ Bug reports and pull requests are welcome on GitHub at https://github.com/zauberware/ms-graph-mailer.
132
+
133
+ ## License
134
+
135
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
136
+
137
+ ## Credits
138
+
139
+ Developed by [Zauberware Technologies GmbH](https://zauberware.com)
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MsGraphMailer
4
+ class DeliveryMethod
5
+ include Dry::Monads[:result]
6
+
7
+ attr_reader :settings
8
+
9
+ # INFO: settings is passed from config.action_mailer.<environment>
10
+ def initialize(settings)
11
+ @settings = settings
12
+ validate_configuration!
13
+ end
14
+
15
+ def deliver!(mail)
16
+ sender_email = extract_sender_email(mail)
17
+ raise DeliveryError, 'No sender email (From) specified in the email.' if sender_email.blank?
18
+
19
+ token_result = TokenService.new.call
20
+
21
+ if token_result.failure?
22
+ logger.error("Failed to get Graph token: #{token_result.failure}")
23
+ raise AuthenticationError, "Unable to authenticate with Microsoft Graph: #{token_result.failure}"
24
+ end
25
+
26
+ message_payload = {
27
+ message: build_message_payload(mail, sender_email),
28
+ saveToSentItems: true
29
+ }
30
+
31
+ response = connection(token_result.value!).post("users/#{CGI.escape(sender_email)}/sendMail") do |req|
32
+ req.headers['Content-Type'] = 'application/json'
33
+ req.body = message_payload.to_json
34
+ end
35
+
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
+
47
+ logger.info('[MsGraphMailer] Email sent successfully via Microsoft Graph')
48
+ response
49
+ rescue Faraday::Error => e
50
+ logger.error("[MsGraphMailer] Network error: #{e.message}")
51
+ raise DeliveryError, "Network error sending email: #{e.message}"
52
+ end
53
+
54
+ private
55
+
56
+ def validate_configuration!
57
+ config = MsGraphMailer.configuration
58
+ return if config&.valid?
59
+
60
+ raise ConfigurationError,
61
+ 'MsGraphMailer is not properly configured. Please configure tenant_id, client_id, and client_secret.'
62
+ end
63
+
64
+ def extract_sender_email(mail)
65
+ Array(mail.from).first.to_s.strip
66
+ end
67
+
68
+ def build_message_payload(mail, sender_email)
69
+ {
70
+ subject: mail.subject.to_s,
71
+ body: build_body(mail),
72
+ from: {
73
+ emailAddress: { address: sender_email }
74
+ },
75
+ toRecipients: build_recipients(mail.to),
76
+ ccRecipients: build_recipients(mail.cc),
77
+ bccRecipients: build_recipients(mail.bcc),
78
+ replyTo: build_recipients(mail.reply_to),
79
+ attachments: build_attachments(mail)
80
+ }.tap do |msg|
81
+ %i[toRecipients ccRecipients bccRecipients replyTo attachments].each do |field|
82
+ msg.delete(field) if msg[field].blank?
83
+ end
84
+ end
85
+ end
86
+
87
+ def build_body(mail)
88
+ if mail.multipart?
89
+ html_part = mail.html_part
90
+ text_part = mail.text_part
91
+
92
+ if html_part
93
+ {
94
+ contentType: 'HTML',
95
+ content: html_part.body.decoded
96
+ }
97
+ elsif text_part
98
+ {
99
+ contentType: 'Text',
100
+ content: text_part.body.decoded
101
+ }
102
+ else
103
+ {
104
+ contentType: 'Text',
105
+ content: ''
106
+ }
107
+ end
108
+ else
109
+ {
110
+ contentType: mail.content_type&.start_with?('text/html') ? 'HTML' : 'Text',
111
+ content: mail.body.decoded
112
+ }
113
+ end
114
+ end
115
+
116
+ def build_recipients(addresses)
117
+ Array(addresses).reject(&:blank?).map do |address|
118
+ { emailAddress: { address: address.to_s.strip } }
119
+ end
120
+ end
121
+
122
+ def build_attachments(mail)
123
+ mail.attachments.map do |attachment|
124
+ {
125
+ '@odata.type': '#microsoft.graph.fileAttachment',
126
+ name: attachment.filename.to_s,
127
+ contentType: attachment.mime_type.to_s,
128
+ contentBytes: Base64.strict_encode64(attachment.body.decoded)
129
+ }
130
+ end
131
+ end
132
+
133
+ def connection(access_token)
134
+ @connection ||= Faraday.new(url: 'https://graph.microsoft.com/v1.0/') do |f|
135
+ f.headers['Authorization'] = "Bearer #{access_token}"
136
+ f.adapter Faraday.default_adapter
137
+ # SSL verification configuration
138
+ f.ssl.verify = ssl_verify?
139
+ end
140
+ end
141
+
142
+ def ssl_verify?
143
+ # Allow disabling SSL verification via settings
144
+ settings[:ssl_verify].nil? || settings[:ssl_verify]
145
+ end
146
+
147
+ def logger
148
+ MsGraphMailer.configuration&.logger || Logger.new($stdout)
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MsGraphMailer
4
+ class TokenService
5
+ include Dry::Monads[:result]
6
+
7
+ def call
8
+ fetch_token
9
+ end
10
+
11
+ private
12
+
13
+ def cached_token
14
+ logger.info('Checking for cached Microsoft Graph token...')
15
+ token = Rails.cache.read('microsoft_graph_token')
16
+ if token.present?
17
+ logger.info('Found valid cached Microsoft Graph token.')
18
+ else
19
+ logger.info('No valid cached Microsoft Graph token found.')
20
+ end
21
+ token
22
+ end
23
+
24
+ def fetch_token
25
+ validate_configuration!
26
+ token = cached_token
27
+ return Success(token) if token.present?
28
+
29
+ scope = 'https://graph.microsoft.com/.default'
30
+
31
+ logger.info("Attempting to fetch Microsoft Graph token for tenant: #{config.tenant_id}...")
32
+
33
+ response = connection.post("#{config.tenant_id}/oauth2/v2.0/token") do |req|
34
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
35
+ req.body = {
36
+ client_id: config.client_id,
37
+ client_secret: config.client_secret,
38
+ grant_type: 'client_credentials',
39
+ scope: scope
40
+ }
41
+ end
42
+
43
+ logger.info("Graph token response status: #{response.status}")
44
+
45
+ body = JSON.parse(response.body)
46
+
47
+ if response.success? && body['access_token'].present?
48
+ access_token = body['access_token']
49
+ expires_in = body['expires_in'] || 3600 # Default to 1 hour
50
+ Rails.cache.write('microsoft_graph_token', access_token, expires_in: expires_in - 300)
51
+
52
+ logger.info("Successfully obtained and cached Graph token (expires in #{expires_in}s)")
53
+ Success(access_token)
54
+ else
55
+ error_msg = body['error_description'] || body['error'] || 'Unknown error'
56
+ logger.error("Failed to fetch Graph token - Status: #{response.status}, Error: #{error_msg}")
57
+ logger.error("Full response body: #{body}")
58
+ Failure("Failed to fetch Graph token: #{error_msg}")
59
+ end
60
+ rescue JSON::ParserError => e
61
+ logger.error("JSON parse error for Graph token response: #{e.message}")
62
+ logger.error("Response body: #{response&.body}")
63
+ Failure("Invalid response format: #{e.message}")
64
+ rescue Faraday::Error => e
65
+ logger.error("Faraday error fetching Graph token: #{e.message}")
66
+ Failure("Network error fetching Graph token: #{e.message}")
67
+ rescue StandardError => e
68
+ logger.error("Unexpected error fetching Graph token: #{e.message}")
69
+ Failure("Unexpected error: #{e.message}")
70
+ end
71
+
72
+ def validate_configuration!
73
+ return if config&.valid?
74
+
75
+ raise ConfigurationError,
76
+ 'MsGraphMailer is not properly configured. Please configure tenant_id, client_id, and client_secret.'
77
+ end
78
+
79
+ def connection
80
+ @connection ||= Faraday.new(url: 'https://login.microsoftonline.com') do |f|
81
+ f.request :url_encoded
82
+ f.adapter Faraday.default_adapter
83
+ f.ssl.verify = ssl_verify?
84
+ end
85
+ end
86
+
87
+ def ssl_verify?
88
+ # Default to true in production environments
89
+ return false if defined?(Rails) && Rails.env.development?
90
+
91
+ true
92
+ end
93
+
94
+ def config
95
+ MsGraphMailer.configuration
96
+ end
97
+
98
+ def logger
99
+ config&.logger || Logger.new($stdout)
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MsGraphMailer
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_mailer'
4
+ require 'faraday'
5
+ require 'dry/monads'
6
+ require 'json'
7
+ require 'base64'
8
+ require 'cgi'
9
+
10
+ require_relative 'mailer/version'
11
+ require_relative 'mailer/token_service'
12
+ require_relative 'mailer/delivery_method'
13
+
14
+ module MsGraphMailer
15
+ class Error < StandardError; end
16
+ class AuthenticationError < Error; end
17
+ class DeliveryError < Error; end
18
+ class ConfigurationError < Error; end
19
+
20
+ class << self
21
+ attr_accessor :configuration
22
+
23
+ def configure
24
+ self.configuration ||= Configuration.new
25
+ yield(configuration)
26
+ end
27
+ end
28
+
29
+ class Configuration
30
+ attr_accessor :tenant_id, :client_id, :client_secret, :logger
31
+
32
+ def initialize
33
+ @tenant_id = nil
34
+ @client_id = nil
35
+ @client_secret = nil
36
+ @logger = nil
37
+ end
38
+
39
+ def valid?
40
+ tenant_id.present? && client_id.present? && client_secret.present?
41
+ end
42
+ end
43
+ end
44
+
45
+ # Register the delivery method with ActionMailer
46
+ ActionMailer::Base.add_delivery_method(
47
+ :microsoft_graph,
48
+ MsGraphMailer::DeliveryMethod
49
+ )
metadata ADDED
@@ -0,0 +1,208 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ms-graph-mailer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Zauberware Technologies GmbH
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-12-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: actionmailer
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '6.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '6.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-monads
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: faraday
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '13.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '13.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop-rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.6'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.6'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop-rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '2.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: webmock
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '3.0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '3.0'
167
+ description: A custom delivery method for ActionMailer that sends emails via Microsoft
168
+ Graph API using OAuth 2.0 client credentials flow
169
+ email:
170
+ - tech@zauberware.com
171
+ executables: []
172
+ extensions: []
173
+ extra_rdoc_files: []
174
+ files:
175
+ - CHANGELOG.md
176
+ - LICENSE.txt
177
+ - README.md
178
+ - lib/ms/graph/mailer.rb
179
+ - lib/ms/graph/mailer/delivery_method.rb
180
+ - lib/ms/graph/mailer/token_service.rb
181
+ - lib/ms/graph/mailer/version.rb
182
+ homepage: https://github.com/zauberware/ms-graph-mailer
183
+ licenses:
184
+ - MIT
185
+ metadata:
186
+ homepage_uri: https://github.com/zauberware/ms-graph-mailer
187
+ source_code_uri: https://github.com/zauberware/ms-graph-mailer
188
+ changelog_uri: https://github.com/zauberware/ms-graph-mailer/blob/main/CHANGELOG.md
189
+ post_install_message:
190
+ rdoc_options: []
191
+ require_paths:
192
+ - lib
193
+ required_ruby_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ version: 2.7.0
198
+ required_rubygems_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ requirements: []
204
+ rubygems_version: 3.5.16
205
+ signing_key:
206
+ specification_version: 4
207
+ summary: ActionMailer delivery method for Microsoft Graph API
208
+ test_files: []