def send_canary_email(user_email: str) -> None: message = EmailMessage( campaign_key=f"canary_email_{uuid.uuid4()}", subject="This is a test email of your PostHog instance", template_name="canary_email", template_context={"site_url": settings.SITE_URL}, ) message.add_recipient(email=user_email) message.send()
def send_email_subscription_report( email: str, subscription: Subscription, assets: List[ExportedAsset], invite_message: Optional[str] = None, total_asset_count: Optional[int] = None, ) -> None: utm_tags = f"{UTM_TAGS_BASE}&utm_medium=email" inviter = subscription.created_by is_invite = invite_message is not None self_invite = inviter.email == email subject = "Posthog Report" invite_summary = None resource_info = subscription.resource_info if not resource_info: raise NotImplementedError( "This type of subscription resource is not supported") subject = f"PostHog {resource_info.kind} report - {resource_info.name}" campaign_key = f"{resource_info.kind.lower()}_subscription_report_{subscription.next_delivery_date.isoformat()}" unsubscribe_url = absolute_uri( f"/unsubscribe?token={get_unsubscribe_token(subscription, email)}&{utm_tags}" ) if is_invite: invite_summary = f"This subscription is { subscription.summary }. The next subscription will be sent on { subscription.next_delivery_date.strftime('%A %B %d, %Y')}" if self_invite: subject = f"You have been subscribed to a PostHog {resource_info.kind}" else: subject = f"{inviter.first_name or 'Someone'} subscribed you to a PostHog {resource_info.kind}" campaign_key = f"{resource_info.kind.lower()}_subscription_new_{uuid.uuid4()}" message = EmailMessage( campaign_key=campaign_key, subject=subject, template_name="subscription_report", template_context={ "images": [x.get_public_content_url() for x in assets], "resource_noun": resource_info.kind, "resource_name": resource_info.name, "resource_url": f"{resource_info.url}?{utm_tags}", "subscription_url": f"{subscription.url}?{utm_tags}", "unsubscribe_url": unsubscribe_url, "inviter": inviter if is_invite else None, "self_invite": self_invite, "invite_message": invite_message, "invite_summary": invite_summary, "total_asset_count": total_asset_count, }, ) message.add_recipient(email=email) message.send()
def send_invite(invite_id: str) -> None: campaign_key: str = f"invite_email_{invite_id}" invite: OrganizationInvite = OrganizationInvite.objects.select_related( "created_by").select_related("organization").get(id=invite_id) message = EmailMessage( campaign_key=campaign_key, subject= f"{invite.created_by.first_name} invited you to join {invite.organization.name} on PostHog", template_name="invite", template_context={"invite": invite}, ) message.add_recipient(email=invite.target_email) message.send()
def create(self, validated_data): if getattr(settings, "SAML_ENFORCED", False): raise serializers.ValidationError( "Password reset is disabled because SAML login is enforced.", code="saml_enforced") if not is_email_available(): raise serializers.ValidationError( "Cannot reset passwords because email is not configured for your instance. Please contact your administrator.", code="email_not_available", ) email = validated_data.pop("email") try: user = User.objects.get(email=email) except User.DoesNotExist: user = None if user: token = default_token_generator.make_token(user) message = EmailMessage( campaign_key=f"password-reset-{user.uuid}-{timezone.now()}", subject=f"Reset your PostHog password", template_name="password_reset", template_context={ "preheader": "Please follow the link inside to reset your password.", "link": f"/reset/{user.uuid}/{token}", "cloud": settings.MULTI_TENANCY, "site_url": settings.SITE_URL, "social_providers": list(user.social_auth.values_list("provider", flat=True)), }, ) message.add_recipient(email) message.send() # TODO: Limit number of requests for password reset emails return True
def send_member_join(invitee_uuid: str, organization_id: str) -> None: invitee: User = User.objects.get(uuid=invitee_uuid) organization: Organization = Organization.objects.get(id=organization_id) campaign_key: str = f"member_join_email_org_{organization_id}_user_{invitee_uuid}" message = EmailMessage( campaign_key=campaign_key, subject=f"{invitee.first_name} joined you on PostHog", template_name="member_join", template_context={ "invitee": invitee, "organization": organization }, ) # Don't send this email to the new member themselves members_to_email = organization.members.exclude(email=invitee.email) if members_to_email: for user in members_to_email: message.add_recipient(email=user.email, name=user.first_name) message.send()
def send_weekly_email_report() -> None: """ Sends the weekly email report to all users in a team. """ if not is_email_available(): logger.info( "Skipping send_weekly_email_report because email is not properly configured" ) return period_start, period_end = get_previous_week() last_week_start: datetime.datetime = period_start - datetime.timedelta(7) last_week_end: datetime.datetime = period_end - datetime.timedelta(7) for team in Team.objects.all(): event_data_set = Event.objects.filter( team=team, timestamp__gte=period_start, timestamp__lte=period_end, ) active_users = PersonDistinctId.objects.filter( distinct_id__in=event_data_set.values( "distinct_id").distinct(), ).distinct() active_users_count: int = active_users.count() if active_users_count == 0: # TODO: Send an email prompting fix to no active users continue last_week_users = PersonDistinctId.objects.filter( distinct_id__in=Event.objects.filter( team=team, timestamp__gte=last_week_start, timestamp__lte=last_week_end, ).values("distinct_id").distinct(), ).distinct() last_week_users_count: int = last_week_users.count() two_weeks_ago_users = PersonDistinctId.objects.filter( distinct_id__in=Event.objects.filter( team=team, timestamp__gte=last_week_start - datetime.timedelta(7), timestamp__lte=last_week_end - datetime.timedelta(7), ).values("distinct_id").distinct(), ).distinct() # used to compute delta in churned users two_weeks_ago_users_count: int = two_weeks_ago_users.count() not_last_week_users = PersonDistinctId.objects.filter( pk__in=active_users.difference(last_week_users, ).values_list( "pk", flat=True, )) # users that were present this week but not last week churned_count = last_week_users.difference(active_users).count() churned_ratio: Optional[float] = (churned_count / last_week_users_count if last_week_users_count > 0 else None) last_week_churn_ratio: Optional[float] = ( two_weeks_ago_users.difference(last_week_users).count() / two_weeks_ago_users_count if two_weeks_ago_users_count > 0 else None) churned_delta: Optional[float] = ( churned_ratio / last_week_churn_ratio - 1 if last_week_churn_ratio else None # type: ignore ) message = EmailMessage( f"PostHog weekly report for {period_start.strftime('%b %d, %Y')} to {period_end.strftime('%b %d')}", "weekly_report", { "preheader": f"Your PostHog weekly report is ready! Your team had {compact_number(active_users_count)} active users last week! 🎉", "team": team.name, "period_start": period_start, "period_end": period_end, "active_users": active_users_count, "active_users_delta": active_users_count / last_week_users_count - 1 if last_week_users_count > 0 else None, "user_distribution": { "new": not_last_week_users.filter( person__created_at__gte=period_start).count() / active_users_count, "retained": active_users.intersection(last_week_users).count() / active_users_count, "resurrected": not_last_week_users.filter( person__created_at__lt=period_start).count() / active_users_count, }, "churned_users": { "abs": churned_count, "ratio": churned_ratio, "delta": churned_delta }, }, ) for user in team.organization.members.all(): # TODO: Skip "unsubscribed" users message.add_recipient(user.email, user.first_name) # TODO: Schedule retry on failed attempt message.send()
def _send_weekly_email_report_for_team(team_id: int) -> None: """ Sends the weekly email report to all users in a team. """ period_start, period_end = get_previous_week() last_week_start: datetime.datetime = period_start - datetime.timedelta(7) last_week_end: datetime.datetime = period_end - datetime.timedelta(7) campaign_key: str = f"weekly_report_for_team_{team_id}_on_{period_start.strftime('%Y-%m-%d')}" team = Team.objects.get(pk=team_id) event_data_set = Event.objects.filter( team=team, timestamp__gte=period_start, timestamp__lte=period_end, ) active_users = PersonDistinctId.objects.filter( distinct_id__in=event_data_set.values( "distinct_id").distinct(), ).distinct() active_users_count: int = active_users.count() if active_users_count == 0: # TODO: Send an email prompting fix to no active users return last_week_users = PersonDistinctId.objects.filter( distinct_id__in=Event.objects.filter( team=team, timestamp__gte=last_week_start, timestamp__lte=last_week_end, ).values("distinct_id").distinct(), ).distinct() last_week_users_count: int = last_week_users.count() two_weeks_ago_users = PersonDistinctId.objects.filter( distinct_id__in=Event.objects.filter( team=team, timestamp__gte=last_week_start - datetime.timedelta(7), timestamp__lte=last_week_end - datetime.timedelta(7), ).values("distinct_id").distinct(), ).distinct() # used to compute delta in churned users two_weeks_ago_users_count: int = two_weeks_ago_users.count() not_last_week_users = PersonDistinctId.objects.filter( pk__in=active_users.difference(last_week_users, ).values_list( "pk", flat=True, )) # users that were present this week but not last week churned_count = last_week_users.difference(active_users).count() churned_ratio: Optional[float] = (churned_count / last_week_users_count if last_week_users_count > 0 else None) last_week_churn_ratio: Optional[float] = ( two_weeks_ago_users.difference(last_week_users).count() / two_weeks_ago_users_count if two_weeks_ago_users_count > 0 else None) churned_delta: Optional[float] = ( churned_ratio / last_week_churn_ratio - 1 if last_week_churn_ratio else None # type: ignore ) message = EmailMessage( campaign_key=campaign_key, subject= f"PostHog weekly report for {period_start.strftime('%b %d, %Y')} to {period_end.strftime('%b %d')}", template_name="weekly_report", template_context={ "preheader": f"Your PostHog weekly report is ready! Your team had {compact_number(active_users_count)} active users last week! 🎉", "team": team.name, "period_start": period_start, "period_end": period_end, "active_users": active_users_count, "active_users_delta": active_users_count / last_week_users_count - 1 if last_week_users_count > 0 else None, "user_distribution": { "new": not_last_week_users.filter( person__created_at__gte=period_start).count() / active_users_count, "retained": active_users.intersection(last_week_users).count() / active_users_count, "resurrected": not_last_week_users.filter( person__created_at__lt=period_start).count() / active_users_count, }, "churned_users": { "abs": churned_count, "ratio": churned_ratio, "delta": churned_delta }, }, ) for user in team.organization.members.all(): # TODO: Skip "unsubscribed" users message.add_recipient(email=user.email, name=user.first_name) message.send()
def send_message_to_all_staff_users(message: EmailMessage) -> None: for user in User.objects.filter(is_active=True, is_staff=True): message.add_recipient(email=user.email, name=user.first_name) message.send()