smtp = SmtpClient.new("SMTP_HOST")
begin
smtp.authenticate("SMTP_USERNAME", "SMTP_PASSWORD")
smtp.send_email(
from: "FROM_EMAIL",
to: "TO_EMAIL",
subject: "MAIL_SUBJECT",
body: "MESSAGE"
)
ensure
smtp.close
end
Last active
January 29, 2025 14:59
-
-
Save xthezealot/1922ed0a3c1266e3e1bca80f62175f36 to your computer and use it in GitHub Desktop.
Crystal SMTP Client
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "base64" | |
require "openssl" | |
require "socket" | |
class SMTPClient | |
CRLF = "\r\n" | |
def initialize(@host : String, @port : Int32 = 587) | |
@socket = TCPSocket.new(@host, @port) | |
@socket.tcp_nodelay = true | |
@ssl_socket = nil | |
read_response | |
end | |
def authenticate(username : String, password : String) | |
send_command("EHLO localhost") | |
send_command("STARTTLS") | |
@ssl_socket = OpenSSL::SSL::Socket::Client.new(@socket) | |
send_command("EHLO localhost") | |
send_command("AUTH LOGIN") | |
send_command(Base64.strict_encode(username.strip)) | |
send_command(Base64.strict_encode(password.strip)) | |
end | |
def send_email(from : String, to : String, subject : String, body : String) | |
send_command("MAIL FROM:<#{from}>") | |
send_command("RCPT TO:<#{to}>") | |
send_command("DATA") | |
send_command("From: #{from}#{CRLF}To: #{to}#{CRLF}Subject: #{subject}#{CRLF}#{CRLF}#{body}#{CRLF}.#{CRLF}") | |
end | |
private def send_command(command : String) | |
socket = @ssl_socket || @socket | |
socket << command << CRLF | |
socket.flush | |
read_response | |
end | |
private def read_response : String | |
socket = @ssl_socket || @socket | |
responses = [] of String | |
while line = socket.gets | |
line = line.chomp | |
responses << line | |
break unless line.starts_with?(/\d{3}-/) | |
end | |
response = responses.last | |
raise "SMTP Error: #{response}" if response.starts_with?(/[45]/) | |
response | |
end | |
def close | |
send_command("QUIT") rescue nil | |
@ssl_socket.try(&.close) | |
@socket.close | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment