"""Email formatting and contact lookup for store onboarding.""" import json import logging logger = logging.getLogger(__name__) def format_onboarding_email( stores: list[dict], contacts: list[dict], ) -> str: """Format grouped onboarding email for multiple stores. Args: stores: List of stores with shop_domain, connect_card_uri, link_expires_at contacts: List of contact dicts with name, role, company, email Returns: Plain text email body """ if not stores: logger.warning("format_onboarding_email called with empty stores list") return "" stores_list = "\n".join(f" • {store['shop_domain']} — {store['connect_card_uri']}" for store in stores) names = [c["name"] for c in contacts if c.get("name")] contact_names = ", ".join(names) if names else "Team" # Stores in a batch can be created moments apart, so their expiry # timestamps can differ slightly — use the earliest so the stated # deadline is never later than any individual link's actual expiry. expiry_values = [store["link_expires_at"] for store in stores if store.get("link_expires_at")] expires_at = min(expiry_values) if expiry_values else "unknown" body = f"""Dear {contact_names}, We've set up new Shopify stores for your merchants and need their store managers to complete the OAuth authorization. Please share the links below with the appropriate store managers. Stores to authorize: {stores_list} IMPORTANT: These links expire in 24 hours (by {expires_at}). Store managers must complete authorization before then. Instructions for Store Managers: 1. Click the link above 2. Log in to Shopify 3. Authorize access to your store data 4. You're done! If you have any questions, contact: D2C Support Team Best regards, Sony Music PDEGO D2C Team """ return body def parse_contacts_from_array(contacts_array: list | str | None) -> list[dict]: """Parse Snowflake ARRAY(OBJECT) contacts into list of dicts. Snowflake returns ARRAY of OBJECT as list of dicts with keys: 'NAME', 'ROLE', 'COMPANY', 'EMAIL' (uppercase from Snowflake) Args: contacts_array: Raw Snowflake ARRAY response (or already parsed list) Returns: List of contact dicts with lowercase keys """ if not contacts_array: return [] if isinstance(contacts_array, str): # If Snowflake returns as JSON string, parse it try: contacts_array = json.loads(contacts_array) except (json.JSONDecodeError, TypeError): logger.warning("Failed to parse contacts JSON (invalid format)") return [] if not isinstance(contacts_array, list): return [] # Normalize keys to lowercase normalized = [] for contact in contacts_array: if isinstance(contact, dict): normalized.append( { "name": contact.get("NAME") if contact.get("NAME") is not None else contact.get("name"), "role": contact.get("ROLE") if contact.get("ROLE") is not None else contact.get("role"), "company": contact.get("COMPANY") if contact.get("COMPANY") is not None else contact.get("company"), "email": contact.get("EMAIL") if contact.get("EMAIL") is not None else contact.get("email"), } ) return normalized def get_email_addresses(contacts: list[dict]) -> list[str]: """Extract email addresses from contact list. Args: contacts: List of contact dicts Returns: List of unique email addresses """ emails = set() for contact in contacts: email = contact.get("email") if email and isinstance(email, str) and "@" in email and "." in email.split("@")[1]: emails.add(email.lower()) return sorted(list(emails))