Initial commit for the generic mailer.

master
Karchnu 2020-11-01 16:28:02 +01:00
commit c04544838d
3 changed files with 252 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
shard.lock
bin
drop
lib

21
shard.yml Normal file
View File

@ -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

227
src/mailer.cr Normal file
View File

@ -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