Skip to content

High email drop rate #262

Closed
Closed

Description

@lmrodriguezr

A large number of emails are not being sent. It appears that mail confirmation messages are usually received, but "authors contacts" are only sent about half of the time, while monthly reminders are almost never sent.

Activity

  1. added theissue type on May 28, 2026
  2. added this to the v1.1 milestone on May 28, 2026
  3. vangberg commented on Jun 3, 2026

    @vangberg
    Contributor

    Today someone on Slack reported not receiving confirmation mail, fwiw.

  4. vangberg commented on Jun 18, 2026

    @vangberg
    Contributor

    I think I found the issue. We have two different cases of mail not being sent:

    1. From cron jobs, e.g. bundle exec bin/rails runner -e production ''ReminderMail.register_reminder".
    2. From Passenger application process, e.g. mails sent with deliver_later from a controller.

    cron jobs

    The cron job fails to send the email because register_reminder eventually calls AdminMailer.with(user: user, registers: registers).register_reminder_email.deliver_later, but we are using the async in-process queue adapter. #deliver_later returns immediately and the Ruby process terminates before the background job is finished.

    This can be reproduced in production:

    bundle exec rails runner -e production 'ExternalMailer.with(reply_to: "harry@vangberg.name", to: "harry@vangberg.name", subject: "Test").simple_email(Contact.find(30).message).deliver_later'

    This does not send anything. But if you wait for e.g. 5 seconds before exiting, the mail is sent:

    bundle exec rails runner -e production 'ExternalMailer.with(reply_to: "harry@vangberg.name", to: "harry@vangberg.name", subject: "Test").simple_email(Contact.find(30).message).deliver_later; sleep 5'

    Short term fix

    Change from deliver_later to deliver_now. ReminderMail.register_reminder is not a long-running task, so this should not be a problem. Pull request @ #283

    Passenger application processes

    The same issue arises in the second case. When a Passenger application process exits, any pending jobs will not be run. This should only be a problem when a worker crashes or when restarting because a new version is deployed. But as it turns out, our Passenger application processes are very short-lived:

    $ passenger-status
    [...]
    /var/www/registry/code (production):
      App root: /var/www/registry/code
      Requests in queue: 0
      * PID: 1979579   Sessions: 0       Processed: 13      Uptime: 38s
        CPU: 0%      Memory  : 49M     Last used: 0s ago
      * PID: 1979670   Sessions: 0       Processed: 2       Uptime: 2s
        CPU: 0%      Memory  : 0M      Last used: 2s ago
      * PID: 1979697   Sessions: 0       Processed: 0       Uptime: 1s
        CPU: 0%      Memory  : 0M      Last used: 1s ago

    This is because passenger_max_requests is set to 30:

    The maximum number of requests an application process will process. After serving that many requests, the application process will be shut down and Passenger will restart it. A value of 0 means that there is no maximum. The application process might also be shut down if its idle timeout is reached.

    $ cat /etc/nginx/sites-enabled/registry.conf | grep max_requests
      passenger_max_requests 30;
        passenger_max_requests 30;

    With the number of requests the Registry is handling, Passenger application processes are constantly churned, so the probability of background jobs being dropped/mails not being sent is quite high. I have not verified that this is actually what happens, but it seems very likely.

    Short term fix

    Setting passenger_max_requests to its default of 0 will make it much less likely that mails are dropped. This should not be a problem, unless we have memory leak issues. At the very least, increasing it to a much larger number would be sensible.

    Long term fix

    Use solid_queue which persists jobs to the database and runs them in an external worker. This way jobs would never be dropped. This would also allow us to e.g. automatically retry failed jobs, which is nice for all of the jobs we have that contact external services that might be unreliable.

    solid_queue only supports Rails 7, so #257 needs to be done first, and we would need to run a worker process and restart it on deploy.

  5. pinned this issue on Jun 18, 2026
  6. unpinned this issue on Jun 18, 2026
  7. vangberg commented on Jun 30, 2026

    @vangberg
    Contributor

    I was wondering if the mails sent by Devise were affected by the same issue as in #292, but Devise defaults to deliver_now, so this should not be an issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions