# frozen-string-literal: truerequire'stringio'require'mail'classRodamoduleRodaPlugins# The mailer plugin allows your Roda application to send emails easily.## class App < Roda# plugin :render# plugin :mailer## route do |r|# r.on "albums" do# r.mail "added" do |album|# @album = album# from 'from@example.com'# to 'to@example.com'# cc 'cc@example.com'# bcc 'bcc@example.com'# subject 'Album Added'# add_file "path/to/album_added_img.jpg"# render(:albums_added_email) # body# end# end# end# end## The default method for sending a mail is +sendmail+:## App.sendmail("/albums/added", Album[1])## If you want to return the <tt>Mail::Message</tt> instance for further modification,# you can just use the +mail+ method:## mail = App.mail("/albums/added", Album[1])# mail.from 'from2@example.com'# mail.deliver## The mailer plugin uses the mail gem, so if you want to configure how# email is sent, you can use <tt>Mail.defaults</tt> (see the mail gem documentation for# more details):## Mail.defaults do# delivery_method :smtp, :address=>'smtp.example.com', :port=>587# end## You can support multipart emails using +text_part+ and +html_part+:## r.mail "added" do |album_added|# from 'from@example.com'# to 'to@example.com'# subject 'Album Added'# text_part render('album_added.txt') # views/album_added.txt.erb# html_part render('album_added.html') # views/album_added.html.erb# end## In addition to allowing you to use Roda's render plugin for rendering# email bodies, you can use all of Roda's usual routing tree features# to DRY up your code:## r.on "albums/:d" do |album_id|# @album = Album[album_id.to_i]# from 'from@example.com'# to 'to@example.com'## r.mail "added" do# subject 'Album Added'# render(:albums_added_email)# end## r.mail "deleted" do# subject 'Album Deleted'# render(:albums_deleted_email)# end# end## When sending a mail via +mail+ or +sendmail+, a RodaError will be raised# if the mail object does not have a body. This is similar to the 404# status that Roda uses by default for web requests that don't have# a body. If you want to specifically send an email with an empty body, you# can use the explicit empty string:## r.mail do# from 'from@example.com'# to 'to@example.com'# subject 'No Body Here'# ""# end## If while preparing the email you figure out you don't want to send an# email, call +no_mail!+:## r.mail '/welcome/:d' do |id| # no_mail! unless user = User[id]# # ...# end## By default, the mailer uses text/plain as the Content-Type for emails.# You can override the default by specifying a :content_type option when# loading the plugin:## plugin :mailer, :content_type=>'text/html'## The mailer plugin does support being used inside a Roda application# that is handling web requests, where the routing block for mails and# web requests is shared. However, it's recommended that you create a# separate Roda application for emails. This can be a subclass of your main# Roda application if you want your helper methods to automatically be# available in your email views.moduleMailerREQUEST_METHOD="REQUEST_METHOD".freezePATH_INFO="PATH_INFO".freezeSCRIPT_NAME='SCRIPT_NAME'.freezeEMPTY_STRING=''.freezeRACK_INPUT='rack.input'.freezeRODA_MAIL='roda.mail'.freezeRODA_MAIL_ARGS='roda.mail_args'.freezeMAIL="MAIL".freezeCONTENT_TYPE='Content-Type'.freezeTEXT_PLAIN="text/plain".freezeOPTS={}.freeze# Error raised when the using the mail class method, but the routing# tree doesn't return the mail object. classError<::Roda::RodaError;end# Set the options for the mailer. Options:# :content_type :: The default content type for emails (default: text/plain)defself.configure(app,opts=OPTS)app.opts[:mailer]=(app.opts[:mailer]||{}).merge(opts).freezeendmoduleClassMethods# Return a Mail::Message instance for the email for the given request path# and arguments. You can further manipulate the returned mail object before# calling +deliver+ to send the mail.defmail(path,*args)mail=::Mail.newcatch(:no_mail)dounlessmail.equal?(new(PATH_INFO=>path,SCRIPT_NAME=>EMPTY_STRING,REQUEST_METHOD=>MAIL,RACK_INPUT=>StringIO.new,RODA_MAIL=>mail,RODA_MAIL_ARGS=>args).call(&route_block))raiseError,"route did not return mail instance for #{path.inspect}, #{args.inspect}"endmailendend# Calls +mail+ and immediately sends the resulting mail.defsendmail(*args)ifm=mail(*args)m.deliverendendendmoduleRequestMethods# Similar to routing tree methods such as +get+ and +post+, this matches# only if the request method is MAIL (only set when using the Roda class# +mail+ or +sendmail+ methods) and the rest of the arguments match# the request. This yields any of the captures to the block, as well as# any arguments passed to the +mail+ or +sendmail+ Roda class methods.defmail(*args)if@env[REQUEST_METHOD]==MAILif_match(args)do|*vs|yield(*(vs+@env[RODA_MAIL_ARGS]))endendendendmoduleResponseMethods# The mail object related to the current request.attr_accessor:mail# If the related request was an email request, add any response headers# to the email, as well as adding the response body to the email.# Return the email unless no body was set for it, which would indicate# that the routing tree did not handle the request.deffinishifm=mailheader_content_type=@headers.delete(CONTENT_TYPE)m.headers(@headers)m.body(@body.join)unless@body.empty?mail_attachments.eachdo|a,block|m.add_file(*a)block.callifblockendifcontent_type=header_content_type||roda_class.opts[:mailer][:content_type]ifmail.multipart?ifmail.content_type=~/multipart\/mixed/&&mail.parts.length>=2&&(part=mail.parts.find{|p|!p.attachment&&p.content_type==TEXT_PLAIN})part.content_type=content_typeendelsemail.content_type=content_typeendendunlessm.body.to_s.empty?&&m.parts.empty?&&@body.empty?mendelsesuperendend# The attachments related to the current mail.defmail_attachments@mail_attachments||=[]endendmoduleInstanceMethods# Add delegates for common email methods.[:from,:to,:cc,:bcc,:subject].eachdo|meth|define_method(meth)do|*args|env[RODA_MAIL].send(meth,*args)nilendend[:text_part,:html_part].eachdo|meth|define_method(meth)do|*args|_mail_part(meth,*args)endend# If this is an email request, set the mail object in the response, as well# as the default content_type for the email.definitialize(env)superifmail=env[RODA_MAIL]res=@_responseres.mail=mailres.headers.delete(CONTENT_TYPE)endend# Delay adding a file to the message until after the message body has been set.# If a block is given, the block is called after the file has been added, and you# can access the attachment via <tt>response.mail.attachments.last</tt>.defadd_file(*a,&block)response.mail_attachments<<[a,block]nilend# Signal that no mail should be sent for this request.defno_mail!throw:no_mailendprivate# Set the text_part or html_part (depending on the method) in the related email,# using the given body and optional headers.def_mail_part(meth,body,headers=nil)env[RODA_MAIL].send(meth)dobody(body)headers(headers)ifheadersendnilendendendregister_plugin(:mailer,Mailer)endend