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 +7 -0
- data/CHANGELOG.md +30 -0
- data/LICENSE.txt +21 -0
- data/README.md +139 -0
- data/lib/ms/graph/mailer/delivery_method.rb +151 -0
- data/lib/ms/graph/mailer/token_service.rb +102 -0
- data/lib/ms/graph/mailer/version.rb +5 -0
- data/lib/ms/graph/mailer.rb +49 -0
- metadata +208 -0
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,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: []
|