Ruby 发送邮件 - SMTP
在现代应用程序中,发送邮件是一个常见的需求,比如用户注册确认、密码重置、通知等。Ruby提供了多种方式来发送邮件,其中最常用的是通过SMTP(Simple Mail Transfer Protocol)协议。本章将详细介绍如何在Ruby中使用SMTP发送邮件。
🎯 SMTP基础
什么是SMTP
SMTP(Simple Mail Transfer Protocol)是用于发送电子邮件的标准协议。它定义了邮件服务器之间以及客户端与服务器之间如何传输邮件。在Ruby中,我们可以使用内置的[Net::SMTP](file:///D:/Workspace/Coding/VueProjects/tutorials-web/docs/ruby/../../../../../../Ruby30-x64/lib/ruby/3.0.0/net/smtp.rb#L89-L774)库来发送邮件。
SMTP工作原理
SMTP发送邮件的基本流程:
- 连接到SMTP服务器
- 进行身份验证(如果需要)
- 指定发件人和收件人
- 发送邮件内容
- 关闭连接
📧 使用Net::SMTP发送邮件
基本邮件发送
ruby
require 'net/smtp'
# SMTP服务器配置
smtp_server = 'smtp.gmail.com'
port = 587
username = 'your_email@gmail.com'
password = 'your_password'
# 邮件内容
from = 'your_email@gmail.com'
to = 'recipient@example.com'
subject = '测试邮件'
body = "这是一封测试邮件\n\n来自Ruby应用程序"
# 创建邮件消息
message = <<~MESSAGE
From: #{from}
To: #{to}
Subject: #{subject}
#{body}
MESSAGE
# 发送邮件
Net::SMTP.start(smtp_server, port, 'localhost', username, password, :plain) do |smtp|
smtp.send_message(message, from, to)
end
puts "邮件发送成功!"发送HTML邮件
ruby
require 'net/smtp'
def send_html_email(smtp_server, port, username, password, from, to, subject, html_body)
# 创建HTML邮件消息
message = <<~MESSAGE
From: #{from}
To: #{to}
Subject: #{subject}
Content-Type: text/html; charset=UTF-8
#{html_body}
MESSAGE
# 发送邮件
Net::SMTP.start(smtp_server, port, 'localhost', username, password, :plain) do |smtp|
smtp.send_message(message, from, to)
end
end
# 使用示例
smtp_server = 'smtp.gmail.com'
port = 587
username = 'your_email@gmail.com'
password = 'your_password'
from = 'your_email@gmail.com'
to = 'recipient@example.com'
subject = 'HTML测试邮件'
html_body = <<~HTML
<html>
<body>
<h1>欢迎使用我们的服务</h1>
<p>这是一封HTML格式的邮件</p>
<ul>
<li>功能1</li>
<li>功能2</li>
<li>功能3</li>
</ul>
<p>感谢您的使用!</p>
</body>
</html>
HTML
send_html_email(smtp_server, port, username, password, from, to, subject, html_body)
puts "HTML邮件发送成功!"发送带附件的邮件
ruby
require 'net/smtp'
require 'base64'
def send_email_with_attachment(smtp_server, port, username, password, from, to, subject, body, attachment_path)
# 读取附件
filename = File.basename(attachment_path)
file_content = File.read(attachment_path, mode: 'rb')
encoded_content = Base64.encode64(file_content).gsub(/\n/, "\n")
# 创建带附件的邮件
boundary = "----=_NextPart_#{Time.now.to_i}_#{rand(1000000)}"
message = <<~MESSAGE
From: #{from}
To: #{to}
Subject: #{subject}
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="#{boundary}"
--#{boundary}
Content-Type: text/plain; charset=UTF-8
#{body}
--#{boundary}
Content-Type: application/octet-stream; name="#{filename}"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="#{filename}"
#{encoded_content}
--#{boundary}--
MESSAGE
# 发送邮件
Net::SMTP.start(smtp_server, port, 'localhost', username, password, :plain) do |smtp|
smtp.send_message(message, from, to)
end
end
# 使用示例
smtp_server = 'smtp.gmail.com'
port = 587
username = 'your_email@gmail.com'
password = 'your_password'
from = 'your_email@gmail.com'
to = 'recipient@example.com'
subject = '带附件的邮件'
body = "这是一封带附件的邮件\n\n请查看附件内容。"
attachment_path = 'path/to/your/file.pdf'
# send_email_with_attachment(smtp_server, port, username, password, from, to, subject, body, attachment_path)
puts "带附件的邮件发送成功!"🛠️ 邮件配置管理
配置类
ruby
class EmailConfig
attr_accessor :smtp_server, :port, :username, :password, :domain
def initialize(smtp_server, port, username, password, domain = 'localhost')
@smtp_server = smtp_server
@port = port
@username = username
@password = password
@domain = domain
end
# 常用邮件服务商配置
def self.gmail(username, password)
new('smtp.gmail.com', 587, username, password, 'localhost')
end
def self.outlook(username, password)
new('smtp-mail.outlook.com', 587, username, password, 'localhost')
end
def self.yahoo(username, password)
new('smtp.mail.yahoo.com', 587, username, password, 'localhost')
end
def self.qq(username, password)
new('smtp.qq.com', 587, username, password, 'localhost')
end
end
# 使用示例
gmail_config = EmailConfig.gmail('your_email@gmail.com', 'your_password')
puts gmail_config.smtp_server # smtp.gmail.com
puts gmail_config.port # 587邮件发送器类
ruby
require 'net/smtp'
require 'base64'
class EmailSender
def initialize(config)
@config = config
end
def send_email(to, subject, body, options = {})
from = options[:from] || @config.username
content_type = options[:content_type] || 'text/plain'
attachments = options[:attachments] || []
message = build_message(from, to, subject, body, content_type, attachments)
Net::SMTP.start(
@config.smtp_server,
@config.port,
@config.domain,
@config.username,
@config.password,
:plain
) do |smtp|
smtp.send_message(message, from, to)
end
end
private
def build_message(from, to, subject, body, content_type, attachments)
if attachments.empty?
build_simple_message(from, to, subject, body, content_type)
else
build_multipart_message(from, to, subject, body, content_type, attachments)
end
end
def build_simple_message(from, to, subject, body, content_type)
<<~MESSAGE
From: #{from}
To: #{to}
Subject: #{subject}
Content-Type: #{content_type}; charset=UTF-8
#{body}
MESSAGE
end
def build_multipart_message(from, to, subject, body, content_type, attachments)
boundary = "----=_NextPart_#{Time.now.to_i}_#{rand(1000000)}"
message = <<~MESSAGE
From: #{from}
To: #{to}
Subject: #{subject}
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="#{boundary}"
--#{boundary}
Content-Type: #{content_type}; charset=UTF-8
#{body}
MESSAGE
attachments.each do |attachment_path|
filename = File.basename(attachment_path)
file_content = File.read(attachment_path, mode: 'rb')
encoded_content = Base64.encode64(file_content).gsub(/\n/, "\n")
message += <<~ATTACHMENT
--#{boundary}
Content-Type: application/octet-stream; name="#{filename}"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="#{filename}"
#{encoded_content}
ATTACHMENT
end
message + "\n--#{boundary}--"
end
end
# 使用示例
config = EmailConfig.gmail('your_email@gmail.com', 'your_password')
sender = EmailSender.new(config)
# 发送简单邮件
sender.send_email(
'recipient@example.com',
'测试邮件',
'这是一封测试邮件'
)
# 发送HTML邮件
sender.send_email(
'recipient@example.com',
'HTML邮件',
'<h1>HTML邮件</h1><p>这是一封HTML邮件</p>',
content_type: 'text/html'
)
# 发送带附件的邮件
# sender.send_email(
# 'recipient@example.com',
# '带附件的邮件',
# '请查看附件',
# attachments: ['path/to/file1.pdf', 'path/to/file2.jpg']
# )🔐 安全处理
环境变量管理敏感信息
ruby
# .env文件内容示例
# SMTP_USERNAME=your_email@gmail.com
# SMTP_PASSWORD=your_app_password
# SMTP_SERVER=smtp.gmail.com
# SMTP_PORT=587
require 'net/smtp'
class SecureEmailSender
def initialize
@username = ENV['SMTP_USERNAME']
@password = ENV['SMTP_PASSWORD']
@smtp_server = ENV['SMTP_SERVER'] || 'smtp.gmail.com'
@port = (ENV['SMTP_PORT'] || 587).to_i
end
def send_email(to, subject, body)
raise "SMTP配置不完整" unless [@username, @password, @smtp_server, @port].all?
from = @username
message = <<~MESSAGE
From: #{from}
To: #{to}
Subject: #{subject}
Content-Type: text/plain; charset=UTF-8
#{body}
MESSAGE
Net::SMTP.start(@smtp_server, @port, 'localhost', @username, @password, :plain) do |smtp|
smtp.send_message(message, from, to)
end
rescue => e
puts "邮件发送失败: #{e.message}"
false
end
end
# 使用示例
# sender = SecureEmailSender.new
# sender.send_email('recipient@example.com', '安全邮件', '这是一封安全邮件')使用OAuth2认证
ruby
require 'net/smtp'
require 'oauth2'
class OAuth2EmailSender
def initialize(client_id, client_secret, refresh_token)
@client_id = client_id
@client_secret = client_secret
@refresh_token = refresh_token
end
def send_email(from, to, subject, body)
access_token = refresh_access_token
message = <<~MESSAGE
From: #{from}
To: #{to}
Subject: #{subject}
Content-Type: text/plain; charset=UTF-8
#{body}
MESSAGE
Net::SMTP.start('smtp.gmail.com', 587, 'localhost', from, access_token, :xoauth2) do |smtp|
smtp.send_message(message, from, to)
end
end
private
def refresh_access_token
client = OAuth2::Client.new(@client_id, @client_secret,
site: 'https://accounts.google.com',
token_url: '/o/oauth2/token'
)
token = OAuth2::AccessToken.from_hash(client, {
refresh_token: @refresh_token,
expires_at: Time.now.to_i - 100 # 强制刷新
})
refreshed_token = token.refresh!
refreshed_token.token
end
end🎯 实用示例
用户注册确认邮件
ruby
class UserRegistrationMailer
def initialize(email_sender)
@email_sender = email_sender
end
def send_confirmation_email(user_email, user_name, confirmation_token)
subject = '欢迎注册 - 请确认您的邮箱'
html_body = <<~HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>邮箱确认</title>
</head>
<body>
<h2>欢迎注册我们的平台!</h2>
<p>亲爱的 #{user_name},</p>
<p>感谢您注册我们的服务。请点击下面的链接确认您的邮箱地址:</p>
<p>
<a href="https://yoursite.com/confirm?token=#{confirmation_token}"
style="background-color: #007bff; color: white; padding: 10px 20px;
text-decoration: none; border-radius: 5px;">
确认邮箱
</a>
</p>
<p>如果您无法点击链接,请复制以下地址到浏览器中打开:</p>
<p>https://yoursite.com/confirm?token=#{confirmation_token}</p>
<p>此链接将在24小时后失效。</p>
<br>
<p>祝您使用愉快!</p>
<p>垦荒教程团队</p>
</body>
</html>
HTML
@email_sender.send_email(
user_email,
subject,
html_body,
content_type: 'text/html'
)
end
end
# 使用示例
# config = EmailConfig.gmail(ENV['SMTP_USERNAME'], ENV['SMTP_PASSWORD'])
# sender = EmailSender.new(config)
# mailer = UserRegistrationMailer.new(sender)
# mailer.send_confirmation_email('user@example.com', '张三', 'abc123token')密码重置邮件
ruby
class PasswordResetMailer
def initialize(email_sender)
@email_sender = email_sender
end
def send_reset_email(user_email, user_name, reset_token)
subject = '密码重置请求'
html_body = <<~HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>密码重置</title>
</head>
<body>
<h2>密码重置请求</h2>
<p>亲爱的 #{user_name},</p>
<p>您请求重置您的账户密码。请点击下面的链接来设置新密码:</p>
<p>
<a href="https://yoursite.com/reset-password?token=#{reset_token}"
style="background-color: #28a745; color: white; padding: 10px 20px;
text-decoration: none; border-radius: 5px;">
重置密码
</a>
</p>
<p>如果您没有请求密码重置,请忽略此邮件。</p>
<p>此链接将在1小时内失效。</p>
<br>
<p>垦荒教程团队</p>
</body>
</html>
HTML
@email_sender.send_email(
user_email,
subject,
html_body,
content_type: 'text/html'
)
end
end通知邮件系统
ruby
class NotificationMailer
def initialize(email_sender)
@email_sender = email_sender
end
def send_notification(recipients, subject, message, options = {})
recipients.each do |recipient|
begin
@email_sender.send_email(
recipient,
subject,
message,
options
)
puts "通知邮件已发送至: #{recipient}"
rescue => e
puts "发送至 #{recipient} 失败: #{e.message}"
end
end
end
def send_system_alert(admin_emails, alert_message)
subject = "系统警报 - #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
html_body = <<~HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>系统警报</title>
</head>
<body>
<h2 style="color: red;">🚨 系统警报</h2>
<p><strong>时间:</strong> #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}</p>
<p><strong>消息:</strong></p>
<div style="background-color: #f8f9fa; padding: 15px; border-left: 4px solid #dc3545;">
<pre>#{alert_message}</pre>
</div>
<p>请立即检查系统状态。</p>
</body>
</html>
HTML
send_notification(
admin_emails,
subject,
html_body,
content_type: 'text/html'
)
end
end📊 性能优化
批量邮件发送
ruby
class BatchEmailSender
def initialize(email_sender, batch_size = 50, delay = 1)
@email_sender = email_sender
@batch_size = batch_size
@delay = delay
end
def send_batch(emails, subject, body, options = {})
emails.each_slice(@batch_size) do |batch|
batch.each do |email|
begin
@email_sender.send_email(email, subject, body, options)
puts "邮件已发送至: #{email}"
rescue => e
puts "发送至 #{email} 失败: #{e.message}"
end
end
# 批次间延迟,避免触发邮件服务商限制
sleep(@delay) unless batch.equal?(emails.each_slice(@batch_size).to_a.last)
end
end
end
# 使用示例
# recipients = ['user1@example.com', 'user2@example.com', ...] # 大量收件人
# config = EmailConfig.gmail(ENV['SMTP_USERNAME'], ENV['SMTP_PASSWORD'])
# sender = EmailSender.new(config)
# batch_sender = BatchEmailSender.new(sender)
# batch_sender.send_batch(recipients, '批量通知', '这是批量发送的通知邮件')异步邮件发送
ruby
require 'thread'
class AsyncEmailSender
def initialize(email_sender)
@email_sender = email_sender
@queue = Queue.new
@worker = Thread.new { process_queue }
end
def send_email_async(to, subject, body, options = {})
@queue << {
to: to,
subject: subject,
body: body,
options: options
}
end
def shutdown
@queue << :shutdown
@worker.join
end
private
def process_queue
loop do
job = @queue.pop
break if job == :shutdown
begin
@email_sender.send_email(
job[:to],
job[:subject],
job[:body],
job[:options]
)
puts "异步邮件已发送至: #{job[:to]}"
rescue => e
puts "异步发送至 #{job[:to]} 失败: #{e.message}"
end
end
end
end
# 使用示例
# config = EmailConfig.gmail(ENV['SMTP_USERNAME'], ENV['SMTP_PASSWORD'])
# sender = EmailSender.new(config)
# async_sender = AsyncEmailSender.new(sender)
#
# async_sender.send_email_async('user@example.com', '异步邮件', '这是一封异步发送的邮件')
#
# # 应用结束前关闭异步发送器
# at_exit { async_sender.shutdown }🎯 最佳实践
1. 错误处理和重试机制
ruby
class RobustEmailSender
def initialize(email_sender, max_retries = 3)
@email_sender = email_sender
@max_retries = max_retries
end
def send_email_with_retry(to, subject, body, options = {})
retries = 0
begin
@email_sender.send_email(to, subject, body, options)
puts "邮件发送成功: #{to}"
true
rescue => e
retries += 1
if retries <= @max_retries
puts "邮件发送失败,#{2 ** retries}秒后重试 (#{retries}/#{@max_retries}): #{e.message}"
sleep(2 ** retries) # 指数退避
retry
else
puts "邮件发送最终失败: #{to} - #{e.message}"
false
end
end
end
end2. 邮件模板系统
ruby
class EmailTemplate
def initialize(template_file)
@template = File.read(template_file)
end
def render(bindings = {})
result = @template.dup
bindings.each do |key, value|
result.gsub!("{{#{key}}}", value.to_s)
end
result
end
end
# 邮件模板文件 (welcome_email.html)
=begin
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>欢迎邮件</title>
</head>
<body>
<h1>欢迎, {{name}}!</h1>
<p>感谢您加入我们。您的注册邮箱是: {{email}}</p>
<p>注册时间: {{signup_time}}</p>
</body>
</html>
=end
# 使用示例
# template = EmailTemplate.new('templates/welcome_email.html')
# html_body = template.render(
# name: '张三',
# email: 'user@example.com',
# signup_time: Time.now.strftime('%Y-%m-%d %H:%M:%S')
# )3. 邮件日志记录
ruby
class EmailLogger
def self.log_email(to, subject, status, error = nil)
log_entry = {
timestamp: Time.now,
to: to,
subject: subject,
status: status,
error: error
}
# 写入日志文件
File.open('email_log.txt', 'a') do |file|
file.puts log_entry.inspect
end
# 或者发送到日志系统
puts "[EMAIL_LOG] #{log_entry}"
end
end
class LoggedEmailSender
def initialize(email_sender)
@email_sender = email_sender
end
def send_email(to, subject, body, options = {})
begin
result = @email_sender.send_email(to, subject, body, options)
EmailLogger.log_email(to, subject, 'success')
result
rescue => e
EmailLogger.log_email(to, subject, 'failed', e.message)
raise e
end
end
end📚 下一步学习
掌握了Ruby SMTP邮件发送后,建议继续学习:
- Ruby Socket 编程 - 学习网络编程基础
- Ruby Web服务 - 了解Web服务开发
- Ruby 多线程 - 掌握并发编程
- Ruby JSON - 学习JSON数据处理
继续您的Ruby学习之旅吧!