2012-09-14 20 views
11

Estamos utilizando AWS SES para enviar correos. Amazon SES envía notificaciones de rebote y reclamo a través del emails or AWS SNS. Nos gustaría procesar automáticamente las notificaciones de rebote y reclamo (procedentes de correo electrónico o SNS de AWS) para extraer los ID de correo electrónico, de modo que estos correos electrónicos puedan eliminarse de la lista original.Procesamiento automático de las notificaciones de Amazon SES para notificaciones de rebote y quejas

Una forma de automatizar es enviar estas notificaciones a un tema en AWS SNS, luego suscribirse al tema utilizando AWS SQS y finalmente leer los mensajes en AWS SQS. SNS admite la suscripción a través de los siguientes protocolos: HTTP/HTTPS/EMail/EMail (JSON)/SMS/SQS. Esto es factible, pero me parece demasiado engorroso para una simple tarea de procesamiento automático de las notificaciones de rebote y reclamo.

¿Hay alguna forma elegante de solucionar este problema?


Me han encontrado una blog entry de Amazon con el código en C#. ¿Hay una mejor solución?

Respuesta

2

Creo que la forma en que describes ES probablemente la forma más elegante. Ya tiene servicios muy adecuados en SNS y SQS que tienen SDK asociados en la mayoría de los idiomas principales para que pueda hacer lo que necesita con facilidad. La parte más difícil es escribir el código para actualizar/eliminar los registros en sus listas de correo.

10

Me parece que suscribirse directamente a SNS utilizando un punto final HTTP es el enfoque más directo. Literalmente tienes que escribir solo unas pocas líneas de código. Aquí está mi ejemplo Django:

def process(request): 
    json = request.raw_post_data    
    js = simplejson.loads(json) 
    info = simplejson.loads(js["Message"]) 
    type = info["notificationType"]   # "Complaint" or "Bounce" 
    email = info["mail"]["destination"][0] 


    # do whatever you want with the email 
+0

Sin embargo, esto es menos confiable que usar SQS, como si falla, nunca volverá a intentarlo, por lo tanto, SQS es preferible. – niico

1

Recientemente, tuve la oportunidad de conseguir este trabajo utilizando un extremo de HTTP a través del SNS. Uso python/django para consumir la notificación. Debe procesar el mensaje de suscripción antes de consumir las notificaciones; puede leer sobre suscripciones en la documentación del SNS.

Creo que si tiene una aplicación más pequeña que no envía muchos correos electrónicos; http endpoint debería funcionar bien. Este código requiere que tenga un modelo de notificación creado.

#process an amazon sns http endpoint notification for amazon ses bounces and complaints 
@csrf_exempt 
def process_ses_notification(request): 

    if request.POST: 

     json_body = request.body 
     #remove this control character(throws an error) thats present inside the test subscription confirmation 
     js = loads(json_body.replace('\n', '')) 

     if js["Type"] == "SubscriptionConfirmation": 

      subscribe_url = js["SubscribeURL"] 
      urllib.urlopen(subscribe_url) 
      return HttpResponse(status=200) 

    elif js["Type"] == "Notification": 

     #process message from amazon sns 
     arg_info = loads(js["Message"]) # may need to use loads(js["Message"]) after testing with amazon 
     arg_notification_type = arg_info["notificationType"] 

     if arg_notification_type == 'Bounce': 
      #required bounce object fields 
      arg_emails=arg_info["bounce"]["bouncedRecipients"] 
      arg_notification_subtype=arg_info["bounce"]["bounceType"] 
      arg_feedback_id=arg_info["bounce"]["feedbackId"] 
      arg_date_recorded=arg_info["bounce"]["timestamp"] 
     elif arg_notification_type == 'Complaint': 
      #required complaint object fields 
      arg_emails=arg_info["complaint"]["complainedRecipients"] 
      arg_feedback_id=arg_info["complaint"]["feedbackId"] 
      arg_date_recorded=arg_info["complaint"]["timestamp"] 
      #check if feedback type is inside optional field name 
      if "complaintFeedbackType" in arg_info["complaint"]: 
       arg_notification_subtype=arg_info["complaint"]["complaintFeedbackType"] 
      else: 
       arg_notification_subtype="" 
     else: 
      HttpResponse(status=400) 

     #save notifications for multiple emails 
     for arg_email in arg_emails: 
      notification = SES_Notification(info=json_body, notification_type=arg_notification_type, 
              email=arg_email["emailAddress"], notification_subtype=arg_notification_subtype, 
              date_recorded=arg_date_recorded, feedback_id=arg_feedback_id) 
      notification.save() 
     return HttpResponse(status=200) 

return HttpResponse(status=400) 
2

A través de ensayo y error que he llegado con éste - que es para Django, pero hace un trabajo decente para mí.

En primer lugar los modelos, entonces el controlador de solicitudes ...

class ComplaintType: 
    ABUSE = 'abuse' 
    AUTH_FAILURE = 'auth-failure' 
    FRAUD = 'fraud' 
    NOT_SPAM = 'not-spam' 
    OTHER = 'other' 
    VIRUS = 'virus' 


COMPLAINT_FEEDBACK_TYPE_CHOICES = [ 
    [ComplaintType.ABUSE, _('Unsolicited email or some other kind of email abuse')], 
    [ComplaintType.AUTH_FAILURE, _('Unsolicited email or some other kind of email abuse')], 
    [ComplaintType.FRAUD, _('Some kind of fraud or phishing activity')], 
    [ComplaintType.NOT_SPAM, _('Entity providing the report does not consider the message to be spam')], 
    [ComplaintType.OTHER, _('Feedback does not fit into any other registered type')], 
    [ComplaintType.VIRUS, _('A virus was found in the originating message')] 
] 


class SES_Complaint(models.Model): 
    subject = models.CharField(max_length=255) 
    message = models.TextField() 
    email_address = models.EmailField(db_index=True) 
    user_agent = models.CharField(max_length=255) 
    complaint_feedback_type = models.CharField(max_length=255, choices=COMPLAINT_FEEDBACK_TYPE_CHOICES) 
    arrival_date = models.DateTimeField() 
    timestamp = models.DateTimeField() 
    feedback_id = models.CharField(max_length=255) 

    created = models.DateTimeField(auto_now_add=True) 
    modified = models.DateTimeField(auto_now=True) 

    class Meta: 
     verbose_name = 'SES Complaint' 
     verbose_name_plural = 'SES Complaints' 

    def get_reason(self): 
     return self.get_complaint_feedback_type_display() 


class BounceType: 
    UNDETERMINED = 'Undetermined' 
    PERMANENT = 'Permanent' 
    TRANSIENT = 'Transient' 


class BounceSubType: 
    UNDETERMINED = 'Undetermined' 
    GENERAL = 'General' 
    NO_EMAIL = 'NoEmail' 
    SUPPRESSED = 'Suppressed' 
    MAILBOX_FULL = 'MailboxFull' 
    MESSAGE_TOO_LARGE = 'MessageToolarge' 
    CONTENT_REJECTED = 'ContentRejected' 
    ATTACHMENT_REJECTED = 'AttachmentRejected' 


BOUNCE_TYPE_CHOICES = [ 
    [BounceType.UNDETERMINED, _('Unable to determine a specific bounce reason')], 
    [BounceType.PERMANENT, _('Unable to successfully send')], 
    [BounceType.TRANSIENT, _('All retry attempts have been exhausted')], 
] 

BOUNCE_SUB_TYPE_CHOICES = [ 
    [BounceSubType.UNDETERMINED, _('Unable to determine a specific bounce reason')], 
    [BounceSubType.GENERAL, _('General bounce. You may be able to successfully retry sending to that recipient in the future.')], 
    [BounceSubType.NO_EMAIL, _('Permanent hard bounce. The target email address does not exist.')], 
    [BounceSubType.SUPPRESSED, _('Address has a recent history of bouncing as invalid.')], 
    [BounceSubType.MAILBOX_FULL, _('Mailbox full')], 
    [BounceSubType.MESSAGE_TOO_LARGE, _('Message too large')], 
    [BounceSubType.CONTENT_REJECTED, _('Content rejected')], 
    [BounceSubType.ATTACHMENT_REJECTED, _('Attachment rejected')] 
] 


class SES_Bounce(models.Model): 
    subject = models.CharField(max_length=255) 
    message = models.TextField() 
    bounce_type = models.CharField(max_length=255, choices=BOUNCE_TYPE_CHOICES) 
    bounce_sub_type = models.CharField(max_length=255, choices=BOUNCE_SUB_TYPE_CHOICES) 
    timestamp = models.DateTimeField() 
    feedback_id = models.CharField(max_length=255) 
    status = models.CharField(max_length=255) 
    action = models.CharField(max_length=255) 
    diagnostic_code = models.CharField(max_length=255) 
    email_address = models.EmailField(db_index=True) 

    created = models.DateTimeField(auto_now_add=True) 
    modified = models.DateTimeField(auto_now=True, db_index=True) 

    class Meta: 
     verbose_name = 'SES Bounce' 
     verbose_name_plural = 'SES Bounces' 

    def get_reason(self): 
     return '%s - %s' % (self.get_bounce_type_display(), self.get_bounce_sub_type_display()) 

Y aquí es el controlador de solicitudes:

@csrf_exempt 
def aws_sns(request): 
    logger.debug('Incoming SNS') 

    if request.method == 'POST': 

     logger.debug('Incoming SNS is POST') 

     sns_message_type = request.META.get('HTTP_X_AMZ_SNS_MESSAGE_TYPE', None) 

     if sns_message_type is not None: 

      logger.debug('Incoming SNS - %s', sns_message_type) 

      json_body = request.body 
      json_body = json_body.replace('\n', '') 
      js = loads(json_body) 

      if sns_message_type == "SubscriptionConfirmation": 

       subscribe_url = js["SubscribeURL"] 
       logger.debug('Incoming subscription - %s', subscribe_url) 
       urllib.urlopen(subscribe_url) 

      elif sns_message_type == "Notification": 

       message = js.get("Message", None) 
       message = message.replace('\n', '') 
       message = loads(message) 

       notification_type = message.get("notificationType", None) 

       if notification_type == 'AmazonSnsSubscriptionSucceeded': 
        logger.debug('Subscription succeeded') 

       elif notification_type == 'Bounce': 

        logger.debug('Incoming bounce') 

        bounce = message['bounce'] 
        bounce_type = bounce['bounceType'] 
        bounce_sub_type = bounce['bounceSubType'] 
        timestamp = bounce['timestamp'] 
        feedback_id = bounce['feedbackId'] 

        bounce_recipients = bounce['bouncedRecipients'] 

        for recipient in bounce_recipients: 
         status = recipient.get('status') 
         action = recipient.get('action') 
         #diagnostic_code = recipient['diagnosticCode'] 
         email_address = recipient['emailAddress'] 

         SES_Bounce.objects.filter(email_address=email_address).delete() 

         SES_Bounce.objects.create(
          message=message, 
          bounce_type=bounce_type, 
          bounce_sub_type=bounce_sub_type, 
          timestamp=timestamp, 
          feedback_id=feedback_id, 
          status=status, 
          action=action, 
          #diagnostic_code=diagnostic_code, 
          email_address=email_address 
         ) 

       elif notification_type == 'Complaint': 

        logger.debug('Incoming complaint') 

        complaint = message['complaint'] 

        user_agent = complaint.get('userAgent') 
        complaint_feedback_type = complaint.get('complaintFeedbackType') 
        arrival_date = complaint.get('arrivalDate') 

        timestamp = complaint['timestamp'] 
        feedback_id = complaint['feedbackId'] 
        recipients = complaint['complainedRecipients'] 

        for recipient in recipients: 
         email_address = recipient['emailAddress'] 

         SES_Complaint.objects.filter(email_address=email_address).delete() 

         SES_Complaint.objects.create(
          #subject=subject, 
          message=message, 
          email_address=email_address, 
          user_agent=user_agent, 
          complaint_feedback_type=complaint_feedback_type, 
          arrival_date=arrival_date, 
          timestamp=timestamp, 
          feedback_id=feedback_id 
         ) 

       else: 
        logger.exception('Incoming Notification SNS is not supported: %s', notification_type) 

      return HttpResponse() 
     else: 
      logger.exception('Incoming SNS did not have the right header') 

      for key, value in request.META.items(): 
       logger.debug('Key: %s - %s', key, value) 

    else: 
     logger.exception('Incoming SNS was not a POST') 

    return HttpResponseBadRequest() 
0

todas las respuestas anteriores son grandes, pero sólo una pequeña e importante además :

primero debe verificar que la solicitud es de amazon sns: (como se describe en Verifying the Signatures of Amazon SNS Messages)

para el código python que valida la firma; un buen ejemplo es here

Cuestiones relacionadas