Un maño entre gaúchos

Archive for the ‘postfix’ tag

Return-Path en un mail desde Rails

without comments

Pongamos la clásica funcionalidad web en la que un usuario lee una noticia y tiene el típico enlace “Mandar la noticia a un amigo” de forma que dicho amigo va a recibir un mail enviado por un servicio de noticias, pero va a saber que quien se lo ha querido enviar es su colega. Lo que necesito entonces es poder mandar emails desde rails que cumplan las siguientes condiciones:

  • La dirección de origen que aparezca en el email, es decir la cabecera From ha de ser una genérica asociada al servicio, como por ejemplo noreply@example.com.
  • El Reply-To ha de ser otra dirección, pongamos la del usuario que ha querido enviar el mail a su amigo.
  • Quiero estar seguro de que cada email que se envía desde este servicio se envía correctamente y si no lo hace, al menos conocer la razón de porque no ha sido así. Por lo tanto necesito que si falla el envío, el servidor SMTP donde ha fallado sea capaz de devolverme una respuesta para que mi sistema pueda ir parseando emails de respuesta de envios fallidos. Eso sí lo que no quiero es que estas respuestas vayan a parar al buzón del sistema del usuario con el que está arrancada la aplicación web, ya que se me pueden mezclar en ese buzón un montón de cosas y no solo las respuestas de esos envíos concretos de correo. Por lo tanto necesito poder indicarle a rails esa dirección de respuesta de errores. La cabecera que se encarga de proporcionarle está información a los servidores de correo es la de Return-Path. Pongamos que está dirección es fail@example.com

Así que el código de ejemplo de un envío de este tipo sería el siguiente:
En el config/environments/production.rb:


config.action_mailer.delivery_method = :sendmail

y en la clase del ActionMailer::Base:

class MessageMailer < ActionMailer::Base
  def message_email(email_data)
    recipients  email_data[:recipient]
    from        "noreply@example.com"
    headers     "Return-Path" => "fail@example.com"
    reply_to    email_data[:sender_email]
    subject     email_data[:subject]
    body        :body => email_data[:body]
  end
end

Si se tienen en la aplicación muchos métodos de envío de correos diferentes, para no tener que indicar siempre el header de Reply-To, se puede poner a mano en el environment como parametro de sendmail:

  config.action_mailer.delivery_method = :sendmail
  config.action_mailer.sendmail_settings = {
    :location => '/usr/sbin/sendmail',
    :arguments => '-i -t -f fail@example.com'
  }

Written by luis

April 17th, 2009 at 5:14 pm

ActiveLDAP, validaciones y callbacks

without comments

Mientras intentaba implementar el soporte de vacaciones con gnarwl y postfix en una aplicación Rails de gestión de usuarios en LDAP me he encontrado con una situación que no me esperaba.

Primero he de comentar por encima en que consiste gnarwl. Se trata de un script al que se le pasa por la entrada standard un correo en texto plano, con su remitente y destinatario. Después el script busca el destinatario del correo en el árbol LDAP donde está guardada toda su información y comprueba si el atributo vacationActive está activado. Si lo está, le envía al remitente del correo un mail con el texto indicado en el atributo vacationInfo.

Al grano. En el modelo del usuario, gracias a ActiveLDAP, tenía indicado que uno de los objectClass que definen a todo usuario en esta aplicación en concreto es el objectClass Vacation.

ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=Usuarios',
             :classes => ['top', 'person', 'qmailUser', 'inetOrgPerson', 'Vacation']

Como no quería sobrecargar el formulario de creación de usuario con un campo/checkbox “Vacaciones”, he metido un callback before_create en el modelo del usuario, de la siguiente forma:

before_create :set_vacation

def set_vacation
  self.vacationActive = false
end

De esta forma, la propia definición de objectClass Vacation del usuario se iba a encargar de asignarle ese objectClass y el before_create se iba a encargar de crear el atributo vacationActive y de ponerlo a false.

Cual ha sido mi sorpresa al ir a crear un usuario nuevo y recibir un mensaje típico de validación que no esperaba:

1 error prohibited this user from being saved

There were problems with the following fields:

    * vacationActive is required attribute by objectClass 'Vacation'

Al principio me ha despistado un poco, pero al final he visto cual era el problema.
Obviamente sabía que cuando una entrada en LDAP tiene el objectClass Vacation, se exige que como mínimo tenga también el atributo vacationActive definido. Lo que no sabía era que ActiveLDAP genera las validaciones en tiempo real consultando primero los objectClass de esa entrada y generandose su lista con los atributos de los que dependen esos objectClass.

Por lo tanto el before_create añadido anteriormente no sirve, ya que es necesario crear ese atributo antes de la validación. Lo correcto sería:

before_validation_on_create :set_vacation

Además de este detalle se había juntado otro problema que hacía que me costase un poco con la solución. Este problema es que ActiveLDAP no acepta una definición de un booleano de la forma clásica:
vacationActive = false

ActiveLDAP acepta valores booleanos de la siguiente en forma de cadenas de la forma “TRUE” o “FALSE”.
Mirando un poco las tripas de ActiveLDAP he visto que tiene una función para normalizar los valores en el caso de que se le pase true o false. No obstante parece que no aplica esa función a nivel interno, por lo que resulta totalmente inutil en caso de desconocimiento del programador, como en este caso me ha pasado a mi.

Por lo tanto, el callback correcto en esta situación no sería el que he indicado anteriormente, sino que sería este:

before_validation_on_create :set_vacation

def set_vacation
  self.vacationActive = "FALSE"
end

Written by luis

August 5th, 2008 at 11:25 am