Skip to content

Ruby 发送邮件

使用 Net::SMTP 在 Ruby 中发送电子邮件

Section titled “使用 Net::SMTP 在 Ruby 中发送电子邮件”

简单邮件传输协议 (SMTP) 是用于发送电子邮件的标准互联网协议。Ruby 的标准库中包含了 Net::SMTP 类,它提供了连接到 SMTP 服务器并发送电子邮件的客户端接口。

Net::SMTP 提供了建立连接、认证(如果需要)和传输电子邮件消息的方法。使用它的主要方式是使用 Net::SMTP.start 的块形式,它可以自动处理连接的打开和关闭。

Net::SMTP.start 方法接受几个参数来配置连接:

  • address: SMTP 服务器的主机名或 IP 地址(默认为 ‘localhost’)。
  • port: SMTP 服务器的端口号(默认为 25,标准 SMTP 端口)。
  • helo_domain: 您的客户端用来标识自己的域名(HELO/EHLO 命令,默认为您的系统主机名)。
  • user: 用于 SMTP 认证的用户名(如果需要,默认为 nil)。
  • password: 用于 SMTP 认证的密码(如果需要,默认为 nil)。
  • authtype: 认证机制(例如 :plain、:login、:cram_md5,如果提供了用户则默认为 :cram_md5)。

在传递给 start 的块内部,SMTP 实例提供了 send_message 方法,该方法接受:

  • message_string: 完整的电子邮件消息内容(头部和正文)作为字符串。
  • from_address: 发件人电子邮件地址(用于 MAIL FROM SMTP 命令)。
  • to_addresses: 单个收件人电子邮件地址(字符串)或收件人电子邮件地址数组。

这里是一个发送电子邮件的简单示例。这假设 SMTP 服务器在 localhost 上运行且无需认证。在实际应用中,您可能会使用远程 SMTP 服务器,这通常需要认证。

require 'net/smtp'
message_body = <<~MESSAGE_END
From: Your Name <your_email@example.com>
To: Recipient Name <recipient_email@example.com>
Subject: Test Email from Ruby (Net::SMTP)
This is a test email message sent using Net::SMTP in Ruby.
Regards,
Ruby Script
MESSAGE_END
begin
Net::SMTP.start('localhost', 25) do |smtp|
smtp.send_message message_body,
'your_email@example.com',
'recipient_email@example.com'
end
puts "Email sent successfully!"
rescue StandardError => e
puts "Error sending email: #{e.message}"
end

注意:<<~MESSAGE_END (波浪线 heredoc) 允许使用带缩进的 heredoc,并移除前导空白字符。邮件头部(From, To, Subject)至关重要,并且必须正确格式化,与正文通过一个空行分隔开。

连接到需要认证的远程 SMTP 服务器:

要使用远程 SMTP 服务器(例如 Gmail、SendGrid 等),您需要知道其地址、端口和您的凭据。

# 泛型认证 SMTP 服务器示例
# 替换为您实际的服务器详细信息和凭据
# Net::SMTP.start('smtp.your_mail_provider.com',
# 587, # TLS/STARTTLS 的常用端口
# 'your_helo_domain.com', # 通常是您的发送域名
# 'your_username',
# 'your_password',
# :plain) do |smtp|
# # ... smtp.send_message ...
# end
# 对于 Gmail,您可能需要启用“低安全性应用访问”或使用应用密码。
# Gmail 的 SMTP 服务器:smtp.gmail.com,端口:587 (TLS) 或 465 (SSL - 在 start 之前需要 Net::SMTP.enable_ssl)
# 考虑到安全和配置的细微之处,对于 Gmail 等服务,通常最好使用专门的邮件发送 gem。

要发送 HTML 格式的电子邮件,您需要将 Content-Type 头部设置为 text/html 并包含一个 MIME-Version 头部。

require 'net/smtp'
html_message = <<~MESSAGE_END
From: Your Name <your_email@example.com>
To: Recipient Name <recipient_email@example.com>
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8
Subject: HTML Test Email from Ruby
<html>
<body>
<h1>Hello from Ruby!</h1>
<p>This is an email message sent in **HTML format**.</p>
<p><a href="https://www.ruby-lang.org">Visit Ruby's Official Website</a></p>
</body>
</html>
MESSAGE_END
begin
Net::SMTP.start('localhost', 25) do |smtp|
smtp.send_message html_message,
'your_email@example.com',
'recipient_email@example.com'
end
puts "HTML Email sent successfully!"
rescue StandardError => e
puts "Error sending HTML email: #{e.message}"
end

发送附件需要构建一个多部分的 MIME 消息。这涉及定义边界来分隔电子邮件的不同部分(文本正文、HTML 正文、附件)。每个部分都有自己的 Content-Type 和 Content-Disposition 头部。附件通常采用 Base64 编码。

使用原生 Net::SMTP 手动构建多部分邮件可能很复杂。对于健壮的附件处理和多部分邮件,强烈推荐使用专门的邮件发送 gem,如 Mail,因为它能大大简化这些任务。然而,这里是一个使用原生 Net::SMTP 的概念性示例(简化版):

require 'net/smtp'
require 'base64' # 用于 Base64.strict_encode64
# --- 配置 ---
from_email = 'your_email@example.com'
to_email = 'recipient_email@example.com'
subject = 'Email with Attachment from Ruby'
text_body = "Please find the attached file."
file_to_attach_path = './my_document.txt' # 为测试创建此文件
# 如果测试文件不存在,则创建一个模拟文件
File.write(file_to_attach_path, "This is a test document.") unless File.exist?(file_to_attach_path)
# --- 读取并编码附件 ---
begin
file_content = File.read(file_to_attach_path)
encoded_content = Base64.strict_encode64(file_content)
attachment_filename = File.basename(file_to_attach_path)
rescue Errno::ENOENT
puts "错误: 附件文件未找到于 #{file_to_attach_path}"
exit
end
# --- 构建 MIME 消息 ---
marker = "MAIL_BOUNDARY_#{Time.now.to_i}_#{rand(1000)}"
message_parts = []
message_parts << "From: Your Name <#{from_email}>"
message_parts << "To: Recipient Name <#{to_email}>"
message_parts << "Subject: #{subject}"
message_parts << "MIME-Version: 1.0"
message_parts << "Content-Type: multipart/mixed; boundary=\"#{marker}\""
message_parts << "\n--#{marker}"
message_parts << "Content-Type: text/plain; charset=UTF-8"
message_parts << "Content-Transfer-Encoding: 7bit"
message_parts << "\n#{text_body}"
message_parts << "\n--#{marker}"
message_parts << "Content-Type: application/octet-stream; name=\"#{attachment_filename}\""
message_parts << "Content-Transfer-Encoding: base64"
message_parts << "Content-Disposition: attachment; filename=\"#{attachment_filename}\""
message_parts << "\n#{encoded_content}"
message_parts << "\n--#{marker}--"
full_message = message_parts.join("\r\n") # 在电子邮件中使用 CRLF 作为行尾符
# --- 发送邮件 ---
begin
Net::SMTP.start('localhost', 25) do |smtp|
smtp.send_message full_message, from_email, to_email
end
puts "带附件的邮件发送成功!"
rescue StandardError => e
puts "发送带附件的邮件出错: #{e.message}"
end

重要注意事项:

  • **错误处理:**始终包含 begin…rescue 块来捕获潜在错误,如连接失败、认证问题或无效地址。
  • **安全性 (TLS/SSL):**对于安全通信,特别是与远程 SMTP 服务器通信时,请确保您正在使用 TLS/STARTTLS(通常在端口 587)或 SSL(通常在端口 465)。Net::SMTP 支持 enable_starttls_auto(如果服务器支持)或 enable_ssl(用于直接 SSL 连接)。对于 enable_ssl,您可能需要使用 Net::SMTP.new 创建 Net::SMTP 对象,然后调用 enable_ssl,再不带块地调用 start,或者使用 OpenSSL 上下文选项。请查看 Net::SMTP 类的文档获取详细信息。
  • **邮件发送 Gem:**对于除了简单邮件之外的任何需求,或者需要模板、高级附件处理以及更易于为各种邮件服务提供商配置的功能,请考虑使用像 Mail (github.com/mikel/mail) 或 Action Mailer (如果您正在使用 Ruby on Rails) 这样的 gem。这些 gem 抽象化了 Net::SMTP 的许多复杂性。
  • **邮件头部和格式化:**密切关注 RFC 标准关于邮件头部和 MIME 格式的要求,以确保您的电子邮件能被各种电子邮件客户端正确渲染。

详细信息请参考 Net::SMTP 类的 Ruby 官方文档。