Module: Goodmail
- Extended by:
- Configuration
- Defined in:
- lib/goodmail.rb,
lib/goodmail/email.rb,
lib/goodmail/error.rb,
lib/goodmail/layout.rb,
lib/goodmail/mailer.rb,
lib/goodmail/builder.rb,
lib/goodmail/version.rb,
lib/goodmail/plaintext.rb,
lib/goodmail/dispatcher.rb,
lib/goodmail/configuration.rb,
lib/goodmail/action_mailer_integration.rb
Overview
The main namespace for the Goodmail gem. Provides configuration and the primary .compose method.
Defined Under Namespace
Modules: ActionMailerIntegration, Configuration, Dispatcher, Layout, Plaintext Classes: Builder, EmailParts, Error, Mailer
Constant Summary collapse
- VERSION =
"0.4.0"- LIST_UNSUBSCRIBE_HEADER =
"List-Unsubscribe"- LIST_UNSUBSCRIBE_POST_HEADER =
"List-Unsubscribe-Post"- LIST_UNSUBSCRIBE_ONE_CLICK_VALUE =
"List-Unsubscribe=One-Click"- GOODMAIL_RENDER_HEADER_KEYS =
%i[ preheader unsubscribe_url locale context config configuration layout_path ].freeze
Constants included from Configuration
Configuration::DEFAULT_CONFIG, Configuration::REQUIRED_CONFIG_KEYS, Configuration::THREAD_CONFIG_KEY
Class Method Summary collapse
-
.action_mailer_headers(headers) ⇒ Object
Returns the header hash Goodmail should hand to Action Mailer’s ‘mail`.
-
.compose(headers = {}, &block) ⇒ ActionMailer::MessageDelivery
Composes an ActionMailer::MessageDelivery using the Goodmail DSL and layout.
-
.install_action_mailer_integration!(base = ActionMailer::Base) ⇒ Object
Installs Goodmail’s mailer helpers once on ActionMailer::Base so app mailers, Devise mailers, Pay mailers, and other custom Action Mailer subclasses can call ‘goodmail_mail` without per-class include glue.
-
.list_unsubscribe_headers(unsubscribe_url) ⇒ Object
Returns the deliverability headers for a configured unsubscribe URL.
- .one_click_unsubscribe_url?(url) ⇒ Boolean
-
.render(headers = {}, &dsl_block) ⇒ Goodmail::EmailParts
Renders the email content using the Goodmail DSL and returns HTML and text parts.
- .render_header_key?(key) ⇒ Boolean
Methods included from Configuration
config, config_with, configure, global_config, reset_config!, with_config
Class Method Details
.action_mailer_headers(headers) ⇒ Object
Returns the header hash Goodmail should hand to Action Mailer’s ‘mail`. Rails intentionally accepts arbitrary message headers and filters only framework-only render keys internally; Goodmail should follow that shape instead of maintaining a narrow whitelist of envelope fields. Source: github.com/rails/rails/blob/debbd18c562df17d01944c475e9291d927910b58/actionmailer/lib/action_mailer/base.rb#L972-L976
59 60 61 62 63 64 65 |
# File 'lib/goodmail/action_mailer_integration.rb', line 59 def self.action_mailer_headers(headers) headers.each_with_object({}) do |(key, value), result| next if render_header_key?(key) result[key] = value end end |
.compose(headers = {}, &block) ⇒ ActionMailer::MessageDelivery
Composes an ActionMailer::MessageDelivery using the Goodmail DSL and layout.
This is the primary entry point for creating emails with Goodmail. The returned MessageDelivery can then have ‘.deliver_now` or `.deliver_later` called on it.
48 49 50 51 |
# File 'lib/goodmail.rb', line 48 def self.compose(headers = {}, &block) # Delegate the actual building process to the Dispatcher Dispatcher.(headers, &block) end |
.install_action_mailer_integration!(base = ActionMailer::Base) ⇒ Object
Installs Goodmail’s mailer helpers once on ActionMailer::Base so app mailers, Devise mailers, Pay mailers, and other custom Action Mailer subclasses can call ‘goodmail_mail` without per-class include glue.
The helper methods stay private because Action Mailer dispatches mailer actions through ‘action_methods`; keeping the API private prevents Rails from treating helpers as deliverable actions. Source: github.com/rails/rails/blob/debbd18c562df17d01944c475e9291d927910b58/actionmailer/lib/action_mailer/base.rb#L614-L618
81 82 83 84 85 |
# File 'lib/goodmail/action_mailer_integration.rb', line 81 def self.install_action_mailer_integration!(base = ActionMailer::Base) return if base < ActionMailerIntegration base.include(ActionMailerIntegration) end |
.list_unsubscribe_headers(unsubscribe_url) ⇒ Object
Returns the deliverability headers for a configured unsubscribe URL.
RFC 8058 one-click unsubscribe is only eligible for HTTPS List-Unsubscribe URLs and uses the exact ‘List-Unsubscribe=One-Click` POST body. Keep the classic List-Unsubscribe header for other non-blank values, but don’t advertise one-click POST support unless the URL qualifies.
Sources:
- RFC 8058 §3.1:
https://www.rfc-editor.org/rfc/rfc8058#section-3.1
- Gmail sender guidelines:
https://support.google.com/mail/answer/81126
- Yahoo sender best practices:
https://senders.yahooinc.com/best-practices/
33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/goodmail/action_mailer_integration.rb', line 33 def self.list_unsubscribe_headers(unsubscribe_url) return {} unless unsubscribe_url.is_a?(String) stripped_url = unsubscribe_url.strip return {} if stripped_url.empty? headers = { LIST_UNSUBSCRIBE_HEADER => "<#{stripped_url}>" } if one_click_unsubscribe_url?(stripped_url) headers[LIST_UNSUBSCRIBE_POST_HEADER] = LIST_UNSUBSCRIBE_ONE_CLICK_VALUE end headers end |
.one_click_unsubscribe_url?(url) ⇒ Boolean
46 47 48 49 50 51 |
# File 'lib/goodmail/action_mailer_integration.rb', line 46 def self.one_click_unsubscribe_url?(url) uri = URI.parse(url) uri.is_a?(URI::HTTPS) && !uri.host.to_s.empty? rescue URI::InvalidURIError false end |
.render(headers = {}, &dsl_block) ⇒ Goodmail::EmailParts
Renders the email content using the Goodmail DSL and returns HTML and text parts. This method does not send the email but prepares its content for sending.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/goodmail/email.rb', line 29 def self.render(headers = {}, &dsl_block) # 1. Initialize the Builder and execute the DSL block current_headers = headers.dup # Avoid modifying the original headers hash directly context = render_header_value!(current_headers, :context) locale = render_header_value!(current_headers, :locale) render_config = render_header_value!(current_headers, :config) render_config = render_header_value!(current_headers, :configuration) if render_config.nil? layout_path = render_header_value!(current_headers, :layout_path) subject = render_header_value!(current_headers, :subject) Goodmail.with_config(render_config) do builder = Goodmail::Builder.new(context: context) evaluate_builder_dsl(builder, locale, &dsl_block) core_html_content = builder.html_output # 2. Determine unsubscribe_url and preheader # These are removed from headers as they are Goodmail-specific, not standard mail headers. unsubscribe_url = render_header_value!(current_headers, :unsubscribe_url) || Goodmail.config.unsubscribe_url preheader = render_header_value!(current_headers, :preheader) || Goodmail.config.default_preheader || subject # 3. Render the raw HTML body using the Layout # The subject is passed for the <title> tag and potentially other uses in layout. # Unsubscribe URL and preheader are passed for inclusion in the layout. raw_html_body = Goodmail::Layout.render( core_html_content, subject, layout_path: layout_path, unsubscribe_url: unsubscribe_url, preheader: preheader ) # 4. Run Premailer for CSS inlining (HTML part). Plaintext goes # through `Goodmail::Plaintext` which pre-processes the source # HTML to neutralize MSO-only markup and the hidden preheader # span — both of which Premailer's plaintext extractor would # otherwise leak into the text body. premailer = Premailer.new( raw_html_body, with_html_string: true, adapter: :nokogiri, preserve_styles: false, # Force inlining and remove <style> block remove_ids: true, # Remove IDs remove_comments: false, # Keep MSO conditional comments in HTML input_encoding: "UTF-8" # See Goodmail::Plaintext for the full # rationale — short version: Premailer # double-encodes accented characters when # the source has no <meta charset>. ) final_inlined_html = premailer.to_inline_css final_plain_text = Goodmail::Plaintext.generate(raw_html_body, preheader: preheader) # 5. Return the structured parts EmailParts.new( html: final_inlined_html, text: final_plain_text, attachments: builder. ) end end |
.render_header_key?(key) ⇒ Boolean
67 68 69 70 |
# File 'lib/goodmail/action_mailer_integration.rb', line 67 def self.render_header_key?(key) GOODMAIL_RENDER_HEADER_KEYS.include?(key) || (key.is_a?(String) && GOODMAIL_RENDER_HEADER_KEYS.include?(key.to_sym)) end |