Some checks failed
Lock Threads / action (push) Has been cancelled
Replace SMTP with LeadMail API service for sending system transactional emails (password resets, invitations, notifications). LeadMail provides built-in email verification via Verifalia and async queue-based sending. Configuration: - Set LEADMAIL_API_TOKEN and LEADMAIL_API_URL in .env - Falls back to SMTP if LeadMail token not present - Works via custom ActionMailer delivery method (stable across upstream merges) Includes: - LeadmailDelivery class with full spec coverage - Support for multipart messages, attachments, CC/BCC - Error handling and logging with message tracking - Documentation in docs/LEADMAIL_INTEGRATION.md
181 lines
5.8 KiB
Ruby
181 lines
5.8 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe LeadmailDelivery do
|
|
let(:settings) do
|
|
{
|
|
api_url: 'https://mail.leadmagnet.dev/api/v1',
|
|
token: 'lm_test_token_123'
|
|
}
|
|
end
|
|
|
|
let(:delivery) { described_class.new(settings) }
|
|
|
|
let(:message) do
|
|
Mail.new do
|
|
from 'sender@example.com'
|
|
to 'recipient@example.com'
|
|
subject 'Test Subject'
|
|
body 'Plain text body'
|
|
end
|
|
end
|
|
|
|
describe '#deliver!' do
|
|
it 'sends email via LeadMail API' do
|
|
stub_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.with(
|
|
headers: { 'Authorization' => 'Bearer lm_test_token_123' },
|
|
body: hash_including(
|
|
subject: 'Test Subject',
|
|
from: { email: 'sender@example.com' },
|
|
to: [{ email: 'recipient@example.com' }]
|
|
)
|
|
)
|
|
.to_return(
|
|
status: 202,
|
|
body: { success: true, data: { log_id: 12345, status: 'queued' } }.to_json
|
|
)
|
|
|
|
delivery.deliver!(message)
|
|
|
|
expect(message.header['X-LeadMail-Log-ID'].value).to eq('12345')
|
|
end
|
|
|
|
it 'handles multipart messages (HTML + text)' do
|
|
html_message = Mail.new do
|
|
from 'sender@example.com'
|
|
to 'recipient@example.com'
|
|
subject 'HTML Email'
|
|
|
|
text_part do
|
|
body 'Plain text version'
|
|
end
|
|
|
|
html_part do
|
|
content_type 'text/html; charset=UTF-8'
|
|
body '<h1>HTML version</h1>'
|
|
end
|
|
end
|
|
|
|
stub_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.to_return(
|
|
status: 202,
|
|
body: { success: true, data: { log_id: 12346, status: 'queued' } }.to_json
|
|
)
|
|
|
|
delivery.deliver!(html_message)
|
|
|
|
expect(a_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.with { |req| body_hash = JSON.parse(req.body); body_hash['text_body'] == 'Plain text version' && body_hash['html_body'] == '<h1>HTML version</h1>' })
|
|
.to have_been_made
|
|
end
|
|
|
|
it 'includes CC and BCC recipients' do
|
|
message_with_cc = Mail.new do
|
|
from 'sender@example.com'
|
|
to 'recipient@example.com'
|
|
cc 'cc@example.com'
|
|
bcc 'bcc@example.com'
|
|
subject 'With CC/BCC'
|
|
body 'Test'
|
|
end
|
|
|
|
stub_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.to_return(
|
|
status: 202,
|
|
body: { success: true, data: { log_id: 12347, status: 'queued' } }.to_json
|
|
)
|
|
|
|
delivery.deliver!(message_with_cc)
|
|
|
|
expect(a_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.with { |req| body_hash = JSON.parse(req.body); body_hash['cc'] == [{ 'email' => 'cc@example.com' }] && body_hash['bcc'] == [{ 'email' => 'bcc@example.com' }] })
|
|
.to have_been_made
|
|
end
|
|
|
|
it 'handles reply-to header' do
|
|
message_with_reply = Mail.new do
|
|
from 'sender@example.com'
|
|
to 'recipient@example.com'
|
|
reply_to 'reply@example.com'
|
|
subject 'With Reply-To'
|
|
body 'Test'
|
|
end
|
|
|
|
stub_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.to_return(
|
|
status: 202,
|
|
body: { success: true, data: { log_id: 12348, status: 'queued' } }.to_json
|
|
)
|
|
|
|
delivery.deliver!(message_with_reply)
|
|
|
|
expect(a_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.with { |req| body_hash = JSON.parse(req.body); body_hash['reply_to'] == { 'email' => 'reply@example.com' } })
|
|
.to have_been_made
|
|
end
|
|
|
|
it 'encodes attachments in base64' do
|
|
message_with_attachment = Mail.new do
|
|
from 'sender@example.com'
|
|
to 'recipient@example.com'
|
|
subject 'With Attachment'
|
|
body 'Test'
|
|
add_file filename: 'test.txt', content: 'File content'
|
|
end
|
|
|
|
stub_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.to_return(
|
|
status: 202,
|
|
body: { success: true, data: { log_id: 12349, status: 'queued' } }.to_json
|
|
)
|
|
|
|
delivery.deliver!(message_with_attachment)
|
|
|
|
expect(a_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.with { |req| body_hash = JSON.parse(req.body); body_hash['attachments'].first['content'].present? })
|
|
.to have_been_made
|
|
end
|
|
|
|
it 'raises LeadmailDeliveryError on API failure' do
|
|
stub_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.to_return(status: 500, body: 'Internal Server Error')
|
|
|
|
expect { delivery.deliver!(message) }
|
|
.to raise_error(LeadmailDeliveryError, /LeadMail API error: 500/)
|
|
end
|
|
|
|
it 'sets default options for verification and disposable emails' do
|
|
stub_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.to_return(
|
|
status: 202,
|
|
body: { success: true, data: { log_id: 12350, status: 'queued' } }.to_json
|
|
)
|
|
|
|
delivery.deliver!(message)
|
|
|
|
expect(a_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.with { |req| body_hash = JSON.parse(req.body); body_hash['options']['on_verification_failure'] == 'strip' && body_hash['options']['allow_disposable'] == false })
|
|
.to have_been_made
|
|
end
|
|
|
|
it 'logs successful delivery' do
|
|
stub_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.to_return(
|
|
status: 202,
|
|
body: { success: true, data: { log_id: 12351, status: 'queued' } }.to_json
|
|
)
|
|
|
|
expect(Rails.logger).to receive(:info).with(match(/Email sent via LeadMail.*log_id=12351/))
|
|
delivery.deliver!(message)
|
|
end
|
|
|
|
it 'logs delivery errors' do
|
|
stub_request(:post, 'https://mail.leadmagnet.dev/api/v1/emails/send')
|
|
.to_raise(StandardError.new('Network error'))
|
|
|
|
expect(Rails.logger).to receive(:error).with(match(/LeadMail delivery failed/))
|
|
expect { delivery.deliver!(message) }.to raise_error(StandardError)
|
|
end
|
|
end
|
|
end
|