Удобный способ делать рассылки по базе сайта (с примером для Django)


    Я расскажу вам про опыт использования сервиса Your MailingList Provider (YMLP) для организации рассылки одного из наших проектов.

    Собственно проблема простая и довольно распространенная — есть база пользователей сайта, задача — правильно и удобно организовать по ней рассылку. У пользователей должна быть возможность отписаться от рассылки, у нас — собственно рассылать, а также (очень желательно) делать выборку по полям профиля (например, для рассылки только по женщинам старше 30) и отслеживать статистику (например, сколько людей открыло письмо).

    Отмечу, что, ввиду того, что наш проект международный, в поле нашего зрения было два сервиса, которые позволяют делать подобные вещи — это уже упомянутый YMLP и NetAtlantic. Второй сервис — заметно менее гибкий и удобный, поэтому мы остановились на его основном конкуренте — YMLP (на самом деле не так давно у NetAtlantic был большой плюс — наличие API, но у YMLP он тоже появился, поэтому все стало ясно как день).

    Итак, YMLP позволяет делать все перечисленное мною выше плюс многое другое. Бесплатный аккаунт позволяет делать рассылку по базе до 1000 человек. Можно создавать группы. Можно хранить шаблоны рассылок. Можно добавлять произвольные поля и делать по ним выборку. Можно создавать пользовательские формы. Есть официальная реализация для работы с API на PHP, но механизм его работы настолько простой, что не составляет никакой трудности использовать любой другой язык программирования, например, Python.

    Очень важная особенность — можно делать любое количество подаккаунтов и настраивать их права.

    Сервисом пользуемся с ноября прошлого года, никаких нареканий нет. Очень рекомендую.

    Итак, обещанный пример для Django — рабочий скрипт интеграции базы пользователей этого фреймворка с YMLP. Вызывается по cron, синхронизируя базу сайта с базой YMLP:

    #!/usr/bin/env python2.6<br/>
    import ossysurllib<br/>
    from random import randint<br/>
    from datetime import datetime<br/>
     <br/>
    sys.path.append('/home/www-data/university')<br/>
    os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'<br/>
     <br/>
    API_URL = "www.ymlp.com/api/"<br/>
     <br/>
    try:<br/>
        from django.utils import simplejson as json<br/>
    except ImportError:<br/>
        import simplejson as json<br/>
     <br/>
    from accounts.models import UserProfile<br/>
    from settings import ROOT, YMLP_API_KEY, YMLP_USERNAME, YMLP_GROUP_NAME<br/>
     <br/>
    _groups = None<br/>
    _fields = None<br/>
     <br/>
    class YMLPException(Exception):<br/>
        pass<br/>
     <br/>
    def _search_id(dicts, search_key, search_value):<br/>
        for d in dicts:<br/>
            if d.get(search_key, "").lower() == search_value.lower():<br/>
                return d.get("ID")<br/>
     <br/>
    def get_fields_list():<br/>
        global _fields<br/>
     <br/>
        if _fields is None:<br/>
            params = {<br/>
                    "Key": YMLP_API_KEY,<br/>
                    "Username": YMLP_USERNAME,<br/>
                    "Output""JSON",<br/>
                }<br/>
            f = urllib.urlopen("%s%s?%s" % (API_URL, "Fields.GetList"urllib.urlencode(params)))<br/>
            result = json.loads(f.read())<br/>
            if type(result) is dict and result.has_key("Code"):<br/>
                raise YMLPException(result["Code"], result.get("Output"))<br/>
            _fields = result<br/>
        return _fields<br/>
     <br/>
    def get_groups_list():<br/>
        global _groups<br/>
     <br/>
        if _groups is None:<br/>
            params = {<br/>
                    "Key": YMLP_API_KEY,<br/>
                    "Username": YMLP_USERNAME,<br/>
                    "Output""JSON",<br/>
                }<br/>
            f = urllib.urlopen("%s%s?%s" % (API_URL, "Groups.GetList"urllib.urlencode(params)))<br/>
            result = json.loads(f.read())<br/>
            if type(result) is dict and result.has_key("Code"):<br/>
                raise YMLPException(result["Code"], result.get("Output"))<br/>
            _groups = result<br/>
        return _groups<br/>
     <br/>
    def add_contact(email, group_name, fields, overrule_unsubscribed=False):<br/>
        groups_list = get_groups_list()<br/>
     <br/>
        group_id = _search_id(groups_list, "GroupName", group_name)<br/>
        if group_id is None:<br/>
            raise YMLPException(None"Invalid group name")<br/>
     <br/>
        fields_list = get_fields_list()<br/>
        params = {<br/>
                "Email"email,<br/>
                "GroupID": group_id,<br/>
                "Key": YMLP_API_KEY,<br/>
                "Username": YMLP_USERNAME,<br/>
                "Output""JSON",<br/>
            }<br/>
     <br/>
        for name,value in fields.items():<br/>
            field_id = _search_id(fields_list, "FieldName", name) or _search_id(fields_list, "Alias", name)<br/>
            if field_id is not None:<br/>
                if type(value) is unicode:<br/>
                    value = value.encode("utf-8")<br/>
                params["Field"+field_id] = value<br/>
     <br/>
        params["OverruleUnsubscribedBounced"] = "1" if overrule_unsubscribed else "0"<br/>
        f = urllib.urlopen("%s%s?%s" % (API_URL, "Contacts.Add"urllib.urlencode(params)))<br/>
        result = json.loads(f.read())<br/>
        if result.get("Code") == "0":<br/>
            return True<br/>
        raise YMLPException(result.get("Code"), result.get("Output"))<br/>
     <br/>
    def unsubscribe_contact(email):<br/>
        params = {<br/>
                "Email"email,<br/>
                "Key": YMLP_API_KEY,<br/>
                "Username": YMLP_USERNAME,<br/>
                "Output""JSON",<br/>
            }<br/>
        f = urllib.urlopen("%s%s?%s" % (API_URL, "Contacts.Unsubscribe"urllib.urlencode(params)))<br/>
        result = json.loads(f.read())<br/>
     <br/>
        if type(result) is dict:<br/>
            if result.get("Code") == "0":<br/>
                return True<br/>
            else:<br/>
                raise YMLPException(result.get("Code"), result.get("Output"))<br/>
        raise YMLPException(None"")<br/>
     <br/>
    def delete_contact(email, group_name):<br/>
        groups_list = get_groups_list()<br/>
     <br/>
        group_id = _search_id(groups_list, "GroupName", group_name)<br/>
        if group_id is None:<br/>
            raise YMLPException(None"Invalid group name")<br/>
     <br/>
        params = {<br/>
                "Email"email,<br/>
                "GroupID": group_id,<br/>
                "Key": YMLP_API_KEY,<br/>
                "Username": YMLP_USERNAME,<br/>
                "Output""JSON",<br/>
            }<br/>
        f = urllib.urlopen("%s%s?%s" % (API_URL, "Contacts.Delete"urllib.urlencode(params)))<br/>
        result = json.loads(f.read())<br/>
     <br/>
        if type(result) is dict:<br/>
            if result.get("Code") == "0":<br/>
                return True<br/>
            else:<br/>
                raise YMLPException(result.get("Code"), result.get("Output"))<br/>
        raise YMLPException(None"")<br/>
     <br/>
    if __name__ == "__main__":<br/>
        print datetime.now().isoformat(" ")<br/>
        print "Exporting..."<br/>
        count = 0<br/>
     <br/>
        def _subscribe_profile(profile, fields):<br/>
            global count, YMLP_GROUP_NAME<br/>
            add_contact(profile.user.email, YMLP_GROUP_NAME, fields)<br/>
            profile.subscribed = True<br/>
            profile.save()<br/>
            print ["o""O"][randint(01)],<br/>
            sys.stdout.flush()<br/>
            count += 1<br/>
     <br/>
        for profile in UserProfile.objects.filter(subscription_accepted=True, subscribed=False):<br/>
            if profile.filled:<br/>
                fields = {<br/>
                        "name"profile.user.first_name,<br/>
                        "lastname"profile.user.last_name,<br/>
                        "sex"profile.gender,<br/>
                        "city"profile.city,<br/>
                        "country"profile.country,<br/>
                        "reference"profile.reference,<br/>
                        "year"0 if profile.birth_date == None else profile.birth_date.year<br/>
                    }<br/>
            else:<br/>
                fields = {}<br/>
     <br/>
            done = False<br/>
            first_try = True<br/>
     <br/>
            while not done:<br/>
                try:<br/>
                    _subscribe_profile(profile, fields)<br/>
                    done = True<br/>
                except YMLPException as inst:<br/>
                    print >> sys.stderr"Error #%s while adding contact \"%s\". The error was: %s" % (inst[0]profile.user.email, inst[1])<br/>
                    if not first_try:<br/>
                        done = True<br/>
                        continue<br/>
                    if inst[0] == "1" and inst[1] == None# contact already exists, let's update<br/>
                        print "Contact \"%s\" already exists, trying to update" % profile.user.email<br/>
                        try:<br/>
                            delete_contact(profile.user.email, YMLP_GROUP_NAME)<br/>
                        except YMLPException:<br/>
                            pass<br/>
                        first_try = False<br/>
                        continue<br/>
                    elif inst[0] == "3"# "Email address already in selected groups"<br/>
                        print "Setting the 'Subscribed' flag..."<br/>
                        profile.subscribed = True<br/>
                        profile.save()<br/>
                    elif inst[0] == "4"# "This email address has previously unsubscribed"<br/>
                        print "Removing the 'Subscription Accepted' flag..."<br/>
                        profile.subscription_accepted = False<br/>
                        profile.save()<br/>
                    elif inst[0] in ["2""100""101""102"]:<br/>
                        sys.exit(-1)<br/>
                    done = True<br/>
     <br/>
        print "\nDone. Contacts added: %d, total users number: %d. Bye." % (count, UserProfile.objects.all().count())<br/>
        print "".zfill(80).replace("0""-")<br/>
        print<br/>
     



    Удачных вам рассылок! :-)
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 5

      +1
      Сейчас занимаюсь разработкой аналогичного экспортера на Java для LifeRay — выложу код позже.
        0
        есть возможность отправлять с адреса со своим доменом у YMLP?
          0
          Конечно.
          0
          Спасибо, YMLP — это лучший сервис рассылок, который я видел.
            0
            И вам всего доброго :-)

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое