Ejemplo n.º 1
0
def main(string_args: Optional[List[str]] = None) -> None:
    """Parse command line arguments and send mails."""

    parser = argparse.ArgumentParser(
        description='Send focus emails.', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        'action', choices=('dry-run', 'list', 'send'), default='dry-run',
        help='Whether to process to a dry run, list all concerned users, or really send emails.')
    parser.add_argument(
        '--dry-run-email', default='*****@*****.**',
        help='Process a dry run and send email to this email address.')
    parser.add_argument(
        '--disable-sentry', action='store_true', help='Disable logging to Sentry.')
    args = parser.parse_args(string_args)

    logging.basicConfig(level='INFO')
    if args.action == 'send' and not args.disable_sentry:
        try:
            report.setup_sentry_logging(os.getenv('SENTRY_DSN'))
        except ValueError:
            logging.error(
                'Please set SENTRY_DSN to enable logging to Sentry, or use --disable-sentry option')
            return

    if args.action == 'send' and auth.SECRET_SALT == auth.FAKE_SECRET_SALT:
        raise ValueError('Set the prod SECRET_SALT env var before continuing.')

    if args.action == 'list':
        logging.info('Potential focus emails: %s', sorted(_FOCUS_CAMPAIGNS.keys()))

    _send_focus_emails(args.action, args.dry_run_email)
Ejemplo n.º 2
0
def main(string_args: Optional[List[str]] = None) -> None:
    """Parse command line arguments and trigger the clean_guest_users function."""

    parser = argparse.ArgumentParser(description='Clean guests user from the database.')
    parser.add_argument(
        '--disable-sentry', action='store_true', help='Disable logging to Sentry.')
    registered_to_group = parser.add_mutually_exclusive_group()
    registered_to_group.add_argument(
        '--registered-to', help='Consider only users who registered before \
        this date.')
    registered_to_group.add_argument(
        '--registered-to-days-ago', default=7, type=int,
        help='Consider only users who registered more than N days ago.')
    parser.add_argument(
        '--no-dry-run', dest='dry_run', action='store_false', help='No dry run really store in DB.')

    args = parser.parse_args(string_args)

    logging.basicConfig(level='INFO')
    if not args.dry_run and not args.disable_sentry:
        try:
            report.setup_sentry_logging(os.getenv('SENTRY_DSN'))
        except ValueError:
            logging.error(
                'Please set SENTRY_DSN to enable logging to Sentry, or use --disable-sentry option')
            return
    if args.registered_to:
        to_date = args.registered_to
    else:
        to_date = (now.get() - datetime.timedelta(days=args.registered_to_days_ago))\
            .strftime('%Y-%m-%dT%H:%M:%S')

    logging.info(
        'Cleaned %d users and got %d errors', *clean_guest_users(_DB, to_date, args.dry_run))
Ejemplo n.º 3
0
def main(string_args: Optional[List[str]] = None, out: TextIO = sys.stdout) \
        -> None:
    """Parse command line arguments and trigger _compute_assessment_report function.
    docker-compose run --rm -e MONGO_URL="$PROD_MONGO" frontend-flask \
        python /work/bob_emploi/frontend/server/asynchronous/assess_assessment.py -s 2017-11-01
    """

    parser = argparse.ArgumentParser(
        description='Statistics on users whith or whithout assessment.')
    since_group = parser.add_mutually_exclusive_group()
    since_group.add_argument(
        '-d', '--since-days-ago', type=int,
        help='Process use cases registered in the last given days.')
    since_group.add_argument(
        '-s', '--since', default='2018',
        help='Process use cases registered since the given date.')
    parser.add_argument(
        '-u', '--until',
        help='Process use cases registered before (but not including) the given date.')
    parser.add_argument(
        '-e', '--examples', default='1', type=int,
        help='Show the given number of examples of use cases whithout assessment.')
    parser.add_argument('--verbose', '-v', action='store_true', help='More detailed output.')
    parser.add_argument(
        '--no-dry-run', dest='dry_run', action='store_false',
        help='No dry run really send reports.')
    args = parser.parse_args(string_args)

    if not args.dry_run:
        report.setup_sentry_logging(os.getenv('SENTRY_DSN'))
    logging.basicConfig(level='DEBUG' if args.verbose else 'INFO')

    present = now.get()
    from_date = args.since
    if args.since_days_ago:
        from_date = present - datetime.timedelta(days=args.since_days_ago)
    to_date = present if args.since_days_ago or not args.until else args.until

    report_text = _compute_assessment_report(
        args.examples, from_date, to_date)
    if args.dry_run:
        out.write(report_text)
        return
    if _SLACK_ASSESSER_URL:
        requests.post(_SLACK_ASSESSER_URL, json={'attachments': [{
            'mrkdwn_in': ['text'],
            'title': f'Assessment coverage from {from_date} to {to_date}',
            'text': report_text,
        }]})
Ejemplo n.º 4
0
def main(string_args: Optional[list[str]] = None) -> None:
    """Parse command line arguments and send mails."""

    parser = argparse.ArgumentParser(
        description='Send focus emails.', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        'action', choices=('dry-run', 'list', 'send'), default='dry-run',
        help='Whether to process to a dry run, list all concerned users, or really send emails.')
    parser.add_argument(
        '--dry-run-email', default='*****@*****.**',
        help='Process a dry run and send email to this email address.')
    parser.add_argument(
        '--restrict-campaigns', help='IDs of campaigns to send.',
        choices=sorted(_FOCUS_CAMPAIGNS.keys()), nargs='*')
    report.add_report_arguments(parser, setup_dry_run=False)
    args = parser.parse_args(string_args)

    if not report.setup_sentry_logging(args, dry_run=args.action != 'send'):
        return

    if args.action == 'send' and auth_token.SECRET_SALT == auth_token.FAKE_SECRET_SALT:
        raise ValueError('Set the prod SECRET_SALT env var before continuing.')

    if args.action == 'list':
        logging.info('Potential focus emails: %s', sorted(_FOCUS_CAMPAIGNS.keys()))

    _send_focus_emails(
        args.action, args.dry_run_email, restricted_campaigns=args.restrict_campaigns)
Ejemplo n.º 5
0
def main(string_args: Optional[list[str]] = None) -> None:
    """Parse command line arguments and trigger the update_users_client_metrics function."""

    parser = argparse.ArgumentParser(
        description='Synchronize MongoDB client metrics fields from Amplitude')
    parser.add_argument(
        '--registered-from',
        help='Consider only users who registered after this date.')
    yesterday = str((now.get() - datetime.timedelta(days=1)).date())
    parser.add_argument(
        '--registered-to',
        default=yesterday,
        help='Consider only users who registered before this date.')
    report.add_report_arguments(parser)

    args = parser.parse_args(string_args)

    if not report.setup_sentry_logging(args):
        return

    user_db = mongo.get_connections_from_env().user_db

    update_users_client_metrics(user_db.user,
                                from_date=args.registered_from,
                                to_date=args.registered_to,
                                dry_run=args.dry_run)
Ejemplo n.º 6
0
def main(string_args: Optional[list[str]] = None) -> None:
    """Clean all support tickets marked for deletion."""

    user_db = mongo.get_connections_from_env().user_db

    parser = argparse.ArgumentParser(
        description='Clean support tickets from the database.')
    report.add_report_arguments(parser)

    args = parser.parse_args(string_args)
    if not report.setup_sentry_logging(args):
        return

    instant = proto.datetime_to_json_string(now.get())
    result = user_db.user.update_many(
        {}, {'$pull': {
            'supportTickets': {
                'deleteAfter': {
                    '$lt': instant
                }
            }
        }})
    logging.info('Removed deprecated support tickets for %d users.',
                 result.modified_count)
    clean_result = user_db.user.update_many({'supportTickets': {
        '$size': 0
    }}, {'$unset': {
        'supportTickets': ''
    }})
    if clean_result.matched_count:
        logging.info('Removed empty support ticket list for %d users.',
                     clean_result.modified_count)
def main(es_client: elasticsearch.Elasticsearch,
         string_args: Optional[list[str]] = None) -> None:
    """Parse command line arguments and trigger sync_employment_status function."""

    parser = argparse.ArgumentParser(
        description=
        'Synchronize mongodb employement status fields retrieving typeform data.'
    )
    parser.add_argument('-r',
                        '--registered-from',
                        default='2017-06-01',
                        help='Process users registered from the given date')
    parser.add_argument(
        '--force-recreate',
        action='store_true',
        help=
        'If set, completely cleanup the index, rather than updating existing documents.'
    )
    parser.add_argument('--index',
                        default='bobusers',
                        help='Elasticsearch index to write to')
    report.add_report_arguments(parser)
    args = parser.parse_args(string_args)

    if not report.setup_sentry_logging(args):
        return

    export_user_to_elasticsearch(es_client,
                                 args.index,
                                 args.registered_from,
                                 force_recreate=args.force_recreate,
                                 dry_run=args.dry_run)
Ejemplo n.º 8
0
def main(string_args: Optional[List[str]] = None,
         out: TextIO = sys.stdout) -> None:
    """Parse command line arguments, computes a report and send it."""

    parser = argparse.ArgumentParser(
        description=
        'Compute a report of user feedbacks and send it through Slack or email'
    )
    parser.add_argument('--no-dry-run',
                        dest='dry_run',
                        action='store_false',
                        help='No dry run really send reports.')
    parser.add_argument('report',
                        choices=_REPORTS.keys(),
                        help='Report type to send.')

    from_group = parser.add_mutually_exclusive_group(required=True)
    from_group.add_argument(
        '--from',
        dest='from_date',
        help='Only consider feedback sent after this date.')
    from_group.add_argument(
        '--from-days-ago',
        help='Only consider feedback sent in the last days.',
        type=int)

    parser.add_argument('--to',
                        dest='to_date',
                        help='Only consider feedback sent before this date.')
    args = parser.parse_args(string_args)

    if not args.dry_run:
        report_helper.setup_sentry_logging(os.getenv('SENTRY_DSN'))

    if args.from_date:
        from_date = args.from_date
    else:
        from_date = (datetime.datetime.now() - datetime.timedelta(days=args.from_days_ago))\
            .strftime('%Y-%m-%d')

    _compute_and_send_report(args.report,
                             from_date,
                             args.to_date,
                             out,
                             dry_run=args.dry_run)
Ejemplo n.º 9
0
def main(es_client: elasticsearch.Elasticsearch,
         string_args: Optional[List[str]] = None) -> None:
    """Parse command line arguments and trigger sync_employment_status function."""

    parser = argparse.ArgumentParser(
        description=
        'Synchronize mongodb employement status fields retrieving typeform data.'
    )
    parser.add_argument('-r',
                        '--registered-from',
                        default='2017-06-01',
                        help='Process users registered from the given date')
    parser.add_argument('--no-dry-run',
                        dest='dry_run',
                        action='store_false',
                        help='No dry run really store in elasticsearch.')
    parser.add_argument('--index',
                        default='bobusers',
                        help='Elasticsearch index to write to')
    parser.add_argument('--verbose',
                        '-v',
                        action='store_true',
                        help='More detailed output.')
    parser.add_argument('--disable-sentry',
                        action='store_true',
                        help='Disable logging to Sentry.')
    args = parser.parse_args(string_args)

    logging.basicConfig(level='DEBUG' if args.verbose else 'INFO')
    if not args.dry_run and not args.disable_sentry:
        try:
            report.setup_sentry_logging(os.getenv('SENTRY_DSN'))
        except ValueError:
            logging.error(
                'Please set SENTRY_DSN to enable logging to Sentry, or use --disable-sentry option'
            )
            return

    export_user_to_elasticsearch(es_client,
                                 args.index,
                                 args.registered_from,
                                 dry_run=args.dry_run)
Ejemplo n.º 10
0
def main(string_args: Optional[List[str]] = None) -> None:
    """Clean all support tickets marked for deletion."""

    parser = argparse.ArgumentParser(
        description='Clean support tickets from the database.')
    parser.add_argument('--disable-sentry',
                        action='store_true',
                        help='Disable logging to Sentry.')

    args = parser.parse_args(string_args)
    logging.basicConfig(level='INFO')
    if not args.disable_sentry:
        try:
            report.setup_sentry_logging(os.getenv('SENTRY_DSN'))
        except ValueError:
            logging.error(
                'Please set SENTRY_DSN to enable logging to Sentry, or use --disable-sentry option'
            )
            return

    instant = proto.datetime_to_json_string(now.get())
    result = _DB.user.update_many(
        {}, {'$pull': {
            'supportTickets': {
                'deleteAfter': {
                    '$lt': instant
                }
            }
        }})
    logging.info('Removed deprecated support tickets for %d users.',
                 result.modified_count)
    clean_result = _DB.user.update_many({'supportTickets': {
        '$size': 0
    }}, {'$unset': {
        'supportTickets': ''
    }})
    if clean_result.matched_count:
        logging.info('Removed empty support ticket list for %d users.',
                     clean_result.modified_count)
Ejemplo n.º 11
0
def main(string_args=None):
    """Parse command line arguments and trigger the update_users_client_metrics function."""

    parser = argparse.ArgumentParser(
        description='Synchronize MongoDB client metrics fields from Amplitude')
    parser.add_argument('--disable-sentry',
                        action='store_true',
                        help='Disable logging to Sentry.')
    parser.add_argument(
        '--registered-from',
        help='Consider only users who registered after this date.')
    yesterday = str((now.get() - datetime.timedelta(days=1)).date())
    parser.add_argument(
        '--registered-to',
        default=yesterday,
        help='Consider only users who registered before this date.')
    parser.add_argument('--no-dry-run',
                        dest='dry_run',
                        action='store_false',
                        help='No dry run really store in DB.')

    args = parser.parse_args(string_args)

    logging.basicConfig(level='INFO')
    if not args.dry_run and not args.disable_sentry:
        try:
            report.setup_sentry_logging(os.getenv('SENTRY_DSN'))
        except ValueError:
            logging.error(
                'Please set SENTRY_DSN to enable logging to Sentry, or use --disable-sentry option'
            )
            return

    update_users_client_metrics(_DB.user,
                                from_date=args.registered_from,
                                to_date=args.registered_to,
                                dry_run=args.dry_run)
Ejemplo n.º 12
0
def main(string_args: Optional[list[str]] = None) -> None:
    """Parse command line arguments and trigger the clean_guest_users function."""

    parser = argparse.ArgumentParser(
        description='Clean guests and inactive users from the database.')

    parser.add_argument(
        '--max-users',
        help='Only consider a maximum of this number of users.',
        type=int)

    report.add_report_arguments(parser)

    args = parser.parse_args(string_args)

    if not report.setup_sentry_logging(args):
        return

    user_db = mongo.get_connections_from_env().user_db

    logging.info(
        'Cleaned %d users, set check date for %d users and got %d errors',
        *clean_users(user_db, args.dry_run, args.max_users))
Ejemplo n.º 13
0
def main(string_args: Optional[list[str]] = None) -> None:
    """Check the status of sent emails on MailJet and update our Database.
    """

    parser = argparse.ArgumentParser(
        description='Update email status on sent emails.',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    report.add_report_arguments(parser)

    parser.add_argument(
        '--campaigns',
        choices=mail_blast.campaign.list_all_campaigns(),
        nargs='*',
        help='Campaign IDs to check. If not specified, run for all campaigns.')

    parser.add_argument('--mongo-collection',
                        default='user',
                        help='Name of the mongo collection to update.')

    args = parser.parse_args(string_args)

    if not report.setup_sentry_logging(args):
        return

    email_mongo_filter = {
        'mailjetMessageId': {
            '$exists': True
        },
    }
    if args.campaigns:
        email_mongo_filter['campaignId'] = {'$in': args.campaigns}
    yesterday = proto.datetime_to_json_string(now.get() -
                                              datetime.timedelta(days=1))
    mongo_filter = {
        '$or': [
            # Emails that we've never checked.
            {
                'emailsSent': {
                    '$elemMatch':
                    dict({
                        'lastStatusCheckedAt': {
                            '$exists': False
                        },
                    }, **email_mongo_filter),
                },
            },
            # Emails checked less than two weeks after they have been sent and
            # that we haven't checked today.
            {
                'emailsSent': {
                    '$elemMatch':
                    dict(
                        {
                            'lastStatusCheckedAt': {
                                '$lt': yesterday
                            },
                            'lastStatusCheckedAfterDays': {
                                '$not': {
                                    '$gte': 14
                                }
                            },
                        }, **email_mongo_filter),
                },
            },
            # Emails sent less than 24 hours ago.
            {
                'emailsSent': {
                    '$elemMatch':
                    dict({
                        'sentAt': {
                            '$gt': yesterday
                        },
                    }, **email_mongo_filter),
                },
            },
        ],
    }

    user_db = mongo.get_connections_from_env().user_db
    mongo_collection = user_db.get_collection(args.mongo_collection)
    selected_users = mongo_collection.find(mongo_filter, {'emailsSent': 1})
    treated_users = 0
    # TODO(cyrille): Make sure errors are logged to sentry.
    # TODO(cyrille): If it fails on a specific user, keep going.
    for user in selected_users:
        emails_sent = user.get('emailsSent', [])
        updated_emails_sent = [
            _update_email_sent_status(email,
                                      yesterday,
                                      campaign_ids=args.campaigns)
            for email in emails_sent
        ]
        mongo_collection.update_one(
            {'_id': user['_id']},
            {'$set': {
                'emailsSent': updated_emails_sent
            }})
        treated_users += 1
        if not treated_users % 100:
            logging.info('Treated %d users', treated_users)
Ejemplo n.º 14
0
def main(string_args: Optional[list[str]] = None) -> None:
    """Parse command line arguments and send mails."""

    parser = argparse.ArgumentParser(
        description='Send focus emails.',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        epilog='''ROME info by prefix file can be downloaded at \
        https://airtable.com/tbl5jBDdG3vnYPWNu/viwz9GaBDHEpjTCU9
        ''')
    parser.add_argument('campaign',
                        choices=campaign.list_all_campaigns(),
                        help='Campaign type to send.')
    parser.add_argument(
        'action',
        choices=('dry-run', 'list', 'send'),
        default='dry-run',
        help=
        'Whether to process to a dry run, list all concerned users, or really send emails.'
    )
    parser.add_argument(
        '--dry-run-email',
        default='*****@*****.**',
        help='Process a dry run and send email to this email address.')
    parser.add_argument(
        '--user-hash',
        help='Only send to users whose ID hash starts with this given hash. \
        Hashing the ID ensures a uniform distribution, so this gives a consistent sample of users.'
    )
    # TODO(cyrille): Replace with option to select only one user_id.
    parser.add_argument(
        '--user-id-start',
        help='Only send to users whose ID starts with this given hash. WARNING: \
        hash distribution is not uniform for old users, do not use this to get a sample.'
    )
    parser.add_argument(
        '--user-collection-prefix',
        default='',
        help='Send to users in the collection whose name is "{prefix}user"')

    registered_from_group = parser.add_mutually_exclusive_group()
    registered_from_group.add_argument(
        '--registered-from',
        default='2017-04-01',
        help='Consider only users who registered after \
        this date.')
    registered_from_group.add_argument(
        '--registered-from-days-ago',
        type=int,
        help='Consider only users who registered less than N days ago.')

    registered_to_group = parser.add_mutually_exclusive_group()
    registered_to_group.add_argument(
        '--registered-to',
        default='2017-11-10',
        help='Consider only users who registered before \
        this date.')
    registered_to_group.add_argument(
        '--registered-to-days-ago',
        type=int,
        help='Consider only users who registered more than N days ago.')

    parser.add_argument(
        '--days-since-any-email',
        default='2',
        type=int,
        help=
        "Consider only users who haven't received any email in the last given number of days."
    )
    parser.add_argument(
        '--days-since-same-campaign',
        default='0',
        type=int,
        help=
        "Consider only users who haven't had the same email in the last given number of \
        days. If default or 0, will not resend the same campaign to anyone.")
    parser.add_argument(
        '--days-since-same-campaign-unread',
        default='0',
        type=int,
        help=
        "Must be smaller than --days-since-same-campaign. Users who received an email between \
        --days-since-same-campaign and --days-since-same-campaign-unread will only be sent a new \
        one if they haven't read it.")
    parser.add_argument(
        '--log-reason-on-error',
        action='store_true',
        help='Show the reason why users are rejected from the blast')
    report.add_report_arguments(parser, setup_dry_run=False)
    args = parser.parse_args(string_args)

    if not report.setup_sentry_logging(args, dry_run=args.action != 'send'):
        return

    if args.days_since_same_campaign < args.days_since_same_campaign_unread:
        logging.error('Please use coherent values in the policy durations.')
        return

    policy = EmailPolicy(args.days_since_any_email,
                         args.days_since_same_campaign_unread,
                         args.days_since_same_campaign)

    registered_from = _date_from_today(args.registered_from,
                                       args.registered_from_days_ago)
    registered_to = _date_from_today(args.registered_to,
                                     args.registered_to_days_ago)

    logging.info(
        '%d emails sent.',
        blast_campaign(args.campaign,
                       args.action,
                       registered_from,
                       registered_to,
                       dry_run_email=args.dry_run_email,
                       user_id_start=args.user_id_start,
                       user_hash=args.user_hash,
                       email_policy=policy,
                       collection_prefix=args.user_collection_prefix,
                       log_reason_on_error=args.log_reason_on_error))
Ejemplo n.º 15
0
def main(string_args: Optional[List[str]] = None) -> None:
    """Check the status of sent emails on MailJet and update our Database.
    """

    parser = argparse.ArgumentParser(
        description='Update email status on sent emails.',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    parser.add_argument(
        '--campaigns',
        choices=mail_blast.campaign.list_all_campaigns(),
        nargs='*',
        help='Campaign IDs to check. If not specified, run for all campaigns.')

    parser.add_argument('--mongo-collection',
                        default='user',
                        help='Name of the mongo collection to update.')

    parser.add_argument('--disable-sentry',
                        action='store_true',
                        help='Disable logging to Sentry.')

    args = parser.parse_args(string_args)

    if not args.disable_sentry:
        try:
            report.setup_sentry_logging(os.getenv('SENTRY_DSN'))
        except ValueError:
            logging.error(
                'Please set SENTRY_DSN to enable logging to Sentry, or use --disable-sentry option'
            )
            return

    email_mongo_filter = {
        'mailjetMessageId': {
            '$exists': True
        },
    }
    if args.campaigns:
        email_mongo_filter['campaignId'] = {'$in': args.campaigns}
    yesterday = proto.datetime_to_json_string(now.get() -
                                              datetime.timedelta(days=1))
    mongo_filter = {
        '$or': [
            # Emails that we've never checked.
            {
                'emailsSent': {
                    '$elemMatch':
                    dict({
                        'lastStatusCheckedAt': {
                            '$exists': False
                        },
                    }, **email_mongo_filter),
                },
            },
            # Emails checked less than two weeks after they have been sent and
            # that we haven't checked today.
            {
                'emailsSent': {
                    '$elemMatch':
                    dict(
                        {
                            'lastStatusCheckedAt': {
                                '$lt': yesterday
                            },
                            'lastStatusCheckedAfterDays': {
                                '$not': {
                                    '$gte': 14
                                }
                            },
                        }, **email_mongo_filter),
                },
            },
            # Emails sent less than 24 hours ago.
            {
                'emailsSent': {
                    '$elemMatch':
                    dict({
                        'sentAt': {
                            '$gt': yesterday
                        },
                    }, **email_mongo_filter),
                },
            },
        ],
    }

    mongo_collection = _DB.get_collection(args.mongo_collection)
    selected_users = mongo_collection.find(mongo_filter, {'emailsSent': 1})
    treated_users = 0
    for user in selected_users:
        emails_sent = user.get('emailsSent', [])
        updated_emails_sent = [
            _update_email_sent_status(email,
                                      yesterday,
                                      campaign_ids=args.campaigns)
            for email in emails_sent
        ]
        mongo_collection.update_one(
            {'_id': user['_id']},
            {'$set': {
                'emailsSent': updated_emails_sent
            }})
        treated_users += 1
        if not treated_users % 100:
            logging.info('Treated %d users', treated_users)
Ejemplo n.º 16
0
def main(string_args=None):
    """Parse command line arguments and send mails."""

    parser = argparse.ArgumentParser(
        description='Send focus emails.',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        epilog='''ROME info by prefix file can be downloaded at \
        https://airtable.com/tbl5jBDdG3vnYPWNu/viwz9GaBDHEpjTCU9
        ''')
    parser.add_argument('campaign',
                        choices=campaign.list_all_campaigns(),
                        help='Campaign type to send.')
    parser.add_argument(
        'action',
        choices=('dry-run', 'list', 'send'),
        default='dry-run',
        help=
        'Whether to process to a dry run, list all concerned users, or really send emails.'
    )
    parser.add_argument(
        '--dry-run-email',
        default='*****@*****.**',
        help='Process a dry run and send email to this email adress.')
    parser.add_argument(
        '--user-hash',
        help='Only send to users whose ID starts with this given hash. WARNING: \
        hash distribution is not uniform for old users, do not use this to get a sample.'
    )

    registered_from_group = parser.add_mutually_exclusive_group()
    registered_from_group.add_argument(
        '--registered-from',
        default='2017-04-01',
        help='Consider only users who registered after \
        this date.')
    registered_from_group.add_argument(
        '--registered-from-days-ago',
        type=int,
        help='Consider only users who registered less than N days ago.')

    registered_to_group = parser.add_mutually_exclusive_group()
    registered_to_group.add_argument(
        '--registered-to',
        default='2017-11-10',
        help='Consider only users who registered before \
        this date.')
    registered_to_group.add_argument(
        '--registered-to-days-ago',
        type=int,
        help='Consider only users who registered more than N days ago.')

    parser.add_argument('--disable-sentry',
                        action='store_true',
                        help='Disable logging to Sentry.')
    parser.add_argument(
        '--days-since-any-email',
        default='2',
        type=int,
        help=
        "Consider only users who haven't received any email in the last given number of days."
    )
    parser.add_argument(
        '--days-since-same-campaign',
        default='0',
        type=int,
        help=
        "Consider only users who haven't had the same email in the last given number of \
        days. If default or 0, will not resend the same campaign to anyone.")
    parser.add_argument(
        '--days-since-same-campaign-unread',
        default='0',
        type=int,
        help=
        "Must be smaller than --days-since-same-campaign. Users who received an email between \
        --days-since-same-campaign and --days-since-same-campaign-unread will only be sent a new \
        one if they haven't read it.")
    args = parser.parse_args(string_args)

    logging.basicConfig(level='INFO')
    if args.action == 'send' and not args.disable_sentry:
        try:
            report.setup_sentry_logging(os.getenv('SENTRY_DSN'))
        except ValueError:
            logging.error(
                'Please set SENTRY_DSN to enable logging to Sentry, or use --disable-sentry option'
            )
            return

    if args.days_since_same_campaign < args.days_since_same_campaign_unread:
        logging.error('Please use coherent values in the policy durations.')
        return

    policy = EmailPolicy(args.days_since_any_email,
                         args.days_since_same_campaign_unread,
                         args.days_since_same_campaign)

    registered_from = _date_from_today(args.registered_from,
                                       args.registered_from_days_ago)
    registered_to = _date_from_today(args.registered_to,
                                     args.registered_to_days_ago)

    logging.info(
        '%d emails sent.',
        blast_campaign(args.campaign,
                       args.action,
                       registered_from,
                       registered_to,
                       dry_run_email=args.dry_run_email,
                       user_hash=args.user_hash,
                       email_policy=policy))