Class: Tina4::Messenger
- Inherits:
-
Object
- Object
- Tina4::Messenger
- Defined in:
- lib/tina4/messenger.rb
Overview
Tina4 Messenger — Email sending (SMTP) and reading (IMAP).
Unified .env-driven configuration with constructor override. Priority: constructor params > .env (TINA4_MAIL_*) > sensible defaults
# .env
TINA4_MAIL_HOST=smtp.gmail.com
TINA4_MAIL_PORT=587
TINA4_MAIL_USERNAME=user@gmail.com
TINA4_MAIL_PASSWORD=app-password
TINA4_MAIL_FROM=noreply@myapp.com
TINA4_MAIL_ENCRYPTION=tls
TINA4_MAIL_IMAP_HOST=imap.gmail.com
TINA4_MAIL_IMAP_PORT=993
mail = Messenger.new # reads from .env
mail = Messenger.new(host: "smtp.office365.com", port: 587) # override
mail.send(to: "user@test.com", subject: "Welcome", body: "<h1>Hello!</h1>", html: true, text: "Hello!")
Instance Attribute Summary collapse
-
#encryption ⇒ Object
readonly
Returns the value of attribute encryption.
-
#from_address ⇒ Object
readonly
Returns the value of attribute from_address.
-
#from_name ⇒ Object
readonly
Returns the value of attribute from_name.
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#imap_encryption ⇒ Object
readonly
Returns the value of attribute imap_encryption.
-
#imap_host ⇒ Object
readonly
Returns the value of attribute imap_host.
-
#imap_port ⇒ Object
readonly
Returns the value of attribute imap_port.
-
#imap_use_tls ⇒ Object
readonly
Returns the value of attribute imap_use_tls.
-
#port ⇒ Object
readonly
Returns the value of attribute port.
-
#use_tls ⇒ Object
readonly
Returns the value of attribute use_tls.
-
#username ⇒ Object
readonly
Returns the value of attribute username.
Instance Method Summary collapse
-
#folders ⇒ Object
List all IMAP folders.
-
#inbox(folder: "INBOX", limit: 20, offset: 0) ⇒ Object
List messages in a folder.
-
#initialize(host: nil, port: nil, username: nil, password: nil, from_address: nil, from_name: nil, encryption: nil, use_tls: nil, imap_host: nil, imap_port: nil, imap_encryption: nil) ⇒ Messenger
constructor
Initialize with SMTP config.
-
#mark_read(uid, folder: "INBOX") ⇒ Object
Mark a message as read (set Seen flag).
-
#read(uid, folder: "INBOX", mark_read: true) ⇒ Object
Read a single message by UID.
-
#search(folder: "INBOX", subject: nil, sender: nil, since: nil, before: nil, unseen_only: false, limit: 20) ⇒ Object
Search messages with filters.
-
#send(to:, subject:, body:, html: false, text: nil, cc: [], bcc: [], reply_to: nil, attachments: [], headers: {}) ⇒ Object
Send email using Ruby’s Net::SMTP Returns { success: true/false, message: “…”, id: “…” }.
-
#test_connection ⇒ Object
Test SMTP connection Returns { success: true/false, message: “…” }.
-
#test_imap_connection ⇒ Hash
Test IMAP connectivity without reading messages.
-
#unread(folder: "INBOX") ⇒ Object
Count unread messages.
Constructor Details
#initialize(host: nil, port: nil, username: nil, password: nil, from_address: nil, from_name: nil, encryption: nil, use_tls: nil, imap_host: nil, imap_port: nil, imap_encryption: nil) ⇒ Messenger
Initialize with SMTP config. Priority: constructor params > ENV (TINA4_MAIL_*) > sensible defaults
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/tina4/messenger.rb', line 44 def initialize(host: nil, port: nil, username: nil, password: nil, from_address: nil, from_name: nil, encryption: nil, use_tls: nil, imap_host: nil, imap_port: nil, imap_encryption: nil) @host = host || ENV["TINA4_MAIL_HOST"] || "localhost" @port = (port || ENV["TINA4_MAIL_PORT"] || 587).to_i @username = username || ENV["TINA4_MAIL_USERNAME"] @password = password || ENV["TINA4_MAIL_PASSWORD"] resolved_from = from_address || ENV["TINA4_MAIL_FROM"] @from_address = resolved_from || @username || "noreply@localhost" @from_name = from_name || ENV["TINA4_MAIL_FROM_NAME"] || "" # SMTP encryption: constructor > .env > backward-compat use_tls > default "tls" env_encryption = encryption || ENV["TINA4_MAIL_ENCRYPTION"] if env_encryption @encryption = env_encryption.downcase elsif !use_tls.nil? @encryption = use_tls ? "tls" : "none" else @encryption = "tls" end @use_tls = %w[tls starttls].include?(@encryption) @imap_host = imap_host || ENV["TINA4_MAIL_IMAP_HOST"] || @host @imap_port = (imap_port || ENV["TINA4_MAIL_IMAP_PORT"] || 993).to_i # IMAP encryption: dedicated env var TINA4_MAIL_IMAP_ENCRYPTION (tls/starttls/none). # Defaults to "tls" — IMAPS over implicit TLS on port 993 is the safe industry norm. env_imap_enc = imap_encryption || ENV["TINA4_MAIL_IMAP_ENCRYPTION"] @imap_encryption = (env_imap_enc && !env_imap_enc.to_s.empty?) ? env_imap_enc.to_s.downcase : "tls" @imap_use_tls = %w[tls starttls ssl].include?(@imap_encryption) end |
Instance Attribute Details
#encryption ⇒ Object (readonly)
Returns the value of attribute encryption.
38 39 40 |
# File 'lib/tina4/messenger.rb', line 38 def encryption @encryption end |
#from_address ⇒ Object (readonly)
Returns the value of attribute from_address.
38 39 40 |
# File 'lib/tina4/messenger.rb', line 38 def from_address @from_address end |
#from_name ⇒ Object (readonly)
Returns the value of attribute from_name.
38 39 40 |
# File 'lib/tina4/messenger.rb', line 38 def from_name @from_name end |
#host ⇒ Object (readonly)
Returns the value of attribute host.
38 39 40 |
# File 'lib/tina4/messenger.rb', line 38 def host @host end |
#imap_encryption ⇒ Object (readonly)
Returns the value of attribute imap_encryption.
38 39 40 |
# File 'lib/tina4/messenger.rb', line 38 def imap_encryption @imap_encryption end |
#imap_host ⇒ Object (readonly)
Returns the value of attribute imap_host.
38 39 40 |
# File 'lib/tina4/messenger.rb', line 38 def imap_host @imap_host end |
#imap_port ⇒ Object (readonly)
Returns the value of attribute imap_port.
38 39 40 |
# File 'lib/tina4/messenger.rb', line 38 def imap_port @imap_port end |
#imap_use_tls ⇒ Object (readonly)
Returns the value of attribute imap_use_tls.
38 39 40 |
# File 'lib/tina4/messenger.rb', line 38 def imap_use_tls @imap_use_tls end |
#port ⇒ Object (readonly)
Returns the value of attribute port.
38 39 40 |
# File 'lib/tina4/messenger.rb', line 38 def port @port end |
#use_tls ⇒ Object (readonly)
Returns the value of attribute use_tls.
38 39 40 |
# File 'lib/tina4/messenger.rb', line 38 def use_tls @use_tls end |
#username ⇒ Object (readonly)
Returns the value of attribute username.
38 39 40 |
# File 'lib/tina4/messenger.rb', line 38 def username @username end |
Instance Method Details
#folders ⇒ Object
List all IMAP folders
194 195 196 197 198 199 200 201 202 |
# File 'lib/tina4/messenger.rb', line 194 def folders imap_connect do |imap| boxes = imap.list("", "*") (boxes || []).map(&:name) end rescue => e Tina4::Log.error("IMAP folders failed: #{e.}") [] end |
#inbox(folder: "INBOX", limit: 20, offset: 0) ⇒ Object
List messages in a folder
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/tina4/messenger.rb', line 124 def inbox(folder: "INBOX", limit: 20, offset: 0) imap_connect do |imap| imap.select(folder) uids = imap.uid_search(["ALL"]) uids = uids.reverse # newest first page = uids[offset, limit] || [] return [] if page.empty? envelopes = imap.uid_fetch(page, ["ENVELOPE", "FLAGS", "RFC822.SIZE"]) (envelopes || []).map { |msg| parse_envelope(msg) } end rescue => e Tina4::Log.error("IMAP inbox failed: #{e.}") [] end |
#mark_read(uid, folder: "INBOX") ⇒ Object
Mark a message as read (set Seen flag).
208 209 210 211 212 213 214 215 |
# File 'lib/tina4/messenger.rb', line 208 def mark_read(uid, folder: "INBOX") imap_connect do |imap| imap.select(folder) imap.uid_store(uid.to_i, "+FLAGS", [:Seen]) end rescue => e Tina4::Log.error("IMAP mark_read failed: #{e.}") end |
#read(uid, folder: "INBOX", mark_read: true) ⇒ Object
Read a single message by UID
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/tina4/messenger.rb', line 141 def read(uid, folder: "INBOX", mark_read: true) imap_connect do |imap| imap.select(folder) data = imap.uid_fetch(uid, ["ENVELOPE", "FLAGS", "BODY[]", "RFC822.SIZE"]) return nil if data.nil? || data.empty? if mark_read imap.uid_store(uid, "+FLAGS", [:Seen]) end msg = data.first (msg) end rescue => e Tina4::Log.error("IMAP read failed: #{e.}") nil end |
#search(folder: "INBOX", subject: nil, sender: nil, since: nil, before: nil, unseen_only: false, limit: 20) ⇒ Object
Search messages with filters
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/tina4/messenger.rb', line 172 def search(folder: "INBOX", subject: nil, sender: nil, since: nil, before: nil, unseen_only: false, limit: 20) imap_connect do |imap| imap.select(folder) criteria = build_search_criteria( subject: subject, sender: sender, since: since, before: before, unseen_only: unseen_only ) uids = imap.uid_search(criteria) uids = uids.reverse page = uids[0, limit] || [] return [] if page.empty? envelopes = imap.uid_fetch(page, ["ENVELOPE", "FLAGS", "RFC822.SIZE"]) (envelopes || []).map { |msg| parse_envelope(msg) } end rescue => e Tina4::Log.error("IMAP search failed: #{e.}") [] end |
#send(to:, subject:, body:, html: false, text: nil, cc: [], bcc: [], reply_to: nil, attachments: [], headers: {}) ⇒ Object
Send email using Ruby’s Net::SMTP Returns { success: true/false, message: “…”, id: “…” }
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/tina4/messenger.rb', line 80 def send(to:, subject:, body:, html: false, text: nil, cc: [], bcc: [], reply_to: nil, attachments: [], headers: {}) = "<#{SecureRandom.uuid}@#{@host}>" raw = ( to: to, subject: subject, body: body, html: html, text: text, cc: cc, bcc: bcc, reply_to: reply_to, attachments: , headers: headers, message_id: ) all_recipients = normalize_recipients(to) + normalize_recipients(cc) + normalize_recipients(bcc) smtp = Net::SMTP.new(@host, @port) smtp.enable_starttls if @use_tls smtp.start(@host, @username, @password, auth_method) do |conn| conn.(raw, @from_address, all_recipients) end Tina4::Log.info("Email sent to #{Array(to).join(', ')}: #{subject}") { success: true, message: "Email sent successfully", id: } rescue => e Tina4::Log.error("Email send failed: #{e.}") { success: false, message: e., id: nil } end |
#test_connection ⇒ Object
Test SMTP connection Returns { success: true/false, message: “…” }
110 111 112 113 114 115 116 117 118 119 |
# File 'lib/tina4/messenger.rb', line 110 def test_connection smtp = Net::SMTP.new(@host, @port) smtp.enable_starttls if @use_tls smtp.start(@host, @username, @password, auth_method) do |_conn| # connection succeeded end { success: true, message: "SMTP connection successful" } rescue => e { success: false, message: e. } end |
#test_imap_connection ⇒ Hash
Test IMAP connectivity without reading messages.
220 221 222 223 224 225 226 227 |
# File 'lib/tina4/messenger.rb', line 220 def test_imap_connection imap_connect do |_imap| # Connection succeeded end { success: true, message: "Connected to #{@imap_host}:#{@imap_port}" } rescue => e { success: false, message: "IMAP connection failed: #{e.}" } end |
#unread(folder: "INBOX") ⇒ Object
Count unread messages
160 161 162 163 164 165 166 167 168 169 |
# File 'lib/tina4/messenger.rb', line 160 def unread(folder: "INBOX") imap_connect do |imap| imap.select(folder) uids = imap.uid_search(["UNSEEN"]) uids.length end rescue => e Tina4::Log.error("IMAP unread count failed: #{e.}") 0 end |