From 9c17a6f30242aa2253b4b86d2d639d891723c186 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 27 May 2026 15:59:19 +0530 Subject: [PATCH] feat(onboarding): detect email provider from domain MX records (#14571) During account enrichment, WebsiteBrandingService now probes the signup domain's MX records to infer whether email is hosted on Google Workspace or Microsoft 365, adding `email_provider` (`google`/`microsoft`/`nil`) to `brand_info`. Matching is anchored on a label boundary so lookalike domains aren't misclassified, and lookup failures fall back to `nil`. This lets downstream UI suggest the right mailbox integration (Gmail vs Outlook). --- app/services/website_branding_service.rb | 36 ++++++++++++++++++- .../enterprise/website_branding_service.rb | 1 + 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/app/services/website_branding_service.rb b/app/services/website_branding_service.rb index 3592267ff..026b51e0e 100644 --- a/app/services/website_branding_service.rb +++ b/app/services/website_branding_service.rb @@ -1,3 +1,5 @@ +require 'resolv' + class WebsiteBrandingService include SocialLinkParser @@ -24,7 +26,8 @@ class WebsiteBrandingService colors: extract_colors(doc), logos: extract_logos(doc), socials: build_socials(links), - email: @email + email: @email, + email_provider: detect_email_provider }) rescue StandardError => e Rails.logger.error "[WebsiteBranding] #{e.message}" @@ -104,6 +107,37 @@ class WebsiteBrandingService rescue URI::InvalidURIError nil end + + GOOGLE_MX_DOMAINS = %w[google.com googlemail.com].freeze + MICROSOFT_MX_DOMAINS = %w[outlook.com].freeze + + # Probes the domain's MX records to infer the mailbox provider, returning + # 'google' or 'microsoft' (matching Channel::Email#provider) or nil when unknown. + def detect_email_provider + hosts = mx_records + return 'google' if mx_hosted_by?(hosts, GOOGLE_MX_DOMAINS) + return 'microsoft' if mx_hosted_by?(hosts, MICROSOFT_MX_DOMAINS) + + nil + end + + # Matches on the registrable domain of the MX host (anchored on a label + # boundary) so lookalikes like "notgoogle.com" don't get misclassified. + def mx_hosted_by?(hosts, provider_domains) + hosts.any? do |host| + provider_domains.any? { |domain| host == domain || host.end_with?(".#{domain}") } + end + end + + def mx_records + Resolv::DNS.open do |resolver| + resolver.timeouts = 5 + resolver.getresources(@domain, Resolv::DNS::Resource::IN::MX).map { |record| record.exchange.to_s.downcase } + end + rescue StandardError => e + Rails.logger.error "[WebsiteBranding] MX probe failed for #{@domain}: #{e.message}" + [] + end end WebsiteBrandingService.prepend_mod_with('WebsiteBrandingService') diff --git a/enterprise/app/services/enterprise/website_branding_service.rb b/enterprise/app/services/enterprise/website_branding_service.rb index 553317c43..c6f2fbdaa 100644 --- a/enterprise/app/services/enterprise/website_branding_service.rb +++ b/enterprise/app/services/enterprise/website_branding_service.rb @@ -58,6 +58,7 @@ module Enterprise::WebsiteBrandingService socials: brand['socials'] || [], links: brand['links'], email: @email, + email_provider: detect_email_provider, industries: brand.dig('industries', 'eic') || [], stock: brand['stock'], is_nsfw: brand['is_nsfw'] || false