Initial commit for the generic mailer.
commit
c04544838d
|
@ -0,0 +1,4 @@
|
|||
shard.lock
|
||||
bin
|
||||
drop
|
||||
lib
|
|
@ -0,0 +1,21 @@
|
|||
name: mailerd
|
||||
version: 0.1.0
|
||||
|
||||
description: |
|
||||
Simple email sending service.
|
||||
|
||||
dependencies:
|
||||
kemal:
|
||||
github: kemalcr/kemal
|
||||
crinja:
|
||||
github: straight-shoota/crinja
|
||||
email:
|
||||
github: arcage/crystal-email
|
||||
authd:
|
||||
git: https://git.baguette.netlib.re/Baguette/authd.git
|
||||
|
||||
targets:
|
||||
mailerd:
|
||||
main: src/mailer.cr
|
||||
|
||||
license: ISC
|
|
@ -0,0 +1,227 @@
|
|||
require "email"
|
||||
require "option_parser"
|
||||
require "baguette-crystal-base"
|
||||
|
||||
require "crinja"
|
||||
|
||||
class Context
|
||||
class_property simulation : Bool = false
|
||||
class_property command : String = "undefined"
|
||||
|
||||
class_property args = [] of String
|
||||
end
|
||||
|
||||
class Baguette::Configuration
|
||||
class Mailer < Base
|
||||
# Default mailer: 127.0.0.1, not "localhost" for glibc fuckery reasons.
|
||||
# Without domain name resolution, one can statically compile the mailer
|
||||
# for machines without glibc (such as on alpine linux).
|
||||
property smtpd_host : String = "127.0.0.1"
|
||||
property smtpd_port : UInt16 = 25
|
||||
|
||||
property verbosity : Int32 = 3
|
||||
|
||||
# template => subject
|
||||
property templates : YAML::Any? = nil
|
||||
property templates_directory : String = "templates/"
|
||||
|
||||
def initialize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class OptionParser
|
||||
def to_s(io : IO)
|
||||
if banner = @banner
|
||||
io << banner
|
||||
io << "\n\n"
|
||||
end
|
||||
@flags.join io, "\n"
|
||||
end
|
||||
end
|
||||
|
||||
opt_simulation = -> (parser : OptionParser) {
|
||||
parser.on "-s", "--simulation", "Simulation: do not do anything." do
|
||||
Context.simulation = true
|
||||
Baguette::Log.info "Simulation."
|
||||
end
|
||||
}
|
||||
|
||||
unrecognized_args_to_context_args = -> (parser : OptionParser,
|
||||
nexact : Int32?,
|
||||
at_least : Int32?) {
|
||||
|
||||
# With the right args, these will be interpreted as serialized data.
|
||||
parser.unknown_args do |args|
|
||||
|
||||
# either we test with the exact expected number of arguments or the least.
|
||||
if exact = nexact
|
||||
if args.size != exact
|
||||
Baguette::Log.error "#{parser}"
|
||||
exit 1
|
||||
end
|
||||
elsif least = at_least
|
||||
if args.size < least
|
||||
Baguette::Log.error "#{parser}"
|
||||
exit 1
|
||||
end
|
||||
else
|
||||
Baguette::Log.error "Number of parameters not even provided!"
|
||||
Baguette::Log.error "#{parser}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
args.each do |arg|
|
||||
Baguette::Log.debug "Unrecognized argument: #{arg} (adding to Context.args)"
|
||||
if Context.args.nil?
|
||||
Context.args = Array(String).new
|
||||
end
|
||||
Context.args.not_nil! << arg
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
opt_help = -> (parser : OptionParser) {
|
||||
parser.on "-h", "--help", "Prints command usage." do
|
||||
puts "usage: #{PROGRAM_NAME} command -h"
|
||||
puts
|
||||
puts parser
|
||||
|
||||
case Context.command
|
||||
when /send/
|
||||
#ENUM.names.each do |n|
|
||||
# Baguette::Log.warning "- #{n}"
|
||||
#end
|
||||
end
|
||||
|
||||
exit 0
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
parser = OptionParser.new do |parser|
|
||||
parser.banner = "Welcome on the Altideal CLI administration."
|
||||
parser.on "-v verbosity", "--verbosity v", "Verbosity. From 0 to 4 (debug)." do |v|
|
||||
Baguette::Context.verbosity = v.to_i
|
||||
Baguette::Log.info "verbosity = #{v}"
|
||||
end
|
||||
|
||||
opt_simulation.call parser
|
||||
|
||||
parser.on("send", "Send an email.") do
|
||||
parser.banner = "usage: send <template> <email>"
|
||||
Baguette::Log.info "sending an email"
|
||||
Context.command = "send"
|
||||
unrecognized_args_to_context_args.call parser, nil, 2
|
||||
end
|
||||
|
||||
opt_help.call parser
|
||||
end
|
||||
|
||||
parser.parse
|
||||
|
||||
class Actions
|
||||
property the_call = {} of String => Proc(Nil)
|
||||
property mailer_configuration : Baguette::Configuration::Mailer
|
||||
property smtpd_client : EMail::Client
|
||||
|
||||
def initialize(@mailer_configuration, @smtpd_client)
|
||||
@the_call["send"] = ->send
|
||||
end
|
||||
|
||||
def env_params
|
||||
{
|
||||
"from" => ENV["FROM"],
|
||||
"subject" => ENV["SUBJECT"],
|
||||
}
|
||||
rescue e
|
||||
Baguette::Log.error "#{e}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# One-shot email transfer.
|
||||
def send
|
||||
template, to = Context.args.not_nil!
|
||||
Baguette::Log.info "sending #{template} to #{to}"
|
||||
|
||||
template_parameters = if t = @mailer_configuration.templates
|
||||
if t[template]?
|
||||
{
|
||||
"from" => t[template]["from"].to_s,
|
||||
"subject" => t[template]["subject"].to_s,
|
||||
}
|
||||
else
|
||||
env_params
|
||||
end
|
||||
else
|
||||
env_params
|
||||
end
|
||||
|
||||
env = Crinja.new
|
||||
env.loader = Crinja::Loader::FileSystemLoader.new @mailer_configuration.templates_directory
|
||||
|
||||
# Getting 'subject' and 'from' through an environment variable or configuration, else raise.
|
||||
subject = template_parameters["subject"]
|
||||
from = template_parameters["from"]
|
||||
|
||||
# Allow to rewrite the destination.
|
||||
to = ENV["TO"]? || to
|
||||
|
||||
template = env.get_template "#{template}.j2"
|
||||
|
||||
if Context.simulation
|
||||
Baguette::Log.info "(simulation) email to send should be:"
|
||||
Baguette::Log.info "subject: #{subject}"
|
||||
Baguette::Log.info "from: #{from}"
|
||||
Baguette::Log.info "to: #{to}"
|
||||
Baguette::Log.info "content: #{template.render ENV}"
|
||||
else
|
||||
content = template.render ENV
|
||||
|
||||
email = EMail::Message.new
|
||||
email.from from
|
||||
email.to to
|
||||
email.subject subject
|
||||
email.message content
|
||||
|
||||
smtpd_client.start do
|
||||
# In this block, default receiver is client
|
||||
send(email)
|
||||
end
|
||||
end
|
||||
rescue e
|
||||
Baguette::Log.error "#{e}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def main
|
||||
|
||||
mail_conf = if c = Baguette::Configuration::Mailer.get
|
||||
c
|
||||
else
|
||||
Baguette::Configuration::Mailer.new
|
||||
end
|
||||
|
||||
if Context.simulation
|
||||
pp! mail_conf
|
||||
end
|
||||
|
||||
smtpd_config = EMail::Client::Config.new mail_conf.smtpd_host, mail_conf.smtpd_port.to_i
|
||||
smtpd_client = EMail::Client.new smtpd_config
|
||||
|
||||
actions = Actions.new mail_conf, smtpd_client
|
||||
|
||||
# Now we did read the intent, we should proceed doing what was asked.
|
||||
begin
|
||||
actions.the_call[Context.command].call
|
||||
rescue e
|
||||
Baguette::Log.info "The command is not recognized (or implemented): #{e}."
|
||||
end
|
||||
|
||||
rescue e
|
||||
Baguette::Log.error "Exception: #{e}"
|
||||
end
|
||||
|
||||
main
|
Loading…
Reference in New Issue