Skip to content

Commit 33f23fc

Browse files
authored
Merge pull request #955 from airweave-ai/feat/donke/slack-notifications
Feat/donke/slack notifications
2 parents de3dd46 + 4f5b805 commit 33f23fc

File tree

3 files changed

+201
-0
lines changed

3 files changed

+201
-0
lines changed

backend/airweave/api/v1/endpoints/organizations.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ async def create_organization(
6060
},
6161
)
6262

63+
# Notify Donke about new sign-up
64+
await _notify_donke_signup(organization, user, db)
65+
6366
return organization
6467
except Exception as e:
6568
from airweave.core.logging import logger
@@ -633,3 +636,58 @@ async def leave_organization(
633636
raise HTTPException(status_code=500, detail="Failed to leave organization")
634637
except Exception as e:
635638
raise HTTPException(status_code=400, detail=str(e)) from e
639+
640+
641+
async def _notify_donke_signup(
642+
organization: schemas.Organization,
643+
user: User,
644+
db: AsyncSession,
645+
) -> None:
646+
"""Notify Donke about new sign-up (best-effort).
647+
648+
Args:
649+
organization: The newly created organization
650+
user: The user who created the organization
651+
db: Database session
652+
"""
653+
import httpx
654+
655+
from airweave.core.config import settings
656+
657+
if not settings.DONKE_URL or not settings.DONKE_API_KEY:
658+
return
659+
660+
try:
661+
# Get plan from billing
662+
billing = await crud.organization_billing.get_by_organization(
663+
db, organization_id=organization.id
664+
)
665+
# Handle both enum and string cases for billing_plan
666+
if billing:
667+
plan = (
668+
billing.billing_plan.value
669+
if hasattr(billing.billing_plan, "value")
670+
else str(billing.billing_plan)
671+
)
672+
else:
673+
plan = "developer"
674+
675+
# Simple HTTP call to Donke (uses Azure app key)
676+
async with httpx.AsyncClient() as client:
677+
await client.post(
678+
f"{settings.DONKE_URL}/api/notify-signup?code={settings.DONKE_API_KEY}",
679+
headers={
680+
"Content-Type": "application/json",
681+
},
682+
json={
683+
"organization_name": organization.name,
684+
"user_email": user.email,
685+
"user_name": user.full_name,
686+
"plan": plan,
687+
"organization_id": str(organization.id),
688+
},
689+
timeout=5.0,
690+
)
691+
logger.info(f"Notified Donke about signup for organization {organization.id}")
692+
except Exception as e:
693+
logger.warning(f"Failed to notify Donke about signup: {e}")

backend/airweave/billing/webhook_handler.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,16 @@ async def _handle_subscription_created(
194194

195195
log.info(f"Subscription created for org {org_id}: {plan}")
196196

197+
# Notify Donke about paid subscription
198+
if plan != BillingPlan.DEVELOPER:
199+
await _notify_donke_subscription(
200+
org_schema, plan, UUID(org_id), is_yearly=False, log=log
201+
)
202+
# Send welcome email for Team plans
203+
await _send_team_welcome_email(
204+
self.db, org_schema, plan, UUID(org_id), is_yearly=False, log=log
205+
)
206+
197207
async def _handle_subscription_updated( # noqa: C901
198208
self,
199209
event: stripe.Event,
@@ -885,6 +895,135 @@ async def _finalize_yearly_prepay( # noqa: C901
885895
)
886896

887897
log.info(f"Yearly prepay finalized for org {organization_id}: sub {sub.id}")
898+
899+
# Notify Donke about yearly subscription
900+
await _notify_donke_subscription(
901+
org, BillingPlan(plan_str), organization_id, is_yearly=True, log=log
902+
)
903+
# Send welcome email for Team plans
904+
await _send_team_welcome_email(
905+
self.db,
906+
org,
907+
BillingPlan(plan_str),
908+
organization_id,
909+
is_yearly=True,
910+
log=log,
911+
)
888912
except Exception as e:
889913
log.error(f"Error finalizing yearly prepay: {e}", exc_info=True)
890914
raise
915+
916+
917+
async def _notify_donke_subscription(
918+
org: schemas.Organization,
919+
plan: BillingPlan,
920+
org_id: UUID,
921+
is_yearly: bool,
922+
log: ContextualLogger,
923+
) -> None:
924+
"""Notify Donke about paid subscription (best-effort).
925+
926+
Args:
927+
org: The organization schema
928+
plan: The billing plan
929+
org_id: Organization ID
930+
is_yearly: Whether this is a yearly subscription
931+
log: Contextual logger
932+
"""
933+
import httpx
934+
935+
from airweave.core.config import settings
936+
937+
if not settings.DONKE_URL or not settings.DONKE_API_KEY:
938+
return
939+
940+
try:
941+
async with httpx.AsyncClient() as client:
942+
await client.post(
943+
f"{settings.DONKE_URL}/api/notify-subscription?code={settings.DONKE_API_KEY}",
944+
headers={
945+
"Content-Type": "application/json",
946+
},
947+
json={
948+
"organization_name": org.name,
949+
"plan": plan.value,
950+
"organization_id": str(org_id),
951+
"is_yearly": is_yearly,
952+
"user_email": None, # Could get from org owner if needed
953+
},
954+
timeout=5.0,
955+
)
956+
log.info(f"Notified Donke about subscription for {org_id}")
957+
except Exception as e:
958+
log.warning(f"Failed to notify Donke: {e}")
959+
960+
961+
async def _send_team_welcome_email(
962+
db: AsyncSession,
963+
org: schemas.Organization,
964+
plan: BillingPlan,
965+
org_id: UUID,
966+
is_yearly: bool,
967+
log: ContextualLogger,
968+
) -> None:
969+
"""Send welcome email to Team plan subscribers via Donke (best-effort).
970+
971+
Args:
972+
db: Database session
973+
org: The organization schema
974+
plan: The billing plan
975+
org_id: Organization ID
976+
is_yearly: Whether this is a yearly subscription
977+
log: Contextual logger
978+
"""
979+
import httpx
980+
from sqlalchemy import select
981+
982+
from airweave.core.config import settings
983+
from airweave.models.user import User
984+
from airweave.models.user_organization import UserOrganization
985+
986+
# Only send for Team plans
987+
if plan != BillingPlan.TEAM:
988+
return
989+
990+
if not settings.DONKE_URL or not settings.DONKE_API_KEY:
991+
return
992+
993+
try:
994+
# Get organization owner to send email
995+
stmt = (
996+
select(User)
997+
.join(UserOrganization, User.id == UserOrganization.user_id)
998+
.where(
999+
UserOrganization.organization_id == org_id,
1000+
UserOrganization.role == "owner",
1001+
)
1002+
.limit(1)
1003+
)
1004+
result = await db.execute(stmt)
1005+
owner = result.scalar_one_or_none()
1006+
1007+
if not owner:
1008+
log.warning(f"No owner found for organization {org_id}, skipping welcome email")
1009+
return
1010+
1011+
# Call Donke to send the welcome email
1012+
async with httpx.AsyncClient() as client:
1013+
await client.post(
1014+
f"{settings.DONKE_URL}/api/send-team-welcome-email?code={settings.DONKE_API_KEY}",
1015+
headers={
1016+
"Content-Type": "application/json",
1017+
},
1018+
json={
1019+
"organization_name": org.name,
1020+
"user_email": owner.email,
1021+
"user_name": owner.full_name or owner.email,
1022+
"plan": plan.value,
1023+
"is_yearly": is_yearly,
1024+
},
1025+
timeout=5.0,
1026+
)
1027+
log.info(f"Team welcome email sent via Donke for {org_id} to {owner.email}")
1028+
except Exception as e:
1029+
log.warning(f"Failed to send Team welcome email via Donke: {e}")

backend/airweave/core/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ class Settings(BaseSettings):
152152
RESEND_API_KEY: Optional[str] = None
153153
RESEND_FROM_EMAIL: Optional[str] = None
154154

155+
# Donke integration (internal automation service)
156+
DONKE_URL: Optional[str] = None
157+
DONKE_API_KEY: Optional[str] = None
158+
155159
# PostHog Analytics Configuration
156160
# Public API key for open source and hosted platform
157161
POSTHOG_API_KEY: str = "phc_Ytp26UB3WwGCdjHTpDBI9HQg2ZA38ITMDKI6fE6EPGS"

0 commit comments

Comments
 (0)