Class: Inquirex::SendEmail

Inherits:
Object
  • Object
show all
Defined in:
lib/inquirex/send_email.rb

Overview

A declarative email built from the collected answers, declared at flow level with the DSL verb send_email. This is the only server-side completion declaration the core gem carries — anything richer (webhooks, CRM pushes, custom code) belongs to the host application.

send_email if: not_empty(:email) do
to      "{{email}}"
from    "forms@agentica.group"
subject "Thanks {{name}} — we got your inquiry"
markdown_text <<~TEXT
  Hi {{name}},

  We received your answers and will reply within one business day.

  {{answers_summary}}
TEXT
end

Nothing is ever delivered by this gem. A SendEmail is data: templated header fields plus one or more body templates, serialized into the definition JSON under "send_emails". The host application decides when (and whether) to render and deliver — either from the serialized fields directly, or via #to_mail, which builds a Mail::Message (the object ActionMailer wraps).

Scalar fields (to, from, cc, bcc, reply_to, subject) and the text / markdown_text bodies render {field} values verbatim; the html body HTML-escapes every interpolated value automatically. markdown_text is carried on the wire as markdown — the core gem never renders Markdown to HTML (no dependencies); hosts that want an HTML part render it themselves.

Bodies accept an inline template String or { file: "path" }, which is read once at definition time and inlined — a definition rehydrated from JSON never touches the filesystem.

The mail gem is a soft dependency, required only when #to_mail is called. Rails hosts always have it (ActionMailer depends on it).

Constant Summary collapse

SCALAR_FIELDS =

Scalar header fields rendered verbatim via Template.render_text in #to_mail and #to_h.

%i[to from cc bcc reply_to subject].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(to: nil, subject: nil, from: nil, cc: nil, bcc: nil, reply_to: nil, text: nil, markdown_text: nil, html: nil, headers: {}, rule: nil) ⇒ SendEmail

Returns a new instance of SendEmail.

Parameters:

  • to (String) (defaults to: nil)

    recipient template (required; keyword defaults to nil so a missing field raises the friendly DefinitionError from #validate!)

  • subject (String) (defaults to: nil)

    subject template (required)

  • text (String, Hash, nil) (defaults to: nil)

    plain-text body template or { file: }

  • markdown_text (String, Hash, nil) (defaults to: nil)

    Markdown body template or { file: }

  • html (String, Hash, nil) (defaults to: nil)

    HTML body template or { file: }

  • headers (Hash) (defaults to: {})

    extra headers (values support {field})

  • rule (Rules::Base, nil) (defaults to: nil)

    serializable gate (the DSL's if: option)

Raises:



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/inquirex/send_email.rb', line 70

def initialize(to: nil, subject: nil, from: nil, cc: nil, bcc: nil, reply_to: nil,
  text: nil, markdown_text: nil, html: nil, headers: {}, rule: nil)
  @to = to
  @from = from
  @cc = cc
  @bcc = bcc
  @reply_to = reply_to
  @subject = subject
  @text = resolve_body(text)
  @markdown_text = resolve_body(markdown_text)
  @html = resolve_body(html)
  @headers = headers.transform_keys(&:to_s).freeze
  @rule = rule
  validate!
  freeze
end

Instance Attribute Details

#bccString? (readonly)

Returns optional address templates ({field} placeholders allowed).

Returns:

  • (String, nil)

    optional address templates ({field} placeholders allowed)



50
51
52
# File 'lib/inquirex/send_email.rb', line 50

def bcc
  @bcc
end

#ccString? (readonly)

Returns optional address templates ({field} placeholders allowed).

Returns:

  • (String, nil)

    optional address templates ({field} placeholders allowed)



50
51
52
# File 'lib/inquirex/send_email.rb', line 50

def cc
  @cc
end

#fromString? (readonly)

Returns optional address templates ({field} placeholders allowed).

Returns:

  • (String, nil)

    optional address templates ({field} placeholders allowed)



50
51
52
# File 'lib/inquirex/send_email.rb', line 50

def from
  @from
end

#headersHash{String => String} (readonly)

Returns extra headers (values support {field}).

Returns:

  • (Hash{String => String})

    extra headers (values support {field})



56
57
58
# File 'lib/inquirex/send_email.rb', line 56

def headers
  @headers
end

#htmlString? (readonly)

Returns body template, inlined at definition time when { file: } was given.

Returns:

  • (String, nil)

    body template, inlined at definition time when { file: } was given



53
54
55
# File 'lib/inquirex/send_email.rb', line 53

def html
  @html
end

#markdown_textString? (readonly)

Returns body template, inlined at definition time when { file: } was given.

Returns:

  • (String, nil)

    body template, inlined at definition time when { file: } was given



53
54
55
# File 'lib/inquirex/send_email.rb', line 53

def markdown_text
  @markdown_text
end

#reply_toString? (readonly)

Returns optional address templates ({field} placeholders allowed).

Returns:

  • (String, nil)

    optional address templates ({field} placeholders allowed)



50
51
52
# File 'lib/inquirex/send_email.rb', line 50

def reply_to
  @reply_to
end

#ruleRules::Base? (readonly)

Returns gate — the email applies only when the rule is true.

Returns:

  • (Rules::Base, nil)

    gate — the email applies only when the rule is true



59
60
61
# File 'lib/inquirex/send_email.rb', line 59

def rule
  @rule
end

#subjectString (readonly)

Returns required recipient / subject templates ({field} placeholders allowed).

Returns:

  • (String)

    required recipient / subject templates ({field} placeholders allowed)



47
48
49
# File 'lib/inquirex/send_email.rb', line 47

def subject
  @subject
end

#textString? (readonly)

Returns body template, inlined at definition time when { file: } was given.

Returns:

  • (String, nil)

    body template, inlined at definition time when { file: } was given



53
54
55
# File 'lib/inquirex/send_email.rb', line 53

def text
  @text
end

#toString (readonly)

Returns required recipient / subject templates ({field} placeholders allowed).

Returns:

  • (String)

    required recipient / subject templates ({field} placeholders allowed)



47
48
49
# File 'lib/inquirex/send_email.rb', line 47

def to
  @to
end

Class Method Details

.from_h(hash) ⇒ SendEmail

Parameters:

  • hash (Hash)

    string or symbol keys

Returns:



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/inquirex/send_email.rb', line 133

def self.from_h(hash)
  fetch = ->(key) { hash[key.to_s] || hash[key.to_sym] }
  rule_data = fetch.call(:if)
  new(
    to:            fetch.call(:to),
    from:          fetch.call(:from),
    cc:            fetch.call(:cc),
    bcc:           fetch.call(:bcc),
    reply_to:      fetch.call(:reply_to),
    subject:       fetch.call(:subject),
    text:          fetch.call(:text),
    markdown_text: fetch.call(:markdown_text),
    html:          fetch.call(:html),
    headers:       fetch.call(:headers) || {},
    rule:          rule_data ? Rules::Base.from_h(rule_data) : nil
  )
end

Instance Method Details

#applicable?(answers_hash) ⇒ Boolean

Whether this email should be built for the given answers — true when no gate was declared or the gate rule evaluates to true.

Parameters:

  • answers_hash (Hash)

    step_id => value context for rule evaluation

Returns:

  • (Boolean)


92
93
94
# File 'lib/inquirex/send_email.rb', line 92

def applicable?(answers_hash)
  @rule.nil? || @rule.evaluate(answers_hash)
end

#to_hHash

Returns wire format, same shape .from_h accepts.

Returns:

  • (Hash)

    wire format, same shape .from_h accepts



117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/inquirex/send_email.rb', line 117

def to_h
  hash = {}
  hash["if"] = @rule.to_h if @rule
  SCALAR_FIELDS.each do |field|
    value = public_send(field)
    hash[field.to_s] = value if value
  end
  hash["text"] = @text if @text
  hash["markdown_text"] = @markdown_text if @markdown_text
  hash["html"] = @html if @html
  hash["headers"] = @headers unless @headers.empty?
  hash
end

#to_mail(answers) ⇒ Mail::Message

Builds a Mail::Message from the templates and the given answers. Pure function — safe to call from a background job to rebuild messages from persisted answers. The text part is @text, falling back to @markdown_text rendered verbatim (Markdown reads fine as plain text); the html part is @html when present.

Parameters:

Returns:

  • (Mail::Message)


104
105
106
107
108
109
110
111
112
113
114
# File 'lib/inquirex/send_email.rb', line 104

def to_mail(answers)
  require_mail!
  mail = ::Mail.new
  SCALAR_FIELDS.each do |field|
    value = public_send(field)
    mail.public_send(:"#{field}=", Template.render_text(value, answers)) if value
  end
  @headers.each { |name, value| mail.header[name] = Template.render_text(value.to_s, answers) }
  attach_bodies(mail, answers)
  mail
end