Send a Welcome Email After Signup Using a Sidekiq Background Job in Rails

Shared by codesnips Jul 2026
4 tabs
class RegistrationsController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)

    if @user.save
      session[:user_id] = @user.id
      redirect_to dashboard_path, notice: "Welcome aboard! Check your inbox."
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.require(:user).permit(:email, :name, :password, :password_confirmation)
  end
end
4 files · ruby Explain with highlit

This snippet shows the idiomatic Rails way to send a welcome email after a user signs up without blocking the HTTP request. Sending email inline during a signup request couples the response time to an external SMTP provider, and any slowness or outage in that provider makes signup itself feel slow or fail. Moving delivery to a background job decouples the two: the controller returns quickly and the email is delivered by a worker process, with automatic retries if the provider is temporarily down.

In RegistrationsController, the create action builds and saves a User inside save_and_notify. The important detail is that the email is not enqueued from the controller directly. Instead the model owns that responsibility via an after_commit callback, so the job is only scheduled once the database transaction has actually committed. This avoids a classic race: if the job were enqueued inside the transaction (for example from after_create), a fast worker could pick up the job and query for a User row that has not yet been committed, producing a spurious RecordNotFound.

In User model, enqueue_welcome_email calls WelcomeEmailJob.perform_later(id). Passing the primary key rather than the whole object is deliberate: ActiveJob serializes arguments, and GlobalID would work, but a bare integer id keeps the payload tiny and forces the job to reload fresh state at execution time. The model also exposes welcome_email_sent? backed by a welcome_email_sent_at timestamp column, which is the foundation for idempotency.

In WelcomeEmailJob, the job is a plain ApplicationJob on the mailers queue. It uses find_by rather than find so a deleted user simply returns early instead of raising and burning retries. The return if user.welcome_email_sent? guard makes the job idempotent: Sidekiq guarantees at-least-once execution, meaning a job can run more than once after a crash or a retry, so the code must tolerate duplicate invocations. Delivery uses deliver_now because the job is already asynchronous — calling deliver_later here would pointlessly enqueue a second job. After a successful send, touch(:welcome_email_sent_at) records the fact so subsequent runs short-circuit.

The trade-off worth understanding is the ordering of the guard and the send. If the process dies between deliver_now and touch, the same email could be sent twice; if the timestamp were set first, a crash could drop the email entirely. This implementation favors delivery over strict exactly-once, which is the usual choice for welcome emails where a rare duplicate is harmless but a missing email is not. For stricter guarantees a developer would introduce a provider-level idempotency key.

This pattern generalizes to almost any post-signup side effect — provisioning, analytics, syncing to a CRM — where the work should happen reliably but need not happen synchronously. Keeping the enqueue in after_commit and the idempotency guard in the job are the two habits that make it robust in production.

Share this code

Here's the card — post it anywhere.

Send a Welcome Email After Signup Using a Sidekiq Background Job in Rails — share card
Link copied