Sending newsletters via rapidmail with SMTP and one-click unsubscribe

Posted . Visible to the public. Repeats.

If you need to implement newsletter sending, rapidmail Show archive.org snapshot is a solid option.

Support is very fast, friendly and helpful, and the initial setup is simple. Since rapidmail works via SMTP, you can simply ask the Ops team to configure SMTP credentials for your application.

You also do not need to use rapidmail’s built-in newsletter feature. Instead, you can send emails as transactional mails, which allows you to keep the entire newsletter logic inside your application.

One thing to keep an eye on is the sending quota. Rapidmail notifies you when 70% of the quota is used, so you have enough time to scale the plan if needed.

Implementing Gmail's one-click unsubscribe

Modern mail clients (e.g. Gmail) support a native unsubscribe button if your email contains the appropriate headers.

Image

You only need to set the following headers in your mailer:

  • List-Unsubscribe
  • List-Unsubscribe-Post

Example (simplified):

def press_release(press_release, frontend_user, locale: I18n.default_locale, from: DEFAULT_NEWSLETTER_SENDER_ADDRESS)
  @subject = press_release.title.localize(locale)
  @token = frontend_user.unsubscription_token

  headers['List-Unsubscribe'] = "<#{unsubscribe_all_unsubscribe_url(token: @token)}>"
  headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click'

  mail from:,
    to: frontend_user.email,
    subject: @subject
end

Generating an unsubscribe token

Each user needs a token that allows them to unsubscribe securely.

Rails' signed_id works well for this:

class FrontendUser < ApplicationRecord

  # ...

  def unsubscription_token
    return unless persisted?

    signed_id(purpose: :unsubscription)
  end
  
  # ...
  
end  

Processing the unsubscribe request

The unsubscribe endpoint receives the token and updates the user's newsletter preference.

# Optional form model with ActiveType
class FrontendUser::Unsubscription < ActiveType::Record[FrontendUser]
 
  # ...

end
# routes.rb
resource :unsubscribe, only: [:show, :update], controller: :unsubscribe do
  member do
    post :unsubscribe_all
  end
end
# app/controllers/frontend/unsubscribe_controller.rb

def unsubscribe_all
  load_unsubscription

  if @unsubscription.update(receives_newsletter: false)
    head :ok
  else
    head :unprocessable_entity
  end
end

private

def load_unsubscription
  @unsubscribe_token = params[:token]
  @unsubscription = FrontendUser::Unsubscription.find_signed(@unsubscribe_token, purpose: :unsubscription)

  unless @unsubscription
    respond_to do |format|
      format.html do
        redirect_to root_path, alert: I18n.t('frontend.unsubscribe.invalid_link')
      end

      format.all do
        head :not_found
      end
    end
  end
end

The endpoint should accept the POST request triggered by the List-Unsubscribe-Post header and return 200 OK if the unsubscribe succeeds.

Profile picture of Dominic Beger
Dominic Beger
Last edit
Dominic Beger
Attachments
License
Source code in this card is licensed under the MIT License.
Posted by Dominic Beger to makandra dev (2026-03-04 09:43)