Library / SDK
mikel/mail avatar
mikel/mail

mikel/mail: the Ruby Mail gem for parsing, generating and sending email

A Really Ruby Mail Library

3,670 stars935 forksRubyMIT

At a glance

What is it?
Mail is a pure Ruby internet library that handles RFC5322 and RFC6532 email parsing, MIME construction and delivery through Net::SMTP and Net::POP3. It suits Ruby services that need to read or build messages directly; it is not a mail server or an SMTP relay.
Who is it for?
Adopt mikel/mail when a Ruby process must parse raw messages or build MIME bodies and hand them to an existing SMTP or POP3 endpoint, and pin the version because 2.9.1 arrived on 2026-07-01 after 2.9.0 in October 2025. Do not adopt it as a mail server, a queue or a deliverability layer; it has no storage and no retry logic.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 91 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What mikel/mail solves, and who ends up using it

Ruby applications that touch email usually hit the same wall: SMTP and POP3 are in the standard library, but the message format is not. Net::SMTP sends bytes. Deciding which bytes, how to encode a non-ASCII subject, how to nest a text and HTML alternative, or how to recover the plain-text part from a multipart/report bounce is left to the caller. Mail fills that gap. The README describes it as "an internet library for Ruby that is designed to handle email generation, parsing and sending in a simple, rubyesque manner", and the network side is delegated: "All network type actions are done through proxy methods to Net::SMTP, Net::POP3 etc."

The audience is narrow and specific. It is Ruby developers writing transactional mailers, inbound mail processors, test fixtures for mail-handling code, or migration scripts that rewrite stored messages. It is not for someone who wants a mail server, a queue with retries, or a dashboard. Mail has no storage layer, no scheduling and no bounce analytics. It reads and writes messages and hands them to a transport you already run.

The project has a stated lineage: it was "Built from my experience with TMail", and the README frames the rewrite around modern Ruby encodings, arguing that "Modern Rubies handle text encodings much more wonderfully than before so these features have been taken full advantage of in this library". Anyone who has maintained a TMail-based codebase will recognise the intent, though the README does not provide a migration guide from TMail.

The encoded/decoded split that governs the whole API

The most consequential design decision in Mail is that every object that can render into an email carries two methods, and they mean different things. The README states the rule plainly: "All objects that can render into an email, have an `#encoded` method", which returns "the object as a complete string ready to send in the mail system, that is, it will include the header field and value and CRLF at the end and wrapped as needed". The counterpart, `#decoded`, returns "the object's value only as a string", without header fields.

That distinction propagates into `to_s`, and this is where newcomers get caught. According to the README, calling `to_s` on a container object calls the encoded method, while calling it on a field object calls the decoded method. So `to_s` on a Mail object yields the whole message ready for the wire, but `to_s` on a From field or a body yields just the value. The README's own advice is to be explicit: "If you are in doubt, call `#encoded`, or `#decoded` explicitly, this is safer if you are not sure." That is honest documentation of a sharp edge rather than a hidden one, but the edge exists.

Parameter values follow the same pattern. Structured fields such as Content-Type return decoded parameter values when you call the parameter name as a method on the object, and encoded values when you go through `object.parameters['<parameter_name>']`. Two access paths, two encodings, one field. Code that mixes them without noticing will produce bodies that look correct in a terminal and wrong in a mail client.

Parsing behaviour: it skips what it cannot read instead of raising

Mail advertises RFC5322 and RFC6532 support, meaning it reads US-ASCII and UTF-8 mail and generates US-ASCII mail. The README also admits a boundary: "There are a few obsoleted email syntax that it will have problems with." The more interesting claim is about failure. The README says that if the parser "finds something it doesn't understand it will not crash, instead, it will skip the problem and keep parsing", and that an unrecognised header is initialised as "an optional unstructured field" before parsing continues.

That is a deliberate trade-off, and it cuts both ways. For an inbound pipeline that must not drop a message because one header is malformed, lenient parsing is the right default; the README states the intent as "Mail won't (ever) crunch your data". For a validation use case, it is the wrong default, because a message that parses successfully may still contain fields the library never understood. The README does not document a strict mode, so if you need to reject malformed input you will have to inspect the resulting object yourself.

The capability list is concrete: RFC5322 reading and writing, RFC6532 UTF-8 header reading, RFC2045 through RFC2049 multipart support, multipart/alternate creation, reading multipart/report and extracting details from it, wrappers for File, Net/POP3 and Net/SMTP, and auto-encoding of non-US-ASCII bodies and header fields. For the common case of a text and HTML pair, the README notes there are helper methods, and for anything else you build the MIME structure manually.

Installing the gem and getting a first message out

The README gives one installation line and points at RubyGems: "I host mail on rubygems, so you can just do". No version pin is suggested, so pin whatever your Gemfile resolves to.

bash
gem install mail

After that, the gem is available as `mail`. The README's Usage section says "All major mail functions should be able to happen from the Mail module", so a first message starts from the Mail module. The README does not print a worked example in the excerpt available, so the honest first step is to confirm the object model against the Encodings section rather than copy a snippet from here.

The two methods the README documents in detail are `#encoded` and `#decoded`. The README states that `#encoded` returns the object as a complete string ready to send, including the header field and value and CRLF at the end and wrapped as needed, while `#decoded` returns the object's value only as a string, without header fields. It adds that calling `to_s` on a container object calls the encoded method, and calling `to_s` on a field object calls the decoded method. Containers include the Mail object and its header object.

The README's advice for anyone unsure which of the two applies is to call `#encoded` or `#decoded` explicitly. Sending is a separate step and goes through the Net::SMTP proxy the README mentions; the README excerpt available does not print a delivery example, so treat the transport call as something to confirm against the library's own documentation before wiring it into a production path.

Where Mail is the wrong tool

Mail parses and generates messages. It does not run a mail server, and the README never claims otherwise. If you need delivery retries, a queue, per-recipient suppression, bounce classification or an audit trail, those live outside this library, in whatever SMTP relay or provider you point it at. Choosing Mail does not remove that infrastructure; it just changes who formats the bytes.

The encoding model is the second limitation. Because `to_s` resolves differently for containers and fields, and because parameter values are decoded through one access path and encoded through another, code that reads naturally can silently produce the wrong representation. The README's own instruction to call `#encoded` or `#decoded` explicitly is the mitigation, and it is a real one, but it means every call site is a place where a reviewer has to know which kind of object is in hand.

Lenient parsing is the third. A parser that skips unrecognised headers and continues will not tell you that it skipped them. For a mailbox reader that is fine. For a compliance or archiving pipeline that must account for every field, it means the library's success is not evidence that the message was fully understood.

The roadmap is also thin. The README's entire "Next TODO" is one item: "Improve MIME support for character sets in headers, currently works, mostly, needs refinement." "Mostly" is the project's own word for a known soft spot in header charset handling.

Mail versus Action Mailer, and versus writing it yourself

The obvious alternative inside Ruby is Action Mailer, the Rails framework component. The difference is architectural, not cosmetic. Action Mailer is a rendering and delivery framework: you define mailer classes, views render the body, and a delivery method moves the message out. Mail is the message object underneath, and it makes no assumptions about Rails, views or a mailer class hierarchy. If your application is Rails and your mail is template-driven, Action Mailer is the shorter path, and it will use a message library of its own. If your application is a plain Ruby service, a background worker, or a script that parses inbound mail, Action Mailer brings a framework you do not need, and Mail is the layer you actually wanted.

The second alternative is the standard library plus hand-rolled MIME. Net::SMTP is already there, and for a plain US-ASCII body with no attachments it is a dozen lines. That stops being true the moment you need a non-ASCII subject, a multipart/alternate pair, or a parser for inbound messages. Mail's capability list covers exactly those cases, and it is the reason the library exists rather than being a thin wrapper.

One caveat on the comparison: the README does not benchmark Mail against either option, and it does not describe performance characteristics at all. The case for Mail here is scope and correctness of the message model, not speed.

Maintenance, release cadence and the MIT licence

The repository is not archived, and the last push was on 2026-07-01, the same day release 2.9.1 was published. The previous release, 2.9.0, was on 2025-10-22, and 2.9.0.beta2 preceded it on 2025-03-20. So the pattern is a minor release roughly annually, with a patch following. That is a slow cadence, and it matters when you plan upgrades: there is no stream of small fixes to absorb, so each release is a larger step.

The API policy is the project's answer to that. The README states: "No API removals within a single point release. All removals to be deprecated with warnings for at least one MINOR point release before removal." In practice this means a removal announced in one minor release should survive until a later one, giving you a release cycle of warning before code breaks, provided you watch deprecation warnings rather than filtering them. The README also notes that the private and protected method policy "is still I/P", so do not rely on internal methods being marked as such.

Compatibility is documented as a tested list: Ruby 2.5 through 3.2, JRuby 9.2 through 9.4, JRuby stable and head, and Truffleruby stable and head. The README says future support will track the preview release plus normal maintenance, security maintenance and the two most recent end-of-life versions on the Ruby Maintenance Branches page. Ruby 3.3 and later are not on the printed list, so verify your interpreter before committing. The README states that every commit is tested by GitHub Actions across all supported Ruby versions.

Licensing is MIT, with the MIT-LICENSE file at the repository root alongside mail.gemspec. MIT is permissive and places few obligations on how you redistribute or embed the gem, but the file itself is the authority, and nothing here is legal advice. The README also points contributors at CONTRIBUTING.md and notes the project uses BDD with a stated expectation of full coverage measured by RCov, plus the TMail functional tests passing before a gem release.

Editorial conclusion

Adopt mikel/mail when a Ruby process must parse raw messages or build MIME bodies and hand them to an existing SMTP or POP3 endpoint, and pin the version because 2.9.1 arrived on 2026-07-01 after 2.9.0 in October 2025. Do not adopt it as a mail server, a queue or a deliverability layer; it has no storage and no retry logic. Before upgrading, read CHANGELOG.rdoc for deprecations, since the README promises no API removal inside a point release, and confirm the Ruby you run is on the tested list, which stops at 3.2 plus JRuby and Truffleruby.

Frequently asked questions

What is the Ruby Mail gem?

It is a pure Ruby internet library for email generation, parsing and sending, built around RFC5322 and RFC6532 support and MIME handling for multipart messages. Network operations are delegated to Net::SMTP, Net::POP3 and similar through proxy methods.

How do I install the Mail gem?

The README gives a single command, gem install mail, since the gem is hosted on RubyGems. The README does not suggest a version pin, so the version you get depends on how your Gemfile resolves it.

What is the difference between encoded and decoded in Mail?

The encoded method returns the object as a complete string ready to send, including the header field, value, CRLF and wrapping. The decoded method returns only the object's value as a string, without header fields. Calling to_s follows the same split: containers resolve to encoded, field objects to decoded.

Which Ruby versions does Mail support?

The README lists Ruby 2.5 through 3.2, JRuby 9.2 through 9.4, JRuby stable and head, and Truffleruby stable and head as tested. Newer Ruby releases are not on that printed list, so check before upgrading your interpreter.

What happens when Mail parses a message it does not understand?

It does not crash. The README states that it skips the problem and keeps parsing, and that an unrecognised header is initialised as an optional unstructured field. The consequence is that a successful parse does not prove every field was understood.

Official sources

  1. Issues
  2. License: MIT
  3. mikel/mail on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mikel-mail.svg)](https://hysenlabs.com/projects/mikel-mail)