Back to Home

Send email without an email service (like Sendgrid, AWS SES etc.)

As supportbee.com’s Backend Eng. & DevOps Lead, I run SupportBee’s email sync and delivery infrastructure and often find myself in situations where I’ve to send different types of emails (Plain text emails, HTML emails, malformed emails etc.) to our emails importers for debugging/testing purposes.

While using an email service (like Sendgrid etc.) is typically the easiest way to send an email to any email server, these services often modify the email slightly. They may add extra headers or remove malformed headers or perform other modification which many times renders them unsuitable for debugging/testing purposes.

A better approach in these scenarios is to just send an email to any email server using your preferred programming language.

Here’s how you can easily send an email from the Ruby programming language.

Install the mail gem

gem install mail

Save the script below on your laptop

require "mail"
require "net/smtp"

from = "[email protected]"
to = "[email protected]"
destination_smtp_server_ip = "172.217.194.26"

mail = Mail.new do
  from from
  to to
  subject "Just a test email"
  body "Just a test email"
end

smtp = Net::SMTP.new(destination_smtp_server_ip)
smtp.open_timeout = 5
smtp.start do |smtp|
  smtp.send_message(mail.to_s, from, to)
end

Modify the from and the to address. Modify the  destination_smtp_server_ip to the ip address of email server you wish to deliver the email to. If you don’t have the destination email server’s ip address, query the recipient domain’s (gmail.com in this case) MX records

~$ dig MX gmail.com +short
5 gmail-smtp-in.l.google.com.
40 alt4.gmail-smtp-in.l.google.com.
10 alt1.gmail-smtp-in.l.google.com.
20 alt2.gmail-smtp-in.l.google.com.
30 alt3.gmail-smtp-in.l.google.com.
~$ dig gmail-smtp-in.l.google.com +short
172.217.194.26

Run the script

ruby send_email_directly.rb

That’s it! In case you see a timeout error (like I did), your broadband provider may be preventing you from sending emails. I switched to my phone’s 4G (which is from a different provider) to resolve this.

~/tmp [r-2.2.3]% ruby send_email_directly.rb
/Users/nisanth/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/net/smtp.rb:541:in `initialize': execution expired (Net::OpenTimeout)
from /Users/nisanth/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/net/smtp.rb:541:in `open'
from /Users/nisanth/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/net/smtp.rb:541:in `tcp_socket'
from /Users/nisanth/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/net/smtp.rb:551:in `block in do_start'
from /Users/nisanth/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/timeout.rb:88:in `block in timeout'
from /Users/nisanth/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/timeout.rb:98:in `call'
from /Users/nisanth/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/timeout.rb:98:in `timeout'
from /Users/nisanth/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/net/smtp.rb:550:in `do_start'
from /Users/nisanth/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/net/smtp.rb:520:in `start'
from send_email_directly.rb:21:in `<main>'

Please note that if you send emails directly to a popular email service provider like Gmail and not to your own email servers, there is a chance that your email may be a classified as spam.

Automatically query MX records

If you don’t care which email server receives your email, you can modify the Ruby script to automatically query the recipient’s domain MX records for you

require "mail"
require "resolv"
require "net/smtp"

from = "[email protected]"
to = "[email protected]"

mail = Mail.new do
  from from
  to to
  subject "Just a test email"
  body "Just a test email"
end

class Domain
  def initialize(domain)
    @domain = domain
  end

  def mx_ip
    mx_ips.first
  end

  private

  attr_reader :domain

  def mx_ips
    Resolv::DNS.open do |dns|
      mx_resources = dns.getresources(domain, Resolv::DNS::Resource::IN::MX)
      mx_ips = mx_resources.map { |mx_resource| IPSocket::getaddress(mx_resource.exchange.to_s) }
    end
  end
end

recipient_domain = mail['To'].addrs.first.domain
destination_smtp_server_ip = Domain.new(recipient_domain).mx_ip
smtp = Net::SMTP.new(destination_smtp_server_ip)
smtp.open_timeout = 5
smtp.start do |smtp|
  smtp.send_message(mail.to_s, from, to)
end

Built with Hugo & Notion. Source code is available at GitHub.