Compare commits
10
Commits
3e73e708e4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f7794959e | ||
|
|
36b3ffe507 | ||
|
|
353ace4d99 | ||
|
|
1c6b4bbabe | ||
|
|
390cb0e657 | ||
|
|
3c8bfe053f | ||
|
|
4cf34b2033 | ||
|
|
238bbb0c68 | ||
|
|
403fe80d37 | ||
|
|
a9470d7e77 |
@@ -19,7 +19,8 @@ Endpoint MCP: `http://localhost:3001/mcp` (Bearer). In produzione usa l’URL pu
|
||||
1. `list_projects` — ottieni i `project_code` (matchlivetv, riskmeter, cardoo, …).
|
||||
2. `today` con quel codice — task scaduti / oggi / in arrivo e opportunità ferme.
|
||||
3. `search` o `get_organization` per il contesto.
|
||||
4. Agisci con `create_activity`, `complete_task`, `create_task`, `update_opportunity_stage`.
|
||||
4. Agisci con `create_activity`, `complete_task`, `create_task`, `update_opportunity_stage`, `update_organization_email`, `check_mail_bounces`.
|
||||
5. Per i bounce della casella mittente: `check_mail_bounces` (IMAP = stesse credenziali SMTP della MailIdentity).
|
||||
|
||||
## Limiti
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ Config già nel repo (il token sta solo in env, non nei file):
|
||||
|
||||
Istruzioni per gli agenti: [AGENTS.md](AGENTS.md).
|
||||
|
||||
Tool: `list_projects`, `today`, `search`, `get_organization`, `create_task`, `complete_task`, `create_activity`, `update_opportunity_stage`.
|
||||
Tool: `list_projects`, `today`, `search`, `get_organization`, `create_task`, `complete_task`, `create_activity`, `update_opportunity_stage`, `update_organization_email`, `check_mail_bounces`.
|
||||
|
||||
Stdio (`mcp/server.rb` / `bin/crm-mcp`) resta come alternativa se un client non parla HTTP.
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
module Api
|
||||
module V1
|
||||
class MailBouncesController < BaseController
|
||||
def index
|
||||
render_agent Crm::AgentSession.new(current_user).check_mail_bounces(
|
||||
params[:project_code],
|
||||
bounce_params
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def bounce_params
|
||||
params.permit(
|
||||
:mail_identity_id,
|
||||
:from_email,
|
||||
:since_days,
|
||||
:mailbox,
|
||||
:limit
|
||||
).to_h
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
module Api
|
||||
module V1
|
||||
class OrganizationEmailsController < BaseController
|
||||
def update
|
||||
render_agent Crm::AgentSession.new(current_user).update_organization_email(
|
||||
params[:project_code],
|
||||
email_params
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def email_params
|
||||
params.permit(
|
||||
:organization_id,
|
||||
:contact_id,
|
||||
:email,
|
||||
:email_invalid,
|
||||
:bounced_email,
|
||||
:bounce_reason,
|
||||
:website,
|
||||
:activity_subject,
|
||||
:activity_description,
|
||||
:update_primary_contact
|
||||
).to_h
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -66,7 +66,7 @@ class ContactsController < ApplicationController
|
||||
|
||||
def contact_params
|
||||
params.require(:contact).permit(
|
||||
:organization_id, :first_name, :last_name, :role, :email, :phone, :mobile,
|
||||
:organization_id, :first_name, :last_name, :role, :email, :email_invalid, :bounced_email, :phone, :mobile,
|
||||
:preferred_contact_method, :notes, :primary_contact
|
||||
)
|
||||
end
|
||||
|
||||
@@ -57,6 +57,7 @@ class MailIdentitiesController < ApplicationController
|
||||
params.require(:mail_identity).permit(
|
||||
:name, :from_name, :from_email, :reply_to, :smtp_host, :smtp_port,
|
||||
:smtp_username, :smtp_password, :smtp_authentication, :encryption,
|
||||
:imap_host, :imap_port, :imap_enabled,
|
||||
:verify_ssl, :active
|
||||
)
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class MailingsController < ApplicationController
|
||||
before_action :require_current_project!
|
||||
before_action :set_mailing, only: %i[show edit update destroy queue cancel test_send test_preview refresh_recipients update_recipients preview audience update_audience]
|
||||
before_action :set_mailing, only: %i[show edit update destroy queue pause resume cancel test_send test_preview refresh_recipients update_recipients preview audience update_audience]
|
||||
before_action :load_form_collections, only: %i[new create edit update]
|
||||
|
||||
def index
|
||||
@@ -11,10 +11,12 @@ class MailingsController < ApplicationController
|
||||
@page_title = "Invii email"
|
||||
@mailings = Mailing.for_project(current_project).includes(:mail_identity).recent
|
||||
@mail_identities_count = MailIdentity.active.count
|
||||
@active_mailings = @mailings.select(&:sending?)
|
||||
@paused_mailings = @active_mailings.select { |mailing| mailing.next_send_at.present? && mailing.next_send_at > Time.current }
|
||||
@active_mailings = @mailings.select { |mailing| mailing.sending? || mailing.paused? }
|
||||
@window_paused_mailings = @mailings.select(&:sending?).select { |mailing| mailing.next_send_at.present? && mailing.next_send_at > Time.current }
|
||||
@paused_mailings = @window_paused_mailings
|
||||
@completed_today = @mailings.count { |mailing| mailing.sent? && mailing.completed_at&.to_date == Time.zone.today }
|
||||
@failed_open = @mailings.sum(&:failed_count)
|
||||
@hourly_throughput = Mailings::HourlyThroughput.new(current_project, range: params[:history])
|
||||
end
|
||||
|
||||
def new
|
||||
@@ -180,16 +182,42 @@ class MailingsController < ApplicationController
|
||||
redirect_to @mailing, alert: e.message
|
||||
end
|
||||
|
||||
def cancel
|
||||
def pause
|
||||
unless @mailing.sending?
|
||||
redirect_to @mailing, alert: "Solo un invio in corso può essere fermato."
|
||||
redirect_to @mailing, alert: "Solo un invio in corso può essere messo in pausa."
|
||||
return
|
||||
end
|
||||
|
||||
remaining = @mailing.pending_count + @mailing.queued_count
|
||||
@mailing.pause_send!
|
||||
redirect_to dashboard_mailings_path,
|
||||
notice: "Invio in pausa. #{@mailing.sent_count} già inviate, #{remaining} pronte per quando riprendi."
|
||||
rescue StandardError => e
|
||||
redirect_to @mailing, alert: e.message
|
||||
end
|
||||
|
||||
def resume
|
||||
unless @mailing.paused?
|
||||
redirect_to @mailing, alert: "Solo un invio in pausa può essere ripreso."
|
||||
return
|
||||
end
|
||||
|
||||
@mailing.resume_send!
|
||||
redirect_to dashboard_mailings_path, notice: "Invio ripreso da dove era rimasto."
|
||||
rescue StandardError => e
|
||||
redirect_to @mailing, alert: e.message
|
||||
end
|
||||
|
||||
def cancel
|
||||
unless @mailing.sending? || @mailing.paused?
|
||||
redirect_to @mailing, alert: "Solo un invio in corso o in pausa può essere annullato."
|
||||
return
|
||||
end
|
||||
|
||||
pending = @mailing.pending_count + @mailing.queued_count
|
||||
@mailing.cancel_send!
|
||||
redirect_to dashboard_mailings_path,
|
||||
notice: "Invio fermato. #{@mailing.sent_count} già inviate, #{pending} annullate in coda."
|
||||
notice: "Invio annullato. #{@mailing.sent_count} già inviate, #{pending} in coda saltate."
|
||||
rescue StandardError => e
|
||||
redirect_to @mailing, alert: e.message
|
||||
end
|
||||
@@ -205,7 +233,12 @@ class MailingsController < ApplicationController
|
||||
html = @test_preview_html
|
||||
subject = "[TEST#{@mailing.ab_test? ? " #{@test_variant}" : ""}] #{@test_preview_subject}"
|
||||
recipient = Mailings::TestRecipient.new(mailing: @mailing, email: to)
|
||||
Mailings::OutboundQueue.exclusive do
|
||||
Mailings::SmtpGate.deliver do
|
||||
CampaignMailer.outreach(recipient, html: html, subject: subject).deliver_now
|
||||
end
|
||||
Mailings::OutboundQueue.stamp!
|
||||
end
|
||||
@mailing.update!(test_sent_at: Time.current, test_sent_to: to)
|
||||
redirect_to @mailing, notice: "Prova inviata subito a #{to}. Controlla l'output e, se è corretto, autorizza l'invio di massa."
|
||||
rescue StandardError => e
|
||||
@@ -273,6 +306,7 @@ class MailingsController < ApplicationController
|
||||
def load_audience_options
|
||||
orgs = Organization.for_project(current_project)
|
||||
@sport_options = orgs.where.not(sport: [nil, ""]).distinct.order(:sport).pluck(:sport)
|
||||
@country_options = orgs.where.not(country: [nil, ""]).distinct.order(:country).pluck(:country)
|
||||
@region_options = orgs.where.not(region: [nil, ""]).distinct.order(:region).pluck(:region)
|
||||
@province_options = orgs.where.not(province: [nil, ""]).distinct.order(:province).pluck(:province)
|
||||
@history_mailings = Mailing.for_project(current_project).where.not(id: @mailing.id).recent
|
||||
@@ -296,7 +330,7 @@ class MailingsController < ApplicationController
|
||||
ActionController::Parameters.new(copied).permit(
|
||||
:preset, :list_min, :list_max, :estimated_value, :history_kind, :history_days,
|
||||
:history_mailing_id, :never_contacted, :exclude_customers, :exclude_mailed_within_days,
|
||||
sports: [], team_genders: [], regions: [], provinces: [], streaming_statuses: [], statuses: [],
|
||||
sports: [], countries: [], team_genders: [], regions: [], provinces: [], streaming_statuses: [], statuses: [],
|
||||
send_statuses: [], ab_variants: [], pipeline_stages: [],
|
||||
exclude_received_mailing_ids: [], exclude_opened_mailing_ids: []
|
||||
)
|
||||
|
||||
@@ -86,7 +86,7 @@ class OrganizationsController < ApplicationController
|
||||
def organization_params
|
||||
params.require(:organization).permit(
|
||||
:name, :legal_name, :organization_type, :sport, :country, :region, :province, :city,
|
||||
:address, :website, :source_url, :phone, :email, :vat_number, :notes, :status, :lead_source,
|
||||
:address, :website, :source_url, :phone, :email, :email_invalid, :bounced_email, :vat_number, :notes, :status, :lead_source,
|
||||
:assigned_user_id, :list_position, :team_gender, :streaming_status, :commercial_fit, :verified_at,
|
||||
project_ids: []
|
||||
)
|
||||
|
||||
@@ -163,6 +163,7 @@ module ApplicationHelper
|
||||
colors = {
|
||||
"draft" => "bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-200",
|
||||
"sending" => "bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-200",
|
||||
"paused" => "bg-sky-100 text-sky-800 dark:bg-sky-950 dark:text-sky-200",
|
||||
"sent" => "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200",
|
||||
"cancelled" => "bg-rose-100 text-rose-800 dark:bg-rose-950 dark:text-rose-200"
|
||||
}
|
||||
@@ -195,6 +196,15 @@ module ApplicationHelper
|
||||
end
|
||||
end
|
||||
|
||||
def mailing_recipient_delivery_note(recipient)
|
||||
if recipient.status == "failed" && recipient.error_message.present?
|
||||
content_tag :div, recipient.error_message, class: "mt-1 text-xs text-rose-700 dark:text-rose-300"
|
||||
elsif recipient.queued? && recipient.error_message.to_s.match?(/Da ritentare/)
|
||||
content_tag :div, "Non risulta inviata. Verrà ritentata in coda.",
|
||||
class: "mt-1 text-xs text-amber-800 dark:text-amber-200"
|
||||
end
|
||||
end
|
||||
|
||||
def ab_variant_badge(variant)
|
||||
return if variant.blank?
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
class DrainOutboundJob < ApplicationJob
|
||||
queue_as :mailers
|
||||
|
||||
def perform
|
||||
result = Mailings::OutboundQueue.drain_one!
|
||||
return unless result.in?(%i[sent deferred wait])
|
||||
return unless Mailings::OutboundQueue.queued_scope.exists?
|
||||
|
||||
wait = Mailings::OutboundQueue.seconds_until_next_slot
|
||||
wait = 0 if wait.blank? || wait.negative?
|
||||
max_wait = [ Mailings::OutboundQueue.min_interval.to_f, 2.minutes ].max + 5.seconds
|
||||
return if wait > max_wait
|
||||
|
||||
self.class.set(wait: wait).perform_later
|
||||
end
|
||||
end
|
||||
@@ -1,28 +1,16 @@
|
||||
class ResumeMailingsJob < ApplicationJob
|
||||
queue_as :mailers
|
||||
|
||||
STALE_QUEUED_AFTER = 10.minutes
|
||||
|
||||
def perform
|
||||
recover_stale_queued!
|
||||
Mailing.sending.due_to_send.find_each do |mailing|
|
||||
next unless Mailings::SendClock.new(mailing).open?
|
||||
next if mailing.mailing_recipients.queued.exists?
|
||||
|
||||
recipient = mailing.mailing_recipients.pending.order(:id).first
|
||||
next unless recipient
|
||||
|
||||
SendMailingRecipientJob.perform_later(recipient.id)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def recover_stale_queued!
|
||||
MailingRecipient.queued.where("updated_at < ?", STALE_QUEUED_AFTER.ago).find_each do |recipient|
|
||||
next unless recipient.mailing.sending?
|
||||
|
||||
recipient.update!(status: "pending", error_message: nil)
|
||||
end
|
||||
DrainOutboundJob.perform_later if Mailings::OutboundQueue.queued_scope.exists?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,14 +21,11 @@ class SendMailingRecipientJob < ApplicationJob
|
||||
return
|
||||
end
|
||||
|
||||
begin
|
||||
CampaignMailer.raise_delivery_errors = true
|
||||
recipient.deliver!
|
||||
ensure
|
||||
CampaignMailer.raise_delivery_errors = false if Rails.env.development?
|
||||
end
|
||||
|
||||
if recipient.pending? && Mailings::OutboundQueue.enqueue(recipient)
|
||||
nxt = mailing.mailing_recipients.pending.order(:id).first
|
||||
mailing.enqueue_next_recipient!(nxt, from: Time.current + mailing.interval_seconds.seconds)
|
||||
end
|
||||
|
||||
DrainOutboundJob.perform_later
|
||||
end
|
||||
end
|
||||
|
||||
@@ -68,6 +68,7 @@ module Catalog
|
||||
MAILING_STATUSES = {
|
||||
"draft" => "Bozza",
|
||||
"sending" => "Invio in corso",
|
||||
"paused" => "In pausa",
|
||||
"sent" => "Inviata",
|
||||
"cancelled" => "Annullata"
|
||||
}.freeze
|
||||
|
||||
@@ -9,15 +9,30 @@ class MailIdentity < ApplicationRecord
|
||||
|
||||
ENCRYPTIONS = Catalog::MAIL_ENCRYPTIONS.keys.freeze
|
||||
|
||||
# Mapping host SMTP → IMAP tipici dei provider usati in produzione.
|
||||
IMAP_HOST_HINTS = {
|
||||
"aruba.it" => "imaps.aruba.it",
|
||||
"smtps.aruba.it" => "imaps.aruba.it",
|
||||
"smtp.aruba.it" => "imaps.aruba.it",
|
||||
"gmail.com" => "imap.gmail.com",
|
||||
"smtp.gmail.com" => "imap.gmail.com",
|
||||
"outlook.com" => "outlook.office365.com",
|
||||
"smtp.office365.com" => "outlook.office365.com"
|
||||
}.freeze
|
||||
|
||||
validates :name, :from_name, :from_email, :smtp_host, :smtp_port, presence: true
|
||||
validates :from_email, format: { with: URI::MailTo::EMAIL_REGEXP }
|
||||
validates :reply_to, format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true
|
||||
validates :smtp_port, numericality: { in: 1..65535 }
|
||||
validates :imap_port, numericality: { in: 1..65535 }, allow_nil: true
|
||||
validates :encryption, inclusion: { in: ENCRYPTIONS }
|
||||
validates :smtp_authentication, inclusion: { in: Catalog::MAIL_AUTH_METHODS.keys }
|
||||
validate :encryption_matches_port
|
||||
|
||||
before_validation :apply_imap_defaults
|
||||
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :imap_enabled, -> { where(imap_enabled: true) }
|
||||
scope :ordered, -> { order(:name) }
|
||||
|
||||
def from_header
|
||||
@@ -30,7 +45,9 @@ class MailIdentity < ApplicationRecord
|
||||
port: smtp_port,
|
||||
enable_starttls_auto: encryption == "starttls",
|
||||
ssl: encryption == "tls",
|
||||
openssl_verify_mode: verify_ssl? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
||||
openssl_verify_mode: verify_ssl? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE,
|
||||
open_timeout: 15,
|
||||
read_timeout: 60
|
||||
}
|
||||
if smtp_username.present?
|
||||
settings[:user_name] = smtp_username
|
||||
@@ -40,8 +57,56 @@ class MailIdentity < ApplicationRecord
|
||||
settings
|
||||
end
|
||||
|
||||
def imap_username
|
||||
smtp_username.presence || from_email
|
||||
end
|
||||
|
||||
def effective_imap_host
|
||||
imap_host.presence || inferred_imap_host
|
||||
end
|
||||
|
||||
def effective_imap_port
|
||||
(imap_port.presence || 993).to_i
|
||||
end
|
||||
|
||||
def imap_ready?
|
||||
imap_enabled? && effective_imap_host.present? && effective_imap_port.positive?
|
||||
end
|
||||
|
||||
def imap_settings
|
||||
{
|
||||
address: effective_imap_host,
|
||||
port: effective_imap_port,
|
||||
ssl: true,
|
||||
user_name: imap_username,
|
||||
password: smtp_password
|
||||
}
|
||||
end
|
||||
|
||||
def inferred_imap_host
|
||||
host = smtp_host.to_s.downcase.strip
|
||||
return if host.blank?
|
||||
|
||||
IMAP_HOST_HINTS.each do |needle, imap|
|
||||
return imap if host == needle || host.end_with?(".#{needle}") || host.include?(needle)
|
||||
end
|
||||
|
||||
# Fallback generico: smtp.X → imap.X, smtps.X → imaps.X
|
||||
if host.start_with?("smtps.")
|
||||
host.sub(/\Asmtps\./, "imaps.")
|
||||
elsif host.start_with?("smtp.")
|
||||
host.sub(/\Asmtp\./, "imap.")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def apply_imap_defaults
|
||||
self.imap_port = 993 if imap_port.blank?
|
||||
self.imap_enabled = true if imap_enabled.nil?
|
||||
self.imap_host = inferred_imap_host if imap_host.blank? && inferred_imap_host.present?
|
||||
end
|
||||
|
||||
def encryption_matches_port
|
||||
return if smtp_port.blank? || encryption.blank?
|
||||
|
||||
|
||||
+20
-1
@@ -77,6 +77,10 @@ class Mailing < ApplicationRecord
|
||||
status == "sending"
|
||||
end
|
||||
|
||||
def paused?
|
||||
status == "paused"
|
||||
end
|
||||
|
||||
def sent?
|
||||
status == "sent"
|
||||
end
|
||||
@@ -215,8 +219,23 @@ class Mailing < ApplicationRecord
|
||||
update!(status: "sent", completed_at: Time.current, next_send_at: nil)
|
||||
end
|
||||
|
||||
def pause_send!
|
||||
raise "Solo un invio in corso può essere messo in pausa" unless sending?
|
||||
|
||||
update!(status: "paused", next_send_at: nil)
|
||||
end
|
||||
|
||||
def resume_send!
|
||||
raise "Solo un invio in pausa può essere ripreso" unless paused?
|
||||
raise "Nessun destinatario da inviare" if pending_count.zero? && queued_count.zero?
|
||||
|
||||
update!(status: "sending")
|
||||
enqueue_next_recipient!(mailing_recipients.pending.order(:id).first, from: Time.current) if pending_count.positive?
|
||||
DrainOutboundJob.perform_later if mailing_recipients.queued.exists?
|
||||
end
|
||||
|
||||
def cancel_send!
|
||||
raise "Solo un invio in corso può essere fermato" unless sending?
|
||||
raise "Solo un invio in corso o in pausa può essere annullato" unless sending? || paused?
|
||||
|
||||
transaction do
|
||||
mailing_recipients.where(status: %w[pending queued]).find_each do |recipient|
|
||||
|
||||
@@ -71,32 +71,76 @@ class MailingRecipient < ApplicationRecord
|
||||
MailMerge.render(mailing.subject_for(ab_variant), **merge_context)
|
||||
end
|
||||
|
||||
def deliver!
|
||||
def queued?
|
||||
status == "queued"
|
||||
end
|
||||
|
||||
def enqueue_for_delivery!
|
||||
Mailings::OutboundQueue.enqueue(self)
|
||||
end
|
||||
|
||||
def deliver_queued!
|
||||
with_lock do
|
||||
return if status.in?(%w[sent skipped])
|
||||
return abort_delivery!("invio annullato") unless mailing.sending?
|
||||
|
||||
update!(status: "queued")
|
||||
return unless queued?
|
||||
end
|
||||
|
||||
return unless mailing.sending?
|
||||
|
||||
html = rendered_html
|
||||
subject_line = rendered_subject_line
|
||||
Mailings::SmtpGate.deliver do
|
||||
CampaignMailer.outreach(self, html: html, subject: subject_line).deliver_now
|
||||
end
|
||||
# Solo dopo deliver_now senza eccezioni: un EOF/SMTP error non è mai un invio riuscito.
|
||||
record_success!(subject_line)
|
||||
:sent
|
||||
rescue *Mailings::SmtpGate::RETRYABLE => e
|
||||
defer_or_fail!(e)
|
||||
rescue StandardError => e
|
||||
update!(status: "failed", error_message: e.message.to_s.truncate(500))
|
||||
:failed
|
||||
ensure
|
||||
mailing.mark_finished_if_done!
|
||||
end
|
||||
|
||||
def deliver!
|
||||
enqueue_for_delivery!
|
||||
deliver_queued! if queued?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def abort_delivery!(reason)
|
||||
update!(status: "skipped", skip_reason: reason)
|
||||
end
|
||||
|
||||
MAX_SMTP_DEFERS = 8
|
||||
# Deve combaciare col testo generato sotto ("Da ritentare (N/8): ..."),
|
||||
# altrimenti smtp_defer_count legge sempre 0 e il retry diventa infinito
|
||||
# (bug osservato in produzione: un destinatario con SMTP EOF permanente
|
||||
# veniva ritentato ogni 30s all'infinito, bloccando gli altri destinatari
|
||||
# in errore della stessa mailing).
|
||||
DEFER_PATTERN = /Da ritentare \((\d+)\//
|
||||
|
||||
def defer_or_fail!(error)
|
||||
attempt = smtp_defer_count + 1
|
||||
message = error.message.to_s.truncate(400)
|
||||
if attempt >= MAX_SMTP_DEFERS
|
||||
update!(status: "failed", error_message: message)
|
||||
:failed
|
||||
else
|
||||
update!(
|
||||
status: "queued",
|
||||
error_message: "Da ritentare (#{attempt}/#{MAX_SMTP_DEFERS}): #{message}"
|
||||
)
|
||||
:deferred
|
||||
end
|
||||
end
|
||||
|
||||
def smtp_defer_count
|
||||
error_message.to_s[DEFER_PATTERN, 1].to_i
|
||||
end
|
||||
|
||||
def record_success!(subject_line)
|
||||
transaction do
|
||||
update!(status: "sent", sent_at: Time.current, rendered_subject: subject_line, error_message: nil)
|
||||
|
||||
@@ -12,6 +12,7 @@ class CampaignImport::MatchlivetvLaunch
|
||||
"LIMITATO" => "limited",
|
||||
"SI / PARZIALE" => "yes_partial",
|
||||
"SI" => "yes",
|
||||
"RILEVATO" => "yes",
|
||||
"SI - SPORTCAM" => "yes_sportcam",
|
||||
"SI SPORTCAM" => "yes_sportcam"
|
||||
}.freeze
|
||||
@@ -53,7 +54,7 @@ class CampaignImport::MatchlivetvLaunch
|
||||
|
||||
rows.each_with_index do |row, idx|
|
||||
name = cell(row, "Società")
|
||||
if duplicate_name?(name)
|
||||
if duplicate_name?(name) || normalize_email(cell(row, "Email verificata")).blank?
|
||||
skipped += 1
|
||||
next
|
||||
end
|
||||
@@ -190,7 +191,7 @@ class CampaignImport::MatchlivetvLaunch
|
||||
end
|
||||
|
||||
def normalize_email(value)
|
||||
email = value.to_s.strip.downcase.gsub(/\s+/, "")
|
||||
email = I18n.transliterate(value.to_s).strip.downcase.gsub(/\s+/, "")
|
||||
email.presence
|
||||
end
|
||||
|
||||
|
||||
@@ -147,8 +147,206 @@ module Crm
|
||||
end
|
||||
end
|
||||
|
||||
# Aggiorna email organizzazione/contatto: bounce, sostituzione, flag non valida + nota timeline.
|
||||
def update_organization_email(project_code, attrs)
|
||||
with_project(project_code) do
|
||||
attrs = attrs.to_h.symbolize_keys
|
||||
organization = organizations_scope.includes(:contacts).find(attrs[:organization_id])
|
||||
contact = nil
|
||||
if attrs[:contact_id].present?
|
||||
contact = organization.contacts.find(attrs[:contact_id])
|
||||
elsif attrs[:update_primary_contact] != false
|
||||
contact = organization.primary_contact || organization.contacts.order(:id).first
|
||||
end
|
||||
|
||||
bounced = attrs[:bounced_email].to_s.strip.downcase.presence
|
||||
new_email = attrs.key?(:email) ? attrs[:email].to_s.strip.downcase.presence : :unchanged
|
||||
mark_invalid = if attrs.key?(:email_invalid)
|
||||
ActiveModel::Type::Boolean.new.cast(attrs[:email_invalid])
|
||||
elsif bounced.present? && (new_email == :unchanged || new_email.nil? || new_email == bounced)
|
||||
true
|
||||
elsif new_email.is_a?(String) && new_email != bounced
|
||||
false
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
previous_org_email = organization.email
|
||||
previous_contact_email = contact&.email
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
org_changes = {}
|
||||
if bounced
|
||||
org_changes[:bounced_email] = bounced
|
||||
end
|
||||
unless new_email == :unchanged
|
||||
org_changes[:email] = new_email
|
||||
end
|
||||
unless mark_invalid.nil?
|
||||
org_changes[:email_invalid] = mark_invalid
|
||||
end
|
||||
if attrs[:website].present?
|
||||
org_changes[:website] = attrs[:website].to_s.strip
|
||||
end
|
||||
organization.update!(org_changes) if org_changes.any?
|
||||
|
||||
if contact
|
||||
contact_changes = {}
|
||||
if bounced
|
||||
contact_changes[:bounced_email] = bounced
|
||||
end
|
||||
unless new_email == :unchanged
|
||||
contact_changes[:email] = new_email
|
||||
end
|
||||
unless mark_invalid.nil?
|
||||
contact_changes[:email_invalid] = mark_invalid
|
||||
end
|
||||
contact.update!(contact_changes) if contact_changes.any?
|
||||
end
|
||||
|
||||
subject = attrs[:activity_subject].presence || begin
|
||||
if new_email.is_a?(String) && new_email.present? && new_email != bounced
|
||||
"Email aggiornata dopo bounce"
|
||||
else
|
||||
"Email non valida (bounce)"
|
||||
end
|
||||
end
|
||||
description = attrs[:activity_description].presence || build_email_update_description(
|
||||
bounced: bounced,
|
||||
new_email: new_email == :unchanged ? nil : new_email,
|
||||
previous_org_email: previous_org_email,
|
||||
previous_contact_email: previous_contact_email,
|
||||
reason: attrs[:bounce_reason]
|
||||
)
|
||||
|
||||
activity = organization.activities.create!(
|
||||
user: @user,
|
||||
contact: contact,
|
||||
activity_type: "note",
|
||||
subject: subject,
|
||||
description: description,
|
||||
happened_at: Time.current
|
||||
)
|
||||
|
||||
ok(
|
||||
organization: organization_json(organization.reload),
|
||||
contact: contact ? contact_json(contact.reload) : nil,
|
||||
activity: activity_json(activity)
|
||||
)
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
err("Organizzazione o contatto non trovato", status: :not_found)
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
validation_error(e.record)
|
||||
end
|
||||
end
|
||||
|
||||
# Legge bounce IMAP dall'account SMTP/IMAP (stesse credenziali MailIdentity).
|
||||
def check_mail_bounces(project_code = nil, attrs = {})
|
||||
attrs = attrs.to_h.symbolize_keys
|
||||
if project_code.present?
|
||||
project = Project.active.find_by(code: project_code.to_s)
|
||||
return err("Progetto non trovato", status: :not_found) if project.nil?
|
||||
return err("Progetto non accessibile", status: :forbidden) unless @user.can_access_project?(project)
|
||||
|
||||
@project = project
|
||||
Current.project = project
|
||||
end
|
||||
|
||||
identity = find_mail_identity(attrs)
|
||||
return err("Account email non trovato", status: :not_found) if identity.nil?
|
||||
|
||||
since_days = (attrs[:since_days].presence || 14).to_i.clamp(1, 90)
|
||||
result = Mailings::ImapBounceReader.new(
|
||||
identity,
|
||||
since: since_days.days.ago,
|
||||
mailbox: attrs[:mailbox].presence || "INBOX",
|
||||
limit: (attrs[:limit].presence || 200).to_i.clamp(1, 500)
|
||||
).call
|
||||
|
||||
unless result.ok
|
||||
return err(result.error || "Lettura IMAP fallita", status: :unprocessable_entity, extra: { identity: result.identity })
|
||||
end
|
||||
|
||||
bounces = enrich_bounces_with_crm(result.bounces)
|
||||
ok(
|
||||
identity: result.identity,
|
||||
since_days: since_days,
|
||||
scanned: result.scanned,
|
||||
bounce_count: bounces.size,
|
||||
unique_failed_emails: bounces.flat_map { |b| b[:failed_emails] }.uniq.sort,
|
||||
bounces: bounces
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_mail_identity(attrs)
|
||||
scope = MailIdentity.active
|
||||
if attrs[:mail_identity_id].present?
|
||||
scope.find_by(id: attrs[:mail_identity_id])
|
||||
elsif attrs[:from_email].present?
|
||||
scope.find_by("LOWER(from_email) = ?", attrs[:from_email].to_s.downcase.strip)
|
||||
else
|
||||
scope.imap_enabled.ordered.find_by("LOWER(from_email) = ?", "info@matchlivetv.it") ||
|
||||
scope.imap_enabled.ordered.first
|
||||
end
|
||||
end
|
||||
|
||||
def enrich_bounces_with_crm(bounces)
|
||||
return bounces if @project.nil?
|
||||
|
||||
orgs = organizations_scope.includes(:contacts).to_a
|
||||
bounces.map do |bounce|
|
||||
matches = []
|
||||
Array(bounce[:failed_emails]).each do |email|
|
||||
org = orgs.find { |o| o.email.to_s.downcase == email } ||
|
||||
orgs.find { |o| o.contacts.any? { |c| c.email.to_s.downcase == email } } ||
|
||||
orgs.find { |o| o.bounced_email.to_s.downcase == email }
|
||||
next unless org
|
||||
|
||||
matches << {
|
||||
organization_id: org.id,
|
||||
name: org.name,
|
||||
current_email: org.email,
|
||||
email_invalid: org.email_invalid?,
|
||||
sport: org.sport
|
||||
}
|
||||
end
|
||||
if matches.empty? && bounce[:club_hint].present?
|
||||
hint = bounce[:club_hint].to_s.downcase
|
||||
org = orgs.find { |o| o.name.to_s.downcase == hint } ||
|
||||
orgs.find { |o| o.name.to_s.downcase.include?(hint) || hint.include?(o.name.to_s.downcase) }
|
||||
if org
|
||||
matches << {
|
||||
organization_id: org.id,
|
||||
name: org.name,
|
||||
current_email: org.email,
|
||||
email_invalid: org.email_invalid?,
|
||||
sport: org.sport,
|
||||
matched_by: "club_hint"
|
||||
}
|
||||
end
|
||||
end
|
||||
bounce.merge(crm_matches: matches)
|
||||
end
|
||||
end
|
||||
|
||||
def build_email_update_description(bounced:, new_email:, previous_org_email:, previous_contact_email:, reason: nil)
|
||||
lines = []
|
||||
lines << "Bounce / email non recapitabile: #{bounced}" if bounced.present?
|
||||
lines << "Motivo: #{reason}" if reason.present?
|
||||
lines << "Email organizzazione precedente: #{previous_org_email}" if previous_org_email.present?
|
||||
lines << "Email contatto precedente: #{previous_contact_email}" if previous_contact_email.present?
|
||||
if new_email.present?
|
||||
lines << "Nuova email impostata: #{new_email}"
|
||||
elsif bounced.present?
|
||||
lines << "Nessuna email alternativa trovata; indirizzo marcato come non valido."
|
||||
end
|
||||
lines.join("\n")
|
||||
end
|
||||
|
||||
|
||||
def with_project(code)
|
||||
project = Project.active.find_by(code: code.to_s)
|
||||
return err("Progetto non trovato", status: :not_found) if project.nil?
|
||||
@@ -230,6 +428,8 @@ module Crm
|
||||
region: org.region,
|
||||
country: org.country,
|
||||
email: org.email,
|
||||
email_invalid: org.email_invalid,
|
||||
bounced_email: org.bounced_email,
|
||||
phone: org.phone,
|
||||
website: org.website,
|
||||
lead_source: org.lead_source,
|
||||
@@ -259,6 +459,8 @@ module Crm
|
||||
full_name: contact.full_name,
|
||||
role: contact.role,
|
||||
email: contact.email,
|
||||
email_invalid: contact.email_invalid,
|
||||
bounced_email: contact.bounced_email,
|
||||
phone: contact.phone,
|
||||
mobile: contact.mobile,
|
||||
primary_contact: contact.primary_contact,
|
||||
|
||||
@@ -5,7 +5,7 @@ module Mailings
|
||||
VALUE_MODES = %w[any present blank].freeze
|
||||
|
||||
ARRAY_KEYS = %w[
|
||||
sports team_genders regions provinces streaming_statuses statuses
|
||||
sports countries team_genders regions provinces streaming_statuses statuses
|
||||
send_statuses ab_variants pipeline_stages
|
||||
exclude_received_mailing_ids exclude_opened_mailing_ids
|
||||
].freeze
|
||||
@@ -46,6 +46,7 @@ module Mailings
|
||||
def summary_parts
|
||||
parts = [Catalog.label_for(Catalog::MAILING_AUDIENCES, preset)]
|
||||
parts << "sport: #{Array(@data["sports"]).join(", ")}" if @data["sports"].present?
|
||||
parts << "nazione: #{Array(@data["countries"]).join(", ")}" if @data["countries"].present?
|
||||
parts << "M/F: #{Array(@data["team_genders"]).map { |g| Catalog.label_for(Catalog::TEAM_GENDERS, g) }.join(", ")}" if @data["team_genders"].present?
|
||||
parts << "regione: #{Array(@data["regions"]).join(", ")}" if @data["regions"].present?
|
||||
parts << "provincia: #{Array(@data["provinces"]).join(", ")}" if @data["provinces"].present?
|
||||
|
||||
@@ -44,6 +44,7 @@ module Mailings
|
||||
|
||||
def apply_organization_filters(scope)
|
||||
scope = scope.where(sport: @filters["sports"]) if @filters["sports"].present?
|
||||
scope = scope.where(country: @filters["countries"]) if @filters["countries"].present?
|
||||
scope = scope.where(team_gender: @filters["team_genders"]) if @filters["team_genders"].present?
|
||||
scope = scope.where(region: @filters["regions"]) if @filters["regions"].present?
|
||||
scope = scope.where(province: @filters["provinces"]) if @filters["provinces"].present?
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
class Mailings::HourlyThroughput
|
||||
HOUR = 1.hour
|
||||
RECENT = 15.minutes
|
||||
DEFAULT_RANGE = "12h"
|
||||
RANGES = {
|
||||
"12h" => { grain: :hour, periods: 12, short: "12h", label: "ultime 12 ore" },
|
||||
"24h" => { grain: :hour, periods: 24, short: "24h", label: "ultime 24 ore" },
|
||||
"7d" => { grain: :day, periods: 7, short: "7g", label: "ultima settimana" }
|
||||
}.freeze
|
||||
|
||||
def self.normalize_range(value)
|
||||
key = value.to_s
|
||||
RANGES.key?(key) ? key : DEFAULT_RANGE
|
||||
end
|
||||
|
||||
def initialize(project, now: Time.current, range: DEFAULT_RANGE)
|
||||
@project = project
|
||||
@now = now
|
||||
@range_key = self.class.normalize_range(range)
|
||||
end
|
||||
|
||||
attr_reader :range_key
|
||||
|
||||
def range
|
||||
RANGES.fetch(@range_key)
|
||||
end
|
||||
|
||||
def sent
|
||||
@sent ||= sent_since(HOUR)
|
||||
end
|
||||
|
||||
def recent_sent
|
||||
@recent_sent ||= sent_since(RECENT)
|
||||
end
|
||||
|
||||
def failed
|
||||
@failed ||= recipients.failed.where(updated_at: (@now - HOUR)..@now).count
|
||||
end
|
||||
|
||||
def drained
|
||||
sent + failed
|
||||
end
|
||||
|
||||
def history
|
||||
@history ||= buckets_for_range
|
||||
end
|
||||
|
||||
def history_total
|
||||
history.sum { |bucket| bucket[:count] }
|
||||
end
|
||||
|
||||
def history_max
|
||||
[history.map { |bucket| bucket[:count] }.max, 1].max
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def buckets_for_range
|
||||
from, step = history_origin
|
||||
keyed = recipients.sent
|
||||
.where(sent_at: from..@now)
|
||||
.pluck(:sent_at)
|
||||
.each_with_object(Hash.new(0)) do |sent_at, counts|
|
||||
counts[bucket_at(sent_at)] += 1
|
||||
end
|
||||
|
||||
range[:periods].times.map do |index|
|
||||
at = from + (step * index)
|
||||
{ at: at, count: keyed[at] || 0, label: bucket_label(at) }
|
||||
end
|
||||
end
|
||||
|
||||
def history_origin
|
||||
if range[:grain] == :day
|
||||
from = (@now.to_date - (range[:periods] - 1)).in_time_zone.beginning_of_day
|
||||
[from, 1.day]
|
||||
else
|
||||
from = (@now - (range[:periods] - 1).hours).beginning_of_hour
|
||||
[from, 1.hour]
|
||||
end
|
||||
end
|
||||
|
||||
def bucket_at(sent_at)
|
||||
time = sent_at.in_time_zone
|
||||
range[:grain] == :day ? time.beginning_of_day : time.beginning_of_hour
|
||||
end
|
||||
|
||||
def bucket_label(at)
|
||||
range[:grain] == :day ? at.strftime("%d/%m") : at.strftime("%H:%M")
|
||||
end
|
||||
|
||||
def sent_since(window)
|
||||
recipients.sent.where(sent_at: (@now - window)..@now).count
|
||||
end
|
||||
|
||||
def recipients
|
||||
MailingRecipient.joins(:mailing).merge(Mailing.for_project(@project))
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,242 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "net/imap"
|
||||
require "mail"
|
||||
|
||||
module Mailings
|
||||
# Legge bounce / DSN dalla casella IMAP di una MailIdentity (stesse credenziali SMTP).
|
||||
class ImapBounceReader
|
||||
BOUNCE_SUBJECT = /
|
||||
Recapito\s+fallito|
|
||||
Undelivered\s+Mail\s+Returned|
|
||||
Undeliverable|
|
||||
Delivery\s+Status|
|
||||
Mail\s+delivery\s+failed|
|
||||
Returned\s+mail
|
||||
/ix
|
||||
|
||||
EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/
|
||||
|
||||
Result = Struct.new(:ok, :identity, :bounces, :error, :scanned, keyword_init: true)
|
||||
|
||||
def initialize(identity, since: 14.days.ago, mailbox: "INBOX", limit: 200)
|
||||
@identity = identity
|
||||
@since = since
|
||||
@mailbox = mailbox
|
||||
@limit = limit
|
||||
end
|
||||
|
||||
def call
|
||||
unless @identity.imap_ready?
|
||||
return Result.new(ok: false, identity: summary, error: "IMAP non configurato o disabilitato per questo account", bounces: [], scanned: 0)
|
||||
end
|
||||
if @identity.smtp_password.blank? || @identity.imap_username.blank?
|
||||
return Result.new(ok: false, identity: summary, error: "Mancano username/password (usa le credenziali SMTP)", bounces: [], scanned: 0)
|
||||
end
|
||||
|
||||
imap = connect!
|
||||
begin
|
||||
imap.examine(@mailbox)
|
||||
ids = search_ids(imap)
|
||||
bounces = []
|
||||
ids.last(@limit).each do |id|
|
||||
raw = imap.fetch(id, "RFC822")&.first&.attr&.fetch("RFC822")
|
||||
next if raw.blank?
|
||||
|
||||
parsed = parse_message(raw, uid: id)
|
||||
bounces << parsed if parsed
|
||||
end
|
||||
|
||||
Result.new(ok: true, identity: summary, bounces: bounces, error: nil, scanned: ids.size)
|
||||
ensure
|
||||
begin
|
||||
imap.logout
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
begin
|
||||
imap.disconnect
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
Result.new(ok: false, identity: summary, error: "#{e.class}: #{e.message}", bounces: [], scanned: 0)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def summary
|
||||
{
|
||||
id: @identity.id,
|
||||
name: @identity.name,
|
||||
from_email: @identity.from_email,
|
||||
imap_host: @identity.effective_imap_host,
|
||||
imap_port: @identity.effective_imap_port
|
||||
}
|
||||
end
|
||||
|
||||
def connect!
|
||||
settings = @identity.imap_settings
|
||||
imap = Net::IMAP.new(settings[:address], port: settings[:port], ssl: settings[:ssl], open_timeout: 20)
|
||||
imap.login(settings[:user_name], settings[:password])
|
||||
imap
|
||||
end
|
||||
|
||||
def search_ids(imap)
|
||||
keys = [ "OR", "SUBJECT", "Recapito fallito", "OR", "SUBJECT", "Undelivered", "SUBJECT", "Undeliverable" ]
|
||||
begin
|
||||
since_key = [ "SINCE", Net::IMAP.format_date(@since.to_date) ]
|
||||
imap.search(since_key + keys)
|
||||
rescue Net::IMAP::BadResponseError, Net::IMAP::NoResponseError
|
||||
imap.search(keys)
|
||||
end
|
||||
end
|
||||
|
||||
def parse_message(raw, uid:)
|
||||
mail = Mail.read_from_string(raw)
|
||||
subject = mail.subject.to_s
|
||||
return nil unless bounce_like?(subject, mail)
|
||||
|
||||
body = full_text(mail)
|
||||
failed = extract_failed_emails(mail, body)
|
||||
own = own_addresses
|
||||
failed.reject! { |e| own.include?(e) || noise_email?(e) }
|
||||
return nil if failed.empty? && !bounce_like?(subject, mail)
|
||||
|
||||
{
|
||||
uid: uid,
|
||||
date: mail.date&.iso8601,
|
||||
bounce_subject: subject,
|
||||
failed_emails: failed.presence || extract_failed_fallback(body) - own.to_a,
|
||||
original_subject: extract_original_subject(mail, body, subject),
|
||||
club_hint: nil,
|
||||
reason: extract_reason(body)
|
||||
}.tap do |row|
|
||||
row[:club_hint] = club_from_subject(row[:original_subject])
|
||||
row[:failed_emails] = Array(row[:failed_emails]).uniq
|
||||
end
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
|
||||
def bounce_like?(subject, mail)
|
||||
return true if subject.match?(BOUNCE_SUBJECT)
|
||||
from = mail.from.to_a.join(" ").downcase
|
||||
from.include?("mailer-daemon") || from.include?("mail-daemon") || from.include?("postmaster")
|
||||
end
|
||||
|
||||
def full_text(mail)
|
||||
parts = []
|
||||
if mail.multipart?
|
||||
mail.parts.each { |p| parts << part_text(p) }
|
||||
else
|
||||
parts << begin
|
||||
mail.decoded.to_s
|
||||
rescue StandardError
|
||||
mail.body.to_s
|
||||
end
|
||||
end
|
||||
parts.compact.join("\n")
|
||||
end
|
||||
|
||||
def part_text(part)
|
||||
if part.multipart?
|
||||
part.parts.map { |p| part_text(p) }.join("\n")
|
||||
elsif part.content_type.to_s =~ %r{text/|message/delivery-status|message/rfc822}i
|
||||
part.decoded.to_s
|
||||
else
|
||||
""
|
||||
end
|
||||
rescue StandardError
|
||||
""
|
||||
end
|
||||
|
||||
def extract_failed_emails(mail, body)
|
||||
found = []
|
||||
body.to_s.scan(/Final-Recipient:\s*rfc822;\s*(#{EMAIL_RE.source})/i) { found << Regexp.last_match(1).downcase }
|
||||
body.to_s.scan(/Original-Recipient:\s*rfc822;\s*(#{EMAIL_RE.source})/i) { found << Regexp.last_match(1).downcase }
|
||||
body.to_s.scan(/X-Failed-Recipients:\s*(#{EMAIL_RE.source})/i) { found << Regexp.last_match(1).downcase }
|
||||
body.to_s.scan(/The mail system\s*<(#{EMAIL_RE.source})>/i) { found << Regexp.last_match(1).downcase }
|
||||
body.to_s.scan(/Invalid Recipient\s*<(#{EMAIL_RE.source})>/i) { found << Regexp.last_match(1).downcase }
|
||||
body.to_s.scan(/RCPT TO:<(#{EMAIL_RE.source})>/i) { found << Regexp.last_match(1).downcase }
|
||||
if (m = body.to_s.match(/Delivery to the following recipients failed permanently:(.*?)(?:Reason:|Reporting-MTA:)/im))
|
||||
m[1].scan(EMAIL_RE).each { |e| found << e.downcase }
|
||||
end
|
||||
found.uniq
|
||||
end
|
||||
|
||||
def extract_failed_fallback(body)
|
||||
body.to_s.scan(/<(#{EMAIL_RE.source})>/).flatten.map(&:downcase).uniq.reject { |e| noise_email?(e) }.first(3)
|
||||
end
|
||||
|
||||
def extract_original_subject(mail, body, bounce_subject)
|
||||
if bounce_subject.to_s.match?(/\AUndeliverable:\s*/i)
|
||||
return bounce_subject.sub(/\AUndeliverable:\s*/i, "").strip
|
||||
end
|
||||
if (nested = mail.parts.find { |p| p.content_type.to_s.include?("message/rfc822") })
|
||||
begin
|
||||
inner = Mail.read_from_string(nested.body.decoded)
|
||||
return inner.subject.to_s if inner.subject.present?
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
end
|
||||
if (m = body.to_s.match(/(?:^|\n)Subject:\s*(.+)/i))
|
||||
raw_subj = m[1].to_s.strip
|
||||
begin
|
||||
return Mail::Encodings.value_decode(raw_subj)
|
||||
rescue StandardError
|
||||
return raw_subj
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def club_from_subject(subject)
|
||||
return if subject.blank?
|
||||
|
||||
[
|
||||
/Pi[ùu] visibilit[àa] (?:per|alle partite di) (.+)/i,
|
||||
/Le partite di (.+?) meritano/i,
|
||||
/alle partite di (.+)$/i
|
||||
].each do |pat|
|
||||
m = subject.match(pat)
|
||||
return m[1].strip if m
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def extract_reason(body)
|
||||
[
|
||||
/Reason:\s*(.+)/i,
|
||||
/Diagnostic-Code:[^\n]+/i,
|
||||
/550[^\n]{0,160}/,
|
||||
/User unknown[^\n]{0,80}/i,
|
||||
/mailbox unavailable[^\n]{0,80}/i
|
||||
].each do |pat|
|
||||
m = body.to_s.match(pat)
|
||||
return m[0].to_s.gsub(/\s+/, " ").strip[0, 220] if m
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def own_addresses
|
||||
[
|
||||
@identity.from_email,
|
||||
@identity.reply_to,
|
||||
@identity.smtp_username,
|
||||
@identity.imap_username
|
||||
].compact.map { |e| e.to_s.downcase.strip }.to_set
|
||||
end
|
||||
|
||||
def noise_email?(email)
|
||||
email = email.to_s.downcase
|
||||
return true if email.end_with?(".mail") && email.split("@").last !~ /\./
|
||||
return true if email.match?(/\A[0-9a-f]{10,}_/)
|
||||
return true if email.include?("mailer-daemon") || email.start_with?("postmaster@")
|
||||
return true if email.end_with?("@vmbox")
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,133 @@
|
||||
class Mailings::OutboundQueue
|
||||
LOCK_PATH = Rails.root.join("tmp/outbound_smtp.lock")
|
||||
STAMP_PATH = Rails.root.join("tmp/outbound_last_sent")
|
||||
ATTEMPT_PATH = Rails.root.join("tmp/outbound_last_attempt")
|
||||
|
||||
class << self
|
||||
attr_accessor :min_interval, :fail_interval
|
||||
|
||||
def enqueue(recipient)
|
||||
recipient.with_lock do
|
||||
return false unless recipient.mailing.sending?
|
||||
return false unless recipient.pending?
|
||||
|
||||
recipient.update!(status: "queued", error_message: nil)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def drain_one!
|
||||
File.open(lock_path, File::RDWR | File::CREAT, 0o644) do |file|
|
||||
return :busy unless file.flock(File::LOCK_EX | File::LOCK_NB)
|
||||
|
||||
wait = seconds_until_next_slot
|
||||
return :wait if wait.positive?
|
||||
|
||||
recipient = next_queued
|
||||
return :empty if recipient.nil?
|
||||
return :closed unless Mailings::SendClock.new(recipient.mailing).open?
|
||||
|
||||
begin
|
||||
CampaignMailer.raise_delivery_errors = true
|
||||
outcome = recipient.deliver_queued!
|
||||
stamp_attempt!
|
||||
stamp! if outcome == :sent
|
||||
Rails.logger.info("[outbound] #{outcome} mailing=#{recipient.mailing_id} to=#{recipient.email}")
|
||||
outcome == :sent ? :sent : :deferred
|
||||
ensure
|
||||
CampaignMailer.raise_delivery_errors = false if Rails.env.development?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def exclusive
|
||||
File.open(lock_path, File::RDWR | File::CREAT, 0o644) do |file|
|
||||
file.flock(File::LOCK_EX)
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def next_queued
|
||||
# Sceglie il destinatario piu vecchio (per priorita/updated_at) TRA le mailing
|
||||
# la cui finestra di invio e attualmente aperta. Prima si sceglieva il piu vecchio
|
||||
# in assoluto: se apparteneva a una mailing con finestra chiusa, drain_one!
|
||||
# restituiva :closed e si fermava, bloccando l'intera coda condivisa anche per
|
||||
# le mailing senza vincoli orari (bug: coda bloccata da mailing con orario chiuso).
|
||||
queued_scope.order(
|
||||
Arel.sql(
|
||||
"CASE WHEN mailing_recipients.error_message IS NULL OR mailing_recipients.error_message = '' THEN 0 ELSE 1 END, mailing_recipients.updated_at ASC, mailing_recipients.id ASC"
|
||||
)
|
||||
).includes(:mailing).find { |r| Mailings::SendClock.new(r.mailing).open? }
|
||||
end
|
||||
|
||||
def queued_scope
|
||||
MailingRecipient.queued.joins(:mailing).merge(Mailing.sending)
|
||||
end
|
||||
|
||||
def seconds_until_next_slot
|
||||
[
|
||||
remaining(last_sent_at, min_interval),
|
||||
remaining(last_attempt_at, fail_interval)
|
||||
].max
|
||||
end
|
||||
|
||||
def reset!
|
||||
@last_sent_at = nil
|
||||
@last_attempt_at = nil
|
||||
File.delete(stamp_path) if File.exist?(stamp_path)
|
||||
File.delete(attempt_path) if File.exist?(attempt_path)
|
||||
rescue Errno::ENOENT
|
||||
nil
|
||||
end
|
||||
|
||||
def stamp!
|
||||
@last_sent_at = Time.current
|
||||
File.write(stamp_path, @last_sent_at.to_f.to_s)
|
||||
end
|
||||
|
||||
def stamp_attempt!
|
||||
@last_attempt_at = Time.current
|
||||
File.write(attempt_path, @last_attempt_at.to_f.to_s)
|
||||
end
|
||||
|
||||
def last_sent_at
|
||||
@last_sent_at ||= read_time(stamp_path)
|
||||
end
|
||||
|
||||
def last_attempt_at
|
||||
@last_attempt_at ||= read_time(attempt_path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remaining(timestamp, interval)
|
||||
return 0 if interval.to_f <= 0 || timestamp.nil?
|
||||
|
||||
leftover = interval - (Time.current - timestamp)
|
||||
leftover.positive? ? leftover : 0
|
||||
end
|
||||
|
||||
def read_time(path)
|
||||
return unless File.exist?(path)
|
||||
|
||||
Time.zone.at(Float(File.read(path)))
|
||||
rescue ArgumentError, TypeError, Errno::ENOENT
|
||||
nil
|
||||
end
|
||||
|
||||
def lock_path
|
||||
LOCK_PATH
|
||||
end
|
||||
|
||||
def stamp_path
|
||||
STAMP_PATH
|
||||
end
|
||||
|
||||
def attempt_path
|
||||
ATTEMPT_PATH
|
||||
end
|
||||
end
|
||||
|
||||
self.min_interval = Rails.env.test? ? 0.0 : 60.0
|
||||
self.fail_interval = Rails.env.test? ? 0.0 : 30.0
|
||||
end
|
||||
@@ -19,6 +19,9 @@ class Mailings::RecipientBuilder
|
||||
elsif !email.match?(URI::MailTo::EMAIL_REGEXP)
|
||||
attrs[:status] = "skipped"
|
||||
attrs[:skip_reason] = "email non valida"
|
||||
elsif email_marked_invalid?(org, contact, email)
|
||||
attrs[:status] = "skipped"
|
||||
attrs[:skip_reason] = "email bounce / non valida"
|
||||
else
|
||||
attrs[:status] = "pending"
|
||||
end
|
||||
@@ -54,4 +57,17 @@ class Mailings::RecipientBuilder
|
||||
end
|
||||
attrs[:ab_variant] = variant
|
||||
end
|
||||
|
||||
def email_marked_invalid?(org, contact, email)
|
||||
email = email.to_s.downcase
|
||||
if contact&.email.to_s.downcase == email && contact.email_invalid?
|
||||
true
|
||||
elsif org.email.to_s.downcase == email && org.email_invalid?
|
||||
true
|
||||
elsif contact&.bounced_email.to_s.downcase == email || org.bounced_email.to_s.downcase == email
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
require "net/smtp"
|
||||
require "openssl"
|
||||
|
||||
class Mailings::SmtpGate
|
||||
MUTEX = Mutex.new
|
||||
RETRYABLE = [
|
||||
EOFError,
|
||||
IOError,
|
||||
Errno::ECONNRESET,
|
||||
Errno::EPIPE,
|
||||
Errno::ETIMEDOUT,
|
||||
Net::OpenTimeout,
|
||||
Net::ReadTimeout,
|
||||
OpenSSL::SSL::SSLError,
|
||||
Net::SMTPServerBusy
|
||||
].freeze
|
||||
|
||||
class << self
|
||||
attr_accessor :min_gap, :max_attempts, :backoff_base
|
||||
|
||||
def deliver
|
||||
MUTEX.synchronize { deliver_locked { yield } }
|
||||
end
|
||||
|
||||
def reset!
|
||||
MUTEX.synchronize { @last_monotonic = nil }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def deliver_locked
|
||||
attempts = 0
|
||||
begin
|
||||
wait_min_gap
|
||||
result = yield
|
||||
stamp!
|
||||
result
|
||||
rescue *RETRYABLE => e
|
||||
attempts += 1
|
||||
stamp!
|
||||
Rails.logger.warn("[smtp-gate] #{e.class}: #{e.message} attempt=#{attempts}/#{max_attempts}")
|
||||
raise if attempts >= max_attempts
|
||||
|
||||
sleep_wait(backoff_base * (2**(attempts - 1)))
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
def wait_min_gap
|
||||
return if min_gap.to_f <= 0 || @last_monotonic.nil?
|
||||
|
||||
elapsed = now - @last_monotonic
|
||||
remaining = min_gap - elapsed
|
||||
sleep_wait(remaining) if remaining.positive?
|
||||
end
|
||||
|
||||
def stamp!
|
||||
@last_monotonic = now
|
||||
end
|
||||
|
||||
def now
|
||||
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
end
|
||||
|
||||
def sleep_wait(seconds)
|
||||
sleep(seconds) if seconds.to_f.positive?
|
||||
end
|
||||
end
|
||||
|
||||
self.min_gap = 0.0
|
||||
self.max_attempts = 1
|
||||
self.backoff_base = 0.0
|
||||
end
|
||||
@@ -23,6 +23,14 @@
|
||||
<label class="block text-sm font-medium">Email</label>
|
||||
<%= f.email_field :email, placeholder: "Email", class: input_class %>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<label class="block text-sm font-medium">Email bounce</label>
|
||||
<%= f.email_field :bounced_email, placeholder: "Indirizzo non recapitabile", class: input_class %>
|
||||
</div>
|
||||
<label class="flex min-h-11 items-center gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700 sm:col-span-2">
|
||||
<%= f.check_box :email_invalid, class: "size-5" %>
|
||||
Email non valida / bounce
|
||||
</label>
|
||||
<div class="space-y-1">
|
||||
<label class="block text-sm font-medium">Telefono</label>
|
||||
<%= f.text_field :phone, placeholder: "Telefono", class: input_class %>
|
||||
|
||||
@@ -42,7 +42,24 @@
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">Password SMTP<%= " (lascia vuoto per non cambiare)" unless mail_identity.new_record? %></label>
|
||||
<%= f.password_field :smtp_password, autocomplete: "new-password", class: input_class %>
|
||||
<p class="mt-1 text-xs text-zinc-500">Stessa password usata anche per IMAP (lettura bounce).</p>
|
||||
</div>
|
||||
<div class="md:col-span-2 border-t border-zinc-100 pt-4 dark:border-zinc-800">
|
||||
<h2 class="text-sm font-semibold">IMAP (lettura casella / bounce)</h2>
|
||||
<p class="mt-1 text-xs text-zinc-500">Di solito host e porta si compilano da soli in base all’SMTP (es. Aruba → imaps.aruba.it:993).</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">Host IMAP</label>
|
||||
<%= f.text_field :imap_host, placeholder: mail_identity.inferred_imap_host || "imaps.aruba.it", class: input_class %>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">Porta IMAP</label>
|
||||
<%= f.number_field :imap_port, min: 1, max: 65535, class: input_class %>
|
||||
</div>
|
||||
<label class="flex min-h-11 items-center gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700 md:col-span-2">
|
||||
<%= f.check_box :imap_enabled, class: "size-5" %>
|
||||
Abilita lettura IMAP (bounce) con le stesse credenziali SMTP
|
||||
</label>
|
||||
<label class="flex min-h-11 items-center gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700">
|
||||
<%= f.check_box :verify_ssl, class: "size-5" %>
|
||||
Verifica il certificato SSL del server
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-wrap items-end justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-xl font-semibold tracking-tight md:text-2xl">Account email / SMTP</h1>
|
||||
<h1 class="text-xl font-semibold tracking-tight md:text-2xl">Account email / SMTP + IMAP</h1>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
<%= link_to "Impostazioni", admin_path, class: "hover:underline" %>
|
||||
· da questi indirizzi partono le campagne. Le password SMTP sono cifrate.
|
||||
· SMTP per gli invii, IMAP (stesse credenziali) per leggere i bounce. Password cifrate.
|
||||
</p>
|
||||
</div>
|
||||
<%= link_to "Nuovo account", new_mail_identity_path, class: "#{btn_primary} w-full sm:w-auto" %>
|
||||
@@ -26,6 +26,14 @@
|
||||
<%= link_to "Modifica", edit_mail_identity_path(identity), class: "text-sm text-zinc-700 hover:underline dark:text-zinc-300" %>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-zinc-600 dark:text-zinc-300"><%= identity.smtp_host %>:<%= identity.smtp_port %> · <%= Catalog.label_for(Catalog::MAIL_ENCRYPTIONS, identity.encryption) %></div>
|
||||
<div class="mt-1 text-sm text-zinc-600 dark:text-zinc-300">
|
||||
IMAP:
|
||||
<% if identity.imap_ready? %>
|
||||
<%= identity.effective_imap_host %>:<%= identity.effective_imap_port %>
|
||||
<% else %>
|
||||
off
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="mt-1 text-sm text-zinc-500"><%= identity.active? ? "Attivo" : "Disattivo" %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -38,6 +46,7 @@
|
||||
<th class="px-4 py-3">Nome</th>
|
||||
<th class="px-4 py-3">Mittente</th>
|
||||
<th class="px-4 py-3">SMTP</th>
|
||||
<th class="px-4 py-3">IMAP</th>
|
||||
<th class="px-4 py-3">Stato</th>
|
||||
<th class="px-4 py-3"></th>
|
||||
</tr>
|
||||
@@ -54,6 +63,13 @@
|
||||
<%= identity.smtp_host %>:<%= identity.smtp_port %>
|
||||
<div class="text-xs text-zinc-500"><%= Catalog.label_for(Catalog::MAIL_ENCRYPTIONS, identity.encryption) %></div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-zinc-600 dark:text-zinc-300">
|
||||
<% if identity.imap_ready? %>
|
||||
<%= identity.effective_imap_host %>:<%= identity.effective_imap_port %>
|
||||
<% else %>
|
||||
<span class="text-zinc-400">off</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="px-4 py-3"><%= identity.active? ? "Attivo" : "Disattivo" %></td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<%= link_to "Modifica", edit_mail_identity_path(identity), class: "text-zinc-700 hover:underline dark:text-zinc-300" %>
|
||||
|
||||
@@ -31,11 +31,9 @@
|
||||
<% elsif mailing.pending_count.positive? %>
|
||||
<p class="mt-3 text-xs text-zinc-500">Prossima email in lavorazione.</p>
|
||||
<% end %>
|
||||
<% if mailing.sending? %>
|
||||
<% if mailing.sending? || mailing.paused? %>
|
||||
<div class="mt-4 border-t border-zinc-100 pt-4 dark:border-zinc-800">
|
||||
<%= button_to "Ferma invio", cancel_mailing_path(mailing), method: :post,
|
||||
class: btn_danger,
|
||||
form: { data: { turbo_confirm: "Fermare l'invio? Restano inviate #{mailing.sent_count} email, #{mailing.pending_count + mailing.queued_count} in coda verranno annullate." } } %>
|
||||
<%= render "mailings/send_controls", mailing: mailing %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<% if mailing.sending? %>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= button_to "Metti in pausa", pause_mailing_path(mailing), method: :post,
|
||||
class: btn_secondary,
|
||||
form: { data: { turbo_confirm: "Mettere in pausa? Le #{mailing.sent_count} già inviate restano. Le #{mailing.pending_count + mailing.queued_count} non ancora partite si riprendono da qui." } } %>
|
||||
<%= button_to "Annulla definitivamente", cancel_mailing_path(mailing), method: :post,
|
||||
class: btn_danger,
|
||||
form: { data: { turbo_confirm: "Annullare l'invio? Le #{mailing.sent_count} già inviate restano, le #{mailing.pending_count + mailing.queued_count} in coda verranno saltate e non si potranno riprendere." } } %>
|
||||
</div>
|
||||
<% elsif mailing.paused? %>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= button_to "Riprendi invio", resume_mailing_path(mailing), method: :post, class: btn_primary %>
|
||||
<%= button_to "Annulla definitivamente", cancel_mailing_path(mailing), method: :post,
|
||||
class: btn_danger,
|
||||
form: { data: { turbo_confirm: "Annullare l'invio? Le #{mailing.sent_count} già inviate restano, le #{mailing.pending_count + mailing.queued_count} in coda verranno saltate e non si potranno riprendere." } } %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -29,7 +29,7 @@
|
||||
<section class="<%= card_class %> space-y-4 p-4 md:p-6">
|
||||
<div>
|
||||
<h2 class="text-base font-semibold">Chi deve ricevere questa email?</h2>
|
||||
<p class="mt-1 text-sm text-zinc-500">Lista di partenza, sport, regione e chi non ha mai ricevuto una email. Il resto è opzionale.</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">Lista di partenza, sport, nazione, regione e chi non ha mai ricevuto una email. Il resto è opzionale.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -47,12 +47,17 @@
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">Sport</label>
|
||||
<%= render "mailings/audience_choices", name: "filters[sports]",
|
||||
choices: @sport_options.map { |sport| [sport, sport] }, selected: @filters["sports"] %>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">Nazione</label>
|
||||
<%= render "mailings/audience_choices", name: "filters[countries]",
|
||||
choices: @country_options.map { |country| [country, country] }, selected: @filters["countries"] %>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-medium">Regione</label>
|
||||
<%= render "mailings/audience_choices", name: "filters[regions]",
|
||||
|
||||
@@ -26,11 +26,44 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3 xl:grid-cols-4">
|
||||
<div class="grid grid-cols-2 gap-3 md:grid-cols-3">
|
||||
<div class="<%= card_class %> p-4">
|
||||
<div class="text-xs uppercase tracking-wide text-zinc-500">In corso</div>
|
||||
<div class="mt-1 text-2xl font-semibold tabular-nums"><%= @active_mailings.size %></div>
|
||||
</div>
|
||||
<div class="<%= card_class %> p-4">
|
||||
<div class="text-xs uppercase tracking-wide text-zinc-500">Mail / ora</div>
|
||||
<div class="mt-1 text-2xl font-semibold tabular-nums"><%= @hourly_throughput.sent %></div>
|
||||
<div class="mt-1 text-xs text-zinc-500">
|
||||
<%= @hourly_throughput.recent_sent %> ultimi 15 min
|
||||
<% if @hourly_throughput.failed.positive? %>
|
||||
· <%= @hourly_throughput.failed %> scodate in errore
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="<%= card_class %> p-4">
|
||||
<div class="flex items-center justify-between gap-1">
|
||||
<div class="text-xs uppercase tracking-wide text-zinc-500">Storico invio</div>
|
||||
<div class="inline-flex items-center gap-1">
|
||||
<% Mailings::HourlyThroughput::RANGES.each do |key, config| %>
|
||||
<% if @hourly_throughput.range_key == key %>
|
||||
<span class="rounded-md bg-zinc-900 px-1.5 py-0.5 text-[10px] font-medium leading-none text-white dark:bg-zinc-100 dark:text-zinc-900"><%= config[:short] %></span>
|
||||
<% else %>
|
||||
<%= link_to config[:short], dashboard_mailings_path(history: key), class: "rounded-md px-1.5 py-0.5 text-[10px] font-medium leading-none text-zinc-500 hover:bg-zinc-100 dark:text-zinc-400 dark:hover:bg-zinc-800" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flex items-end gap-0.5" style="height: 2.5rem" role="img" aria-label="Invii confermati, <%= @hourly_throughput.range[:label] %>">
|
||||
<% @hourly_throughput.history.each do |bucket| %>
|
||||
<% pct = (bucket[:count].to_f / @hourly_throughput.history_max * 100).round %>
|
||||
<div class="flex-1 rounded <%= bucket[:count].positive? ? "bg-emerald-500" : "bg-zinc-200 dark:bg-zinc-700" %>"
|
||||
style="height: <%= bucket[:count].positive? ? [pct, 12].max : 8 %>%"
|
||||
title="<%= bucket[:label] %>: <%= bucket[:count] %>"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-zinc-500"><%= @hourly_throughput.history_total %> inviate · <%= @hourly_throughput.range[:label] %></div>
|
||||
</div>
|
||||
<div class="<%= card_class %> p-4">
|
||||
<div class="text-xs uppercase tracking-wide text-zinc-500">In pausa (fascia)</div>
|
||||
<div class="mt-1 text-2xl font-semibold tabular-nums"><%= @paused_mailings.size %></div>
|
||||
@@ -77,6 +110,8 @@
|
||||
<%= mailing.sent_count %> inviate · <%= mailing.opened_count %> aperte · <%= mailing.pending_count %> in coda
|
||||
<% if mailing.sending? && mailing.next_send_at.present? && mailing.next_send_at > Time.current %>
|
||||
· Riparte <%= l(mailing.next_send_at, format: :short) %>
|
||||
<% elsif mailing.paused? %>
|
||||
· In pausa, si può riprendere
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -34,15 +34,13 @@
|
||||
<%= link_to "Raffina destinatari", audience_mailing_path(@mailing), class: btn_secondary %>
|
||||
<%= button_to "Rigenera lista", refresh_recipients_mailing_path(@mailing), method: :post, class: btn_secondary %>
|
||||
<%= render "mailings/delete_draft_button", mailing: @mailing, extra_class: "flex-1 sm:flex-none" %>
|
||||
<% elsif @mailing.sending? %>
|
||||
<%= button_to "Ferma invio", cancel_mailing_path(@mailing), method: :post,
|
||||
class: btn_danger,
|
||||
form: { data: { turbo_confirm: "Fermare l'invio? Le #{@mailing.sent_count} già inviate restano, #{@mailing.pending_count + @mailing.queued_count} in coda verranno annullate." } } %>
|
||||
<% elsif @mailing.sending? || @mailing.paused? %>
|
||||
<%= render "mailings/send_controls", mailing: @mailing %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if @mailing.sending? || @mailing.sent? %>
|
||||
<% if @mailing.sending? || @mailing.paused? || @mailing.sent? %>
|
||||
<%= render "mailings/progress_card", mailing: @mailing %>
|
||||
<% end %>
|
||||
|
||||
@@ -65,7 +63,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if @mailing.sending? || @mailing.sent? || @mailing.cancelled? %>
|
||||
<% if @mailing.sending? || @mailing.paused? || @mailing.sent? || @mailing.cancelled? %>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="<%= card_class %> p-4">
|
||||
<div class="text-xs uppercase tracking-wide text-zinc-500">Aperte</div>
|
||||
@@ -179,9 +177,7 @@
|
||||
<% if recipient.skip_reason.present? && !recipient.pending? %>
|
||||
<div class="mt-1 text-xs text-zinc-500"><%= recipient.skip_reason %></div>
|
||||
<% end %>
|
||||
<% if recipient.error_message.present? %>
|
||||
<div class="mt-1 text-xs text-rose-700"><%= recipient.error_message %></div>
|
||||
<% end %>
|
||||
<%= mailing_recipient_delivery_note(recipient) %>
|
||||
<% if recipient.sent_at.present? %>
|
||||
<div class="mt-1 text-xs text-zinc-500">
|
||||
Inviata <%= format_dt(recipient.sent_at) %>
|
||||
@@ -238,9 +234,7 @@
|
||||
<% if recipient.skip_reason.present? && !recipient.pending? %>
|
||||
<div class="text-xs text-zinc-500"><%= recipient.skip_reason %></div>
|
||||
<% end %>
|
||||
<% if recipient.error_message.present? %>
|
||||
<div class="text-xs text-rose-700"><%= recipient.error_message %></div>
|
||||
<% end %>
|
||||
<%= mailing_recipient_delivery_note(recipient) %>
|
||||
<% if recipient.sent_at.present? %>
|
||||
<div class="text-xs text-zinc-500">Inviata <%= format_dt(recipient.sent_at) %></div>
|
||||
<% end %>
|
||||
|
||||
@@ -34,6 +34,13 @@
|
||||
<div class="sm:col-span-2"><%= f.label :source_url, class: "mb-1 block text-sm font-medium" %><%= f.text_field :source_url, class: input_class %></div>
|
||||
<div><%= f.label :phone, class: "mb-1 block text-sm font-medium" %><%= f.text_field :phone, class: input_class %></div>
|
||||
<div><%= f.label :email, class: "mb-1 block text-sm font-medium" %><%= f.email_field :email, class: input_class %></div>
|
||||
<div class="flex items-end gap-2 pb-1">
|
||||
<label class="inline-flex items-center gap-2 text-sm">
|
||||
<%= f.check_box :email_invalid, class: "rounded border-zinc-300" %>
|
||||
Email non valida / bounce
|
||||
</label>
|
||||
</div>
|
||||
<div><%= f.label :bounced_email, "Email bounce", class: "mb-1 block text-sm font-medium" %><%= f.email_field :bounced_email, class: input_class %></div>
|
||||
<div><%= f.label :vat_number, class: "mb-1 block text-sm font-medium" %><%= f.text_field :vat_number, class: input_class %></div>
|
||||
<div><%= f.label :verified_at, class: "mb-1 block text-sm font-medium" %><%= f.date_field :verified_at, class: input_class %></div>
|
||||
<div class="sm:col-span-2"><%= f.label :commercial_fit, class: "mb-1 block text-sm font-medium" %><%= f.text_area :commercial_fit, rows: 3, class: input_class %></div>
|
||||
|
||||
@@ -74,7 +74,18 @@
|
||||
<div><dt class="text-slate-500">Sport</dt><dd class="font-medium"><%= [@organization.sport.presence, @organization.team_gender_label.presence].compact.join(" · ").presence || "—" %></dd></div>
|
||||
<div><dt class="text-slate-500">Località</dt><dd class="font-medium"><%= [@organization.city, @organization.province, @organization.region, @organization.country].compact_blank.join(", ").presence || "—" %></dd></div>
|
||||
<div><dt class="text-slate-500">Lista campagna</dt><dd class="font-medium"><%= @organization.list_position || "—" %></dd></div>
|
||||
<div><dt class="text-slate-500">Email</dt><dd class="font-medium break-anywhere"><%= mailto_link(@organization.email, class: "text-sky-700 hover:underline dark:text-sky-400") %></dd></div>
|
||||
<div>
|
||||
<dt class="text-slate-500">Email</dt>
|
||||
<dd class="font-medium break-anywhere">
|
||||
<%= mailto_link(@organization.email, class: "text-sky-700 hover:underline dark:text-sky-400") %>
|
||||
<% if @organization.email_invalid? %>
|
||||
<span class="ml-1 rounded bg-rose-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-rose-800 dark:bg-rose-950 dark:text-rose-200">Non valida</span>
|
||||
<% end %>
|
||||
<% if @organization.bounced_email.present? && @organization.bounced_email != @organization.email %>
|
||||
<div class="mt-1 text-xs text-rose-600 dark:text-rose-300">Bounce: <%= @organization.bounced_email %></div>
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
<div><dt class="text-slate-500">Telefono</dt><dd class="font-medium"><%= tel_link(@organization.phone, class: "text-sky-700 hover:underline dark:text-sky-400") %></dd></div>
|
||||
<div><dt class="text-slate-500">Sito / profilo</dt><dd class="font-medium"><%= external_link(@organization.website, class: "text-sky-700 hover:underline dark:text-sky-400") %></dd></div>
|
||||
<div><dt class="text-slate-500">Fonte ricerca</dt><dd class="font-medium"><%= external_link(@organization.source_url, class: "text-sky-700 hover:underline dark:text-sky-400") %></dd></div>
|
||||
@@ -98,7 +109,12 @@
|
||||
<%= contact.full_name %>
|
||||
<% if contact.primary_contact? %><span class="ml-1 rounded bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200">Principale</span><% end %>
|
||||
</div>
|
||||
<div class="break-anywhere text-sm text-slate-500"><%= [contact.role, contact.email, contact.phone.presence || contact.mobile].compact_blank.join(" · ") %></div>
|
||||
<div class="break-anywhere text-sm text-slate-500">
|
||||
<%= [contact.role, contact.email, contact.phone.presence || contact.mobile].compact_blank.join(" · ") %>
|
||||
<% if contact.email_invalid? %>
|
||||
<span class="ml-1 rounded bg-rose-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-rose-800 dark:bg-rose-950 dark:text-rose-200">Email non valida</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%= link_to "Modifica", edit_contact_path(contact), class: "text-sm text-zinc-600 hover:underline dark:text-zinc-300" %>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# o dopo un riavvio del processo. L'adapter :async non persiste i job.
|
||||
Rails.application.config.after_initialize do
|
||||
next if Rails.env.test?
|
||||
next unless defined?(Rails::Server) || ENV["MAILING_POLLER"] == "true"
|
||||
next unless defined?(Rails::Server)
|
||||
|
||||
Thread.new do
|
||||
Rails.logger.info("[mailing-poller] avviato")
|
||||
|
||||
@@ -33,6 +33,8 @@ Rails.application.routes.draw do
|
||||
get "today", to: "today#show"
|
||||
get "search", to: "search#show"
|
||||
get "organizations/:id", to: "organizations#show", as: :organization
|
||||
patch "organizations/:organization_id/email", to: "organization_emails#update", as: :organization_email
|
||||
get "mail_bounces", to: "mail_bounces#index", as: :mail_bounces
|
||||
post "tasks", to: "tasks#create"
|
||||
post "tasks/:id/complete", to: "tasks#complete", as: :complete_task
|
||||
post "activities", to: "activities#create"
|
||||
@@ -74,6 +76,8 @@ Rails.application.routes.draw do
|
||||
end
|
||||
member do
|
||||
post :queue
|
||||
post :pause
|
||||
post :resume
|
||||
post :cancel
|
||||
post :test_send
|
||||
get :test_preview
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
N.,Regione,Prov.,Società,M/F,Email verificata,Sito / profilo,Streaming rilevato,Evidenza / fit commerciale,Test A/B,Stato invio,Data invio,Esito,Demo / Trial,Conversione,Piano,Fonte contatto / ricerca,Data verifica,Note follow-up
|
||||
2001,Lombardia,MI,Olimpia Milano,M,olimpia@olimpiamilano.com,https://olimpiamilano.com,SÌ,Club con il maggior numero di tifosi e uno dei settori giovanili più importanti d'Italia. Seguito nazionale e internazionale. Ottimo per sponsorizzazioni.,A,DA INVIARE,,,NO,NO,,https://olimpiamilano.com,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2002,Emilia-Romagna,BO,Virtus Bologna,M,office@virtus.it,https://virtus.it,SÌ,Club storico con grandissimo seguito e settore giovanile di vertice. Palazzetto sempre pieno.,B,DA INVIARE,,,NO,NO,,https://virtus.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2003,Emilia-Romagna,RE,Pallacanestro Reggiana,M,reggiana@pallacanestroreggiana.it,https://pallacanestroreggiana.it,SÌ,Società con buon seguito e accademia giovanile strutturata (Pallacanestro Reggiana Academy).,A,DA INVIARE,,,NO,NO,,https://pallacanestroreggiana.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2004,Veneto,VE,Reyer Venezia,M,info@reyer.it,https://reyer.it,SÌ,Club con seguito storico e solido settore giovanile (Minibasket Reyer). Finalista Scudetto 2025/26.,B,DA INVIARE,,,NO,NO,,https://reyer.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2005,Piemonte,AL,Derthona Basket,M,segreteria@derthonabasket.it,https://derthonabasket.it,LIMITATO,"Sito ufficiale curato con sezione ""Match Room""/app dedicata ma nessuno streaming live strutturato: forte candidato commerciale",A,DA INVIARE,,,NO,NO,,https://www.derthonabasket.it/contatti/,2026-09-07,Duplicato consolidato da denominazioni alternative: Derthona Basket; Derthona Basket (Tortona) | Verifica web 07/09/2026: contatto di segreteria pubblicato sul sito ufficiale; sostituito il precedente info@.
|
||||
2006,Campania,NA,Napoli Basket,M,segreteria@napolibasketball.com,https://napolibasketball.com,SÌ,"Club di Serie A a Napoli, grande bacino di tifosi, partite di alto livello, target primario per streaming",B,DA INVIARE,,,NO,NO,,https://www.napolibasketball.com/contactus,2026-09-07,Duplicato consolidato da denominazioni alternative: Napoli Basket; Napoli Basketball (Guerri Napoli Basketball) | Verifica web 07/09/2026: scelta la segreteria generale pubblicata sul sito ufficiale per il primo contatto.
|
||||
2007,Friuli-Venezia Giulia,TS,Pallacanestro Trieste,M,info@pallacanestrotrieste.it,https://pallacanestrotrieste.it,LIMITATO,Club storico con seguito fedele e settore giovanile attivo.,A,DA INVIARE,,,NO,NO,,https://pallacanestrotrieste.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2008,Lombardia,VA,Pallacanestro Varese,M,info@pallacanestrovarese.it,https://pallacanestrovarese.it,SÌ,Club con grandissimo seguito storico e progetto ambizioso sul settore giovanile. Luis Scola punta sui giovani.,B,DA INVIARE,,,NO,NO,,https://pallacanestrovarese.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2009,Trentino-Alto Adige,TN,Aquila Trento,M,info@aquilabasket.it,https://aquilabasket.it,SÌ,Club con seguito solido e settore giovanile di alto livello. Partecipa a competizioni europee.,A,DA INVIARE,,,NO,NO,,https://aquilabasket.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2010,Lombardia,CO,Pallacanestro Cantù,M,info@pallacanestrocantu.com,https://pallacanestrocantu.it,SÌ,Club storico con grandissimo seguito e vivaio tra i più importanti d'Italia. Codice FIP storico.,B,DA INVIARE,,,NO,NO,,https://pallacanestrocantu.com/organigramma/,07/09/2026,Verifica web 07/09/2026: email aggiornata e confermata sul sito ufficiale Pallacanestro Cantù.
|
||||
2011,Lazio,RM,Roma SPQR,M,info@romaspqr.it,,NON RILEVATO,Nuova realtà romana con ambizioso progetto. Potenziale per costruire seguito e settore giovanile.,A,DA INVIARE,,,NO,NO,,https://vrijetijd.brugge.be,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2012,Lazio,RM,Maxima Roma,M,segreteria@maximaromabasketball.it,,NON RILEVATO,Nuova realtà romana. Potenziale per sviluppo di seguito e settore giovanile.,B,DA INVIARE,,,NO,NO,,https://www.maximaromabasketball.it/,07/09/2026,Verifica web 07/09/2026: sostituito il precedente indirizzo con la segreteria pubblicata sul sito ufficiale.
|
||||
2013,Campania,SA,Scafati Basket,M,scafatibasket1969@gmail.com,https://www.scafatibasket.com,SÌ,"Club di Serie A, partite su DAZN/Eurosport, pubblico ampio per contenuti streaming complementari",A,DA INVIARE,,,NO,NO,,https://www.scafatibasket.com/contatti/,07/09/2026,Duplicato consolidato da denominazioni alternative: Scafati Basket; Scafati Basket (Givova Scafati) | Verifica web 07/09/2026: sostituito il precedente indirizzo con il contatto pubblicato nella pagina ufficiale Contatti.
|
||||
2014,Veneto,VR,Scaligera Verona,M,info@scaligerabasket.it,,NON RILEVATO,Club storico con seguito fedele e progetto giovanile. Neo-promossa in Serie A.,B,DA INVIARE,,,NO,NO,,https://www.scaligerabasket.it/,07/09/2026,Verifica web 07/09/2026: indirizzo confermato sul sito ufficiale Scaligera Basket.
|
||||
2015,Veneto,TV,Universo Treviso,M,info@trevisobasket.it,,NON RILEVATO,Società con seguito e tradizione. Settore giovanile attivo.,A,DA INVIARE,,,NO,NO,,https://trevisobasket.it/,07/09/2026,Verifica web 07/09/2026: sostituito il precedente dominio con l'indirizzo ufficiale corrente di Universo Treviso Basket.
|
||||
2016,Friuli-Venezia Giulia,UD,Amici Pallacanestro Udinese,M,info@apudine.it,https://www.scuolabasket.it/scuole/scheda/Udine-UDINE/AMICI-PALL-UDINESE-SSD-ARL/1801,NON RILEVATO,Club con seguito storico e progetto giovanile. Presente in Serie A.,B,DA INVIARE,,,NO,NO,,https://vrijetijd.brugge.be,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Email pubblica non reperita: verificare telefono/social/form prima dell'invio email diretta. | Contatto email non strutturato nel file origine: vedi indirizzo sede (email non reperita) | Duplicato consolidato da denominazioni alternative: Amici Pallacanestro Udinese; Amici Pallacanestro Udinese SSD | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2017,Campania,AV,Avellino Basket,M,info@avellinobasket.it,https://www.avellinobasket.it/avellino_basket_school,NON RILEVATO,"Scuola giovanile della società di Avellino, percorso completo Minibasket-Under 19",A,DA INVIARE,,,NO,NO,,https://www.avellinobasket.it/,07/09/2026,Duplicato consolidato da denominazioni alternative: Avellino Basket; Halley Campania Avellino; Avellino Basket School (Avellino Basket) | Verifica web 07/09/2026: indirizzo confermato sul sito ufficiale Avellino Basket.
|
||||
2018,Emilia-Romagna,BO,Fortitudo Bologna,M,info@fortitudo.it,,NON RILEVATO,Club storico con grandissimo seguito e vivaio di eccellenza. Serie A2.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2019,Puglia,BR,New Basket Brindisi,M,sede@newbasketbrindisi.it,https://www.newbasketbrindisi.it,SÌ,"Storicamente la società più quotata di Puglia, Serie A2, grande seguito di pubblico",A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Duplicato consolidato da denominazioni alternative: New Basket Brindisi; Valtur Brindisi; New Basket Brindisi (Valtur Brindisi) | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2020,Campania,CE,JuveCaserta,M,info@juvecaserta.it,,NON RILEVATO,Club storico con grandissimo seguito e tradizione giovanile. Serie A2.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2021,Emilia-Romagna,FE,Cento,M,info@centobasket.it,https://www.scuolabasket.it/scuole/scheda/Ferrara-CENTO/ASD-PALLACANESTRO-CENTO/1309,NON RILEVATO,Società con seguito e settore giovanile in crescita. Serie A2.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Email pubblica non reperita: verificare telefono/social/form prima dell'invio email diretta. | Contatto email non strutturato nel file origine: tel. 336/368610 (email non reperita) | Duplicato consolidato da denominazioni alternative: Cento; A.S.D. Pallacanestro Cento | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2022,Friuli-Venezia Giulia,UD,Cividale,M,info@cividalebasket.it,,NON RILEVATO,Club con seguito e progetto giovanile. Serie A2.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2023,Lombardia,CR,Juvi Cremona,M,segreteria@juvicremona1952.it,,NON RILEVATO,Società con seguito e settore giovanile di qualità. Serie A2.,A,DA INVIARE,,,NO,NO,,https://www.juvicremona1952.it/,07/09/2026,Verifica web 07/09/2026: sostituito il precedente indirizzo con la segreteria ufficiale corrente.
|
||||
2024,Emilia-Romagna,FC,Forlì,M,info@pallacanestroforli2015.it,,NON RILEVATO,Club con seguito e tradizione. Settore giovanile attivo. Serie A2.,B,DA INVIARE,,,NO,NO,,https://www.pallacanestroforli2015.it/sede-contatti/,07/09/2026,Verifica web 07/09/2026: sostituito il precedente indirizzo con quello pubblicato nella pagina ufficiale Sede e Contatti.
|
||||
2025,Toscana,LI,Libertas Livorno,M,info@libertaslivorno1947.it,https://www.libertaslivorno1947.it/,SÌ,"Prima squadra femminile in Serie A1, ticketing e streaming partite strutturati",A,DA INVIARE,,,NO,NO,,https://libertaslivorno1947.it/,07/09/2026,Duplicato consolidato da denominazioni alternative: Libertas Livorno; Libertas Livorno 1947 | Verifica web 07/09/2026: sostituito il precedente dominio con l'indirizzo ufficiale corrente.
|
||||
2026,Veneto,VE,Mestre,M,info@mestrebasket.it,https://www.basketmestre.it/,SÌ,"Neopromossa in Serie A2 dopo 37 anni, settore giovanile Eccellenza U19/U17/U15 e minibasket/baskin strutturati; già attiva con dirette streaming su Facebook",B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Duplicato consolidato da denominazioni alternative: Mestre; Basket Mestre (Gemini Mestre) | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2027,Lombardia,MI,Urania Milano,M,info@uraniamilano.it,,NON RILEVATO,Società con seguito e settore giovanile in crescita. Serie A2.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2028,Toscana,PT,Lumos Pistoia Basket,M,info@pistoiabasket.it,,NON RILEVATO,Club con seguito e progetto giovanile. Serie A2.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2029,Lazio,RI,Sebastiani Rieti,M,info@sebastianirieti.it,https://www.rsrsebastianirieti.it/,LIMITATO,"Club storico di Serie A2, settore giovanile strutturato, forte seguito locale",A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Duplicato consolidato da denominazioni alternative: Sebastiani Rieti; RSR Sebastiani Rieti | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2030,Emilia-Romagna,RN,Dole Basket Rimini,M,info@riminibasket.it,,NON RILEVATO,Club con seguito e progetto giovanile. Serie A2.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2031,Puglia,BA,Ruvo di Puglia,M,info@ruvodipugliabasket.it,http://www.pallacanestroruvodipuglia.it,LIMITATO,"Club di Serie A2 in provincia di Bari, buon bacino di tifosi locale",A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Email pubblica non reperita: verificare telefono/social/form prima dell'invio email diretta. | Contatto email non strutturato nel file origine: vedi form contatti sito ufficiale | Duplicato consolidato da denominazioni alternative: Ruvo di Puglia; Pallacanestro Ruvo di Puglia | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2032,Sardegna,SS,Banco di Sardegna Sassari,M,info@dinamobasket.com,https://dinamobasket.com,SÌ,Club con grandissimo seguito in Sardegna. Settore giovanile strutturato. Serie A2.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2033,Piemonte,TO,Reale Mutua Torino,M,info@torinobasket.it,,NON RILEVATO,Società con seguito e progetto giovanile. Serie A2.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2034,Lombardia,PV,ELAchem Vigevano,M,info@vigevanobasket.it,http://www.vigevano1955.com,LIMITATO,"Storico club (ex Serie A1, oggi A2), già usa dirette YouTube non strutturate: margine per piattaforma professionale",B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Email pubblica non reperita: verificare telefono/social/form prima dell'invio email diretta. | Contatto email non strutturato nel file origine: vedi form contatti sito | Duplicato consolidato da denominazioni alternative: ELAchem Vigevano; ELAchem Vigevano 1955 | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2035,Sicilia,AG,Fortitudo Agrigento,M,info@fortitudoagrigento.it,https://www.fortitudoagrigento.it,LIMITATO,"Principale società cestistica di Agrigento (Serie B Nazionale), sito ufficiale con contatti per sponsorship/collaborazioni, streaming di lega solo parziale e assente per giovanili",A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2036,Sicilia,ME,Orlandina Academy,M,info@orlandinabasket.it,https://orlandinabasket.it,NON RILEVATO,Club storico con seguito. Settore giovanile. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://orlandinabasket.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2037,Lombardia,MI,Aurora Desio,M,info@auroradesio.it,https://www.auroradesio.it/,LIMITATO,"Vivaio di eccellenza (21 finali nazionali giovanili, 7 titoli regionali dal 2000), prima squadra in Serie B",A,DA INVIARE,,,NO,NO,,https://ilgiorno.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Duplicato consolidato da denominazioni alternative: Aurora Desio; Pallacanestro Aurora Desio | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2038,Emilia-Romagna,PR,Fulgor Fidenza,M,info@fulgorfidenza.it,https://www.fulgorfidenza.it,NON RILEVATO,"Serie B Interregionale + giovanili, FB/IG attivi (@fulgorfidenza), buon fit commerciale",B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Email pubblica non reperita: verificare telefono/social/form prima dell'invio email diretta. | Contatto email non strutturato nel file origine: vedi form contatti sito (fulgorfidenza.it/contatti) | Duplicato consolidato da denominazioni alternative: Fulgor Fidenza; Pallacanestro Fulgor Fidenza | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2039,Emilia-Romagna,PC,Pall. Fiorenzuola Bees,M,info@fiorenzuolabees.it,,NON RILEVATO,Società con seguito e settore giovanile. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2040,Lombardia,MI,Legnano Basket Knights,M,info@legnanobasket.it,,NON RILEVATO,Club con seguito e settore giovanile. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Duplicato consolidato da denominazioni alternative: Legnano Basket Knights; SAE Scientifica Legnano | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2041,Lombardia,BS,Virtus Lumezzane,M,info@virtuslumezzane.it,,NON RILEVATO,Società con seguito e progetto giovanile. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2042,Piemonte,AL,Monferrato Basket,M,info@monferratobasket.it,,NON RILEVATO,Club con seguito e settore giovanile. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Duplicato consolidato da denominazioni alternative: Monferrato Basket; Novipiù Monferrato | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2043,Toscana,PT,Gema Montecatini,M,info@gemamontecatini.it,,NON RILEVATO,Società con seguito. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2044,Toscana,PT,Herons Montecatini,M,info@heronsmontecatini.it,,NON RILEVATO,Club con seguito e progetto giovanile. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Duplicato consolidato da denominazioni alternative: Herons Montecatini; Fabo Herons Montecatini | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2045,Piemonte,VB,Fulgor Omegna,M,info@fulgoromegna.it,,NON RILEVATO,Società con seguito. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2046,Lombardia,BS,Orzinuovi,M,info@orzibasket.it,,NON RILEVATO,Società con seguito e progetto giovanile. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Duplicato consolidato da denominazioni alternative: Orzinuovi; Basket Orzinuovi | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2047,Sicilia,EN,Piazza Armerina,M,info@piazzaarmerinabasket.it,,NON RILEVATO,Società con seguito siciliano. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2048,Emilia-Romagna,PC,Bakery Basket Piacenza,M,info@bakerypiacenza.it,,NON RILEVATO,Club con seguito e progetto giovanile. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2049,Emilia-Romagna,PC,UCC Assigeco Piacenza,M,info@assigeco.it,,NON RILEVATO,Società con seguito. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2050,Veneto,TV,Rucker San Vendemiano,M,info@ruckersanvendemiano.it,,NON RILEVATO,Club con seguito e settore giovanile. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2051,Lombardia,BG,Treviglio Brianza Basket,M,info@trevigliobasket.it,,NON RILEVATO,Società con seguito e progetto giovanile. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2052,Veneto,VI,Pallacanestro Vicenza,M,info@vicenzabasket.it,,NON RILEVATO,Club storico con seguito. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2053,Puglia,FG,San Severo,M,info@sanseverobasket.it,,NON RILEVATO,Società con seguito pugliese. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2054,Toscana,SI,Mens Sana Siena,M,info@messinasana.it,,NON RILEVATO,Club storico con grandissimo seguito. Settore giovanile di eccellenza. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://ravennanotizie.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2055,Lazio,LT,Latina Basket,M,info@latinabasket.it,https://www.latinabasket.it/,LIMITATO,"Storico club con settore giovanile e minibasket strutturato, canale YouTube attivo",A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2056,Toscana,LI,Pielle Livorno,M,info@piellelivorno.it,,NON RILEVATO,Club storico con grande seguito. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2057,Campania,NA,Power Basket Nocera,M,info@powernocera.it,,NON RILEVATO,Società con seguito campano. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2058,Toscana,LI,Solbat Piombino,M,info@solbatpiombino.it,,NON RILEVATO,Club con seguito e progetto giovanile. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2059,Toscana,PT,Dany Basket Quarrata,M,info@quarratabasket.it,,NON RILEVATO,Società con seguito. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2060,Emilia-Romagna,RA,OraSì Basket Ravenna,M,info@orasiravenna.it,,NON RILEVATO,Club con seguito e progetto giovanile. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://ravennanotizie.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2061,Lazio,RM,Luiss Roma,M,info@luissbasket.it,,NON RILEVATO,Società con seguito e progetto accademico-sportivo. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2062,Lazio,RM,Virtus Roma,M,info@virtusroma.it,,NON RILEVATO,Club storico con grandissimo seguito. Settore giovanile di eccellenza. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://ilrestodelcarlino.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2063,Umbria,PG,UBS Foligno,M,info@folignobasket.it,,NON RILEVATO,"Società con eccellente settore giovanile (10 gruppi giovanili, 8 nel minibasket).",A,DA INVIARE,,,NO,NO,,https://lanazione.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2064,Lombardia,MI,New Basket Team,M,info@newbasketteam.it,,NON RILEVATO,Società con forte focus sul settore giovanile (99 tesserati tra Under e Senior).,B,DA INVIARE,,,NO,NO,,https://radiogold.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2065,Lombardia,PV,Scotti Basket,M,info@scottibasket.it,,NON RILEVATO,Società con settore giovanile in forte crescita. Progetto di inserimento Under 17 in prima squadra.,A,DA INVIARE,,,NO,NO,,https://laprovinciapavese.gelocal.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2066,Puglia,MT,Virtus Matera,M,info@virtusmatera.it,,NON RILEVATO,Società con progetto giovani. Inserimento di sei giovani in prima squadra.,B,DA INVIARE,,,NO,NO,,https://antennasud.com,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2067,Toscana,LU,Pallacanestro Lucca,M,info@luccabasket.it,,NON RILEVATO,Club con seguito e settore giovanile. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://ilrestodelcarlino.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2068,Toscana,SI,Pallacanestro Chiusi,M,info@chiusibasket.it,,NON RILEVATO,Società con seguito. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://ilrestodelcarlino.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2069,Toscana,LI,Pallacanestro Piombino,M,info@piombinobasket.it,,NON RILEVATO,Club con seguito e collaborazione con Derthona per il settore giovanile.,A,DA INVIARE,,,NO,NO,,https://derthonabasket.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2070,Lombardia,MI,Basket Foxes Giussano,M,info@basketfoxes.it,,NON RILEVATO,Società con 40 anni di attività e settore giovanile consolidato. Premiata dalla FIP.,B,DA INVIARE,,,NO,NO,,https://primacomo.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2071,Lombardia,MI,Scuola Basket Ottantatre,M,info@scuolabasket83.it,,NON RILEVATO,Società con 40 anni di attività e forte vocazione giovanile. Premiata dalla FIP.,A,DA INVIARE,,,NO,NO,,https://primacomo.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2072,Lombardia,CO,Olimpia Cadorago,M,info@olimpiacadorago.it,,NON RILEVATO,Società con 50 anni di attività e settore giovanile. Premiata dalla FIP.,B,DA INVIARE,,,NO,NO,,https://primacomo.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2073,Emilia-Romagna,FE,Andrea Costa Imola,M,info@andreacostaimola.it,,NON RILEVATO,Club con seguito e progetto giovanile. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://ilrestodelcarlino.it,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2074,Marche,AN,Jesi Academy,M,info@jesiacademy.it,,NON RILEVATO,Società con seguito marchigiano e settore giovanile. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2075,Emilia-Romagna,FE,Raggisolaris Faenza,M,info@raggisolaris.it,,NON RILEVATO,Club con seguito e progetto giovanile. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2076,Emilia-Romagna,FE,Ferrara Basket,M,info@ferrarabasket.it,,NON RILEVATO,Società con seguito. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2077,Emilia-Romagna,BO,Virtus Pallacanestro Imola,M,info@virtusimola.it,,NON RILEVATO,Club con seguito e settore giovanile. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2078,Marche,AN,General Contractor Basket Jesi,M,info@basketjesi.it,,NON RILEVATO,Società con seguito. Serie B Nazionale.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2079,Lombardia,BG,Bergamo Basket,M,info@bergamobasket.it,,NON RILEVATO,Club con seguito e settore giovanile. Serie B Nazionale.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2080,Toscana,PO,Pistoia Basket,M,info@pistoiabasket2000.it,,NON RILEVATO,Società con seguito e progetto giovanile. Serie A2.,B,DA INVIARE,,,NO,NO,,https://legapallacanestro.com,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2081,Marche,PU,Victoria Libertas Pesaro,M,info@victorialibertas.it,,NON RILEVATO,Club storico con grandissimo seguito. Serie A2.,A,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2082,Lombardia,BG,Blu Basket Bergamo,M,info@blubasket.it,,NON RILEVATO,Società con seguito e progetto giovanile. Serie A2.,B,DA INVIARE,,,NO,NO,,https://sportando.basketball,2026-09-07,"Email da verificare sul sito ufficiale | Email indicata come da verificare nel file origine. | Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2083,Lombardia,MN,Stings Mantova (ex Pallacanestro Mantovana),M,info@stingsmantova.it,https://www.stingsmantova.it/,NON RILEVATO,"Vivaio multi-categoria U14-U19, prima squadra risalita in Serie B Interregionale nel 2024 dopo cessione titolo A2",A,DA INVIARE,,,NO,NO,,https://www.stingsmantova.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2084,Lombardia,VA,Robur et Fides Varese (sezione basket),M,giovanili@roburetfides.com,https://www.roburetfides.com/basket/,NON RILEVATO,"Vivaio storico con 8 titoli giovanili italiani (U15/U17/U19), promossa in Serie C nel 2025",B,DA INVIARE,,,NO,NO,,https://www.roburetfides.com/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2085,Lombardia,BG,Unica BluOrobica (Bluorobica Bergamo),M,segreteria@bluorobica.com,http://www.bluorobica.net/,SÌ,"Settore giovanile strutturato U13-U19, Serie B Interregionale; già trasmette dirette su canale YouTube proprio, target per upgrade professionale",A,DA INVIARE,,,NO,NO,,http://www.bluorobica.net/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2086,Lombardia,PV,Aironi Pallacanestro Robbio,M,info@aironirobbio.it,https://www.aironirobbio.it/,NON RILEVATO,"Principale società cestistica della Lomellina, Serie C Silver con minibasket attivo",B,DA INVIARE,,,NO,NO,,https://www.aironirobbio.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2087,Lombardia,BS,CUS Brescia Basket,M,info@cusbrescia.it,https://www.cusbresciabasket.it/,NON RILEVATO,"Settore giovanile completo Minibasket-U19, prima squadra Divisione Regionale 2, ampia community universitaria",A,DA INVIARE,,,NO,NO,,https://www.cusbresciabasket.it/contatti,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2088,Lombardia,SO,Sportiva Basket Sondrio,M/F,segreteria@sportivabasketsondrio.it,https://www.sportivabasketsondrio.it/,NON RILEVATO,"Unica realtà cestistica strutturata della provincia, settori giovanili maschile e femminile U13-U19 e minibasket",B,DA INVIARE,,,NO,NO,,https://www.sportivabasketsondrio.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2092,Lombardia,CO,Polisportiva Comense 2015 (ex Pool Comense),F,info@comense.it,https://www.comense.it/,LIMITATO,"Erede della Pool Comense (15 scudetti italiani 1990-2004), settore giovanile femminile completo U13-U19 in Serie C, canale YouTube presente",B,DA INVIARE,,,NO,NO,,https://www.comense.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2093,Piemonte,CN,Granda College Cuneo,M/F,info@grandacollege.it,https://www.grandacollege.it/gc/,LIMITATO,"Vivaio molto strutturato M e F (U13-U19), Serie B Femminile campione regionale e finalista nazionale U17F, video su YouTube ma senza streaming live regolare",A,DA INVIARE,,,NO,NO,,https://www.grandacollege.it/gc/index.php/le-squadre,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2095,Piemonte,NO,A.S.D. Novara Basket,M,info@novarabasket.it,https://www.novarabasket.it/,NON RILEVATO,"Settore giovanile ampio (U13-U19 Gold/Regionali), squadra Divisione Regionale 1 maschile, comunicazione solo via social",A,DA INVIARE,,,NO,NO,,https://www.novarabasket.it/contatti.html,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2096,Piemonte,AT,Scuola Basket Asti,M/F,asdscuolabasketasti@gmail.com,https://www.scuolabasketasti.it/,SÌ,"Unica società di riferimento della città (500 tesserati), settore giovanile M e F su 7 palestre, canale YouTube ufficiale con partite integrali di Serie C",B,DA INVIARE,,,NO,NO,,https://www.scuolabasketasti.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2097,Piemonte,BI,A.S.D. Teens Basket Biella,M,info@teensbasketbiella.com,https://www.teensbasketbiella.com/,LIMITATO,"7 squadre giovanili + prima squadra Serie C, solo dirette Facebook occasionali; buon target per streaming strutturato",A,DA INVIARE,,,NO,NO,,https://www.biellainsieme.it/no_profit/asd-teens-basket-biella/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2098,Piemonte,VB,Azzurra Basket VCO,F,vcobasketazzurra@gmail.com,https://www.azzurra-basket-vco.it/,NON RILEVATO,"Società femminile provincia VCO (sede a Omegna) con squadre U18/U16/U13 e prima squadra Serie C, nessuno streaming rilevato",B,DA INVIARE,,,NO,NO,,https://www.azzurra-basket-vco.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2100,Liguria,IM,A.S.D. Imperia Basket Riviera dei Fiori,M/F,imperiabasket@gmail.com,https://imperiabasket.com/,NON RILEVATO,"Vivaio strutturato con categorie da Minibasket a U19, sia maschili che femminili, buona presenza social",B,DA INVIARE,,,NO,NO,,https://imperiabasket.com/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2101,Liguria,SP,Cestistica Spezzina,F,052534@spes.fip.it,https://www.cestisticaspezzina.it/,LIMITATO,"Storico club femminile (1969, ex Serie A1/A2) in ricostruzione su giovanile e minibasket dopo uscita dalla A2 nel 2025, forte community e brand riconosciuto",A,DA INVIARE,,,NO,NO,,https://www.cestisticaspezzina.it/societa/organigramma.html,2026-09-07,Contatto email non strutturato nel file origine: vedi pagina Facebook: https://www.facebook.com/CestSpezzina/ | Verifica web 07/09/2026: indirizzo societario FIP pubblicato nell'organigramma ufficiale.
|
||||
2102,Valle d'Aosta,AO,"A.S.D. Basket Pont-Donnas ""Paolo Preti""",M,info@basketpontdonnas.it,https://www.basketpontdonnas.it/,NON RILEVATO,"Club trentennale, selezionato dalla FIP tra le 8 Regioni pilota per sviluppo attività femminile (budget 15.000€ per il 2026), club in fase di espansione",B,DA INVIARE,,,NO,NO,,https://www.comune.pontsaintmartin.ao.it/unita-organizzativa/basket-pont-donnas/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2103,Veneto,VR,Alpo Basket '99,F,alpobasket99@pec.it,https://www.alpobasket.it/,LIMITATO,Storica società di Verona (Alpo di Villafranca) in Serie A2 femminile con settore giovanile strutturato MB/U13-U19; canale YouTube ma nessuna diretta live regolare,A,DA INVIARE,,,NO,NO,,https://www.alpobasket.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2104,Veneto,VI,Orange1 Basket Bassano,M,info@orange1basketbassano.eu,https://orange1basketbassano.eu/,SÌ,"Settore giovanile d'eccellenza (U19/U17/U15 Eccellenza, Academy, progetto scuola-sport) che già trasmette le partite casalinghe su YouTube con telecronaca",B,DA INVIARE,,,NO,NO,,https://orange1basketbassano.eu/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2105,Veneto,VI,AS Vicenza,F,asdilvicenza58@gmail.com,https://www.asvicenza.it/,SÌ,"Storica società dal 1958, prima squadra in Serie A2 femminile con settore giovanile U19F/U17F/U14F e minibasket; canale dedicato ""AS Vicenza TV"" su YouTube",A,DA INVIARE,,,NO,NO,,https://www.asvicenza.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2106,Veneto,PD,Petrarca Basket Padova,M,info@petrarcabasket.it,https://petrarcabasket.it/,LIMITATO,"Uno dei settori giovanili più riconosciuti del Veneto (Eccellenza U19-U13, Baskin, minibasket), presenza YouTube ma senza diretta strutturata sistematica",B,DA INVIARE,,,NO,NO,,https://petrarcabasket.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2107,Veneto,TV,Nuova Pallacanestro Treviso (progetto Blossoms),F,nuovapallacanestrotreviso@gmail.com,https://nuovapallacanestrotreviso.com/blossoms-2/,LIMITATO,"Progetto ""Blossoms"" dedicato al settore giovanile femminile (U14-U17 Gold, minibasket) in collaborazione con Basket Istrana; nessuno streaming dedicato al settore giovanile/femminile",A,DA INVIARE,,,NO,NO,,https://nuovapallacanestrotreviso.com/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2108,Veneto,RO,Nuovo Basket Rovigo,M,info@nuovobasketrovigo.it,https://nuovobasketrovigo.it/,LIMITATO,"Settore giovanile in forte crescita (minibasket, U14/U15, U14 femminile) con academy avviata da tre stagioni; presenza YouTube/Facebook ma senza streaming live strutturato",B,DA INVIARE,,,NO,NO,,https://nuovobasketrovigo.it/giovanili-2/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2109,Veneto,BL,Valbelluna Basket,M/F,valbellunabasket@gmail.com,https://valbellunabasket.com/,NON RILEVATO,"Settore sia maschile (Serie DR1, U19/U15/U13 Gold) sia femminile (Promozione, U15/U13) e minibasket a Belluno/Sedico; target ideale per introdurre il servizio da zero in provincia periferica",A,DA INVIARE,,,NO,NO,,https://valbellunabasket.com/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2110,Emilia-Romagna,FE,Vis 2008 Ferrara,M/F,info@vis2008ferrara.it,https://www.vis2008ferrara.it/,LIMITATO,"Settore giovanile ampio (U13-U19 Gold/Eccellenza) sia maschile (""Vis 2008"") sia femminile (""Vis Rosa""), canale YouTube esistente ma non usato per dirette regolari",B,DA INVIARE,,,NO,NO,,https://www.vis2008ferrara.it/contatti,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2111,Emilia-Romagna,RA,Basket Lugo SSD ARL,M,info@basketlugo.it,https://basketlugo.it/,NON RILEVATO,"Squadra senior in Divisione Regionale 1 maschile e settore giovanile completo (U13-U19, Esordienti/Aquilotti/Scoiattoli/Pulcini, anche U17 femminile), nessuno streaming rilevato",A,DA INVIARE,,,NO,NO,,https://basketlugo.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2112,Emilia-Romagna,RN,Basket Santarcangelo (Dulca/Angels),F,redazione@santarcangelobasket.com,https://www.santarcangelobasket.com/,LIMITATO,"Storico club femminile romagnolo, prima squadra in playoff di Serie C, settore giovanile e minibasket attivi, canale YouTube non sfruttato per dirette",B,DA INVIARE,,,NO,NO,,https://www.santarcangelobasket.com/contatti,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2114,Emilia-Romagna,MO,US Pallacanestro Vignola,M,info@uspallacanestro.it,https://www.uspallacanestro.it/,NON RILEVATO,"Settore giovanile molto ampio (psicomotricità 3-5 anni, minibasket, programma disabilità ""Camaleonti""), prima squadra maschile promossa in Serie C, nessuno streaming",B,DA INVIARE,,,NO,NO,,https://www.uspallacanestro.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2115,Emilia-Romagna,PC,A.D.P. Virtus Pontedellolio,M,virtuspontedellolio@gmail.com,https://www.instagram.com/virtuspontedellolio/,NON RILEVATO,"Piccola realtà di provincia con minibasket su tre sedi e squadre fino a Serie C/Divisione Regionale, senza pedigree eccezionale ma reale e attiva: utile per test A/B",A,DA INVIARE,,,NO,NO,,https://virtuspontedellolio.wordpress.com/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2116,Emilia-Romagna,RE,Puianello Basket Team (Chemco Puianello),F,puianellobt@gmail.com,https://www.puianellobasketteam.it/,NON RILEVATO,"Società esclusivamente femminile, prima squadra in Serie B e Under 14 arrivata 7ª ai nazionali 2026; ottimo settore giovanile, nessuno streaming rilevato",B,DA INVIARE,,,NO,NO,,https://www.puianellobasketteam.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2117,Emilia-Romagna,PR,Basket Club Valtarese 2000,F,info@valtaresebasket.it,https://valtaresebasket.it/,NON RILEVATO,"Club di Borgo Val di Taro con squadra femminile senior e settore giovanile esteso su più sedi (Aquilotti/Scoiattoli/Pulcini, Minibasket Bedonia), nessuno streaming strutturato",A,DA INVIARE,,,NO,NO,,https://valtaresebasket.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2118,Trentino-Alto Adige,TN,Junior Basket Rovereto (JBR) A.S.D.,M,segreteria@juniorbasketrovereto.net,https://www.juniorbasketrovereto.net/,LIMITATO,"Oltre 450 tesserati su 26 squadre dal minibasket alla senior, settore giovanile molto strutturato (U13-U19 + Baskin) in città non capoluogo",B,DA INVIARE,,,NO,NO,,https://www.juniorbasketrovereto.net/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2119,Trentino-Alto Adige,TN,A.S.D. Basket Pergine,M/F,info@basketpergine.it,https://www.basketpergine.it/,LIMITATO,"Settore sia maschile che femminile, 4 squadre giovanili + 3 gruppi minibasket a Pergine Valsugana, community molto attiva su Facebook",A,DA INVIARE,,,NO,NO,,https://www.basketpergine.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2120,Trentino-Alto Adige,BZ,Maia Basket Merano,F,segreteriamaiabasket@gmail.com,https://www.maiabasket.it/,NON RILEVATO,"Più grande società di basket femminile dell'area (fondata 1973), squadre giovanili U13-U19 attive; nessuno streaming attuale = opportunità per differenziarsi",B,DA INVIARE,,,NO,NO,,https://www.maiabasket.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2121,Trentino-Alto Adige,BZ,A.S. SSV Brixen Basket Bressanone,M,basket@ssv-brixen.info,https://ssvbrixen.code4.it/pallacanestro/contatto/,NON RILEVATO,"Società reale e attiva a Bressanone con sezione giovanili/under, nessun pedigree eccezionale ma target valido per test A/B su società più piccole",A,DA INVIARE,,,NO,NO,,https://ssvbrixen.code4.it/pallacanestro/contatto/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2122,Friuli-Venezia Giulia,TS,A.S.D. Pallacanestro Interclub Muggia,F,pallacanestrointerclub@gmail.com,https://www.pallacanestrointerclubmuggia.com/,LIMITATO,"Storica società (dal 1968) con prima squadra femminile in Serie B, programma inclusivo Baskin e settore giovanile attivo",B,DA INVIARE,,,NO,NO,,https://www.pallacanestrointerclubmuggia.com/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2123,Friuli-Venezia Giulia,PN,A.S.D. Sistema Basket Pordenone,M,presidente@sistemabasketpordenone.com,https://www.facebook.com/SBPordenone/,LIMITATO,"Settore giovanile molto strutturato (U19, U17 Eccellenza, U15 Eccellenza, U14, minibasket) con prima squadra in Serie B Interregionale maschile",A,DA INVIARE,,,NO,NO,,https://www.comune.pordenone.it/it/vivere-pordenone/associazioni-online/associazioni/sistema-basket-pordenone-asd,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2124,Lazio,RM,Anzio Basket Club,M,anziobasketclub@gmail.com,https://www.anziobasketclub.it/,NON RILEVATO,"Attivo dal 1976, settore giovanile strutturato, due impianti sportivi propri",B,DA INVIARE,,,NO,NO,,https://www.anziobasketclub.it/societa/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2125,Lazio,RM,Cestistica Civitavecchia 1948,M,segreteria@cestisticacivitavecchia.it,https://cestisticacivitavecchia.com/,NON RILEVATO,"Storica società dal 1948, squadra Serie C e minibasket, forte radicamento territoriale",A,DA INVIARE,,,NO,NO,,https://cestisticacivitavecchia.com/contatti-2/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2126,Lazio,RM,Esquilino Basketball,M/F,segreteria@esquilinobasketball.it,https://www.esquilinobasketball.it/,LIMITATO,"Settore giovanile maschile e femminile, minibasket, canale YouTube attivo",B,DA INVIARE,,,NO,NO,,https://www.esquilinobasketball.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2127,Lazio,RM,Club Basket Frascati,M/F,societa@basketfrascati.com,https://www.basketfrascati.com/,NON RILEVATO,"Gruppo multi-sede (Frascati, Monte Porzio, Roma) con settore giovanile ampio M/F",A,DA INVIARE,,,NO,NO,,https://www.basketfrascati.com/contatti.htm,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2128,Lazio,RM,Frassati Ciampino Basketball,M/F,info@frassaticiampinobasket.it,https://www.frassaticiampinobasket.it/,NON RILEVATO,"Presente dal 1983, squadre Serie C maschile e femminile, minibasket strutturato",B,DA INVIARE,,,NO,NO,,https://www.frassaticiampinobasket.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2129,Lazio,VT,Blu Star Basket Viterbo,M/F,angyferri12@gmail.com,https://www.facebook.com/blustarviterbo/,NON RILEVATO,"Oltre 150 tesserati, 30+ anni dedicati al settore giovanile, titoli Under regionali",A,DA INVIARE,,,NO,NO,,https://www.tusciaweb.eu/2023/02/blu-star-basket-viterbo-oltre-150-tesserati-per-una-realta-che-da-oltre-30-anni-si-dedica-al-settore-giovanile/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2130,Toscana,PI,IES Basket Pisa,M/F,info@iesbasketpisa.it,https://www.iesbasketpisa.it/,NON RILEVATO,"Corsi maschili e femminili tutte le età, squadre giovanili U13-U19, progetto Baskin",B,DA INVIARE,,,NO,NO,,https://www.iesbasketpisa.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2131,Toscana,LU,Le Mura Spring,F,amministrazione@lemuraspring.it,https://www.lemuraspring.it/,NON RILEVATO,"Club femminile con Serie B, settore giovanile U19/U17/U13 e minibasket, Baskin",A,DA INVIARE,,,NO,NO,,https://www.lemuraspring.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2132,Toscana,FI,ABC Castelfiorentino,M/F,info@abccastelfiorentino.it,https://www.abccastelfiorentino.it/,NON RILEVATO,"60 anni di attività (dal 1966), Serie C maschile e femminile, vivaio dal microbasket",B,DA INVIARE,,,NO,NO,,https://www.abccastelfiorentino.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2133,Toscana,LU,Basket Femminile Porcari,F,info@basketfemminileporcari.it,https://www.basketfemminileporcari.it/,LIMITATO,"Club femminile con settore giovanile completo dal minibasket a Serie C, dal 2015",A,DA INVIARE,,,NO,NO,,https://www.basketfemminileporcari.it/index.php/chi-siamo/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2134,Toscana,SI,Poggibonsi Basket,M/F,mail@poggibonsibasket.it,https://www.poggibonsibasket.it/,NON RILEVATO,"Settore giovanile M/F completo U13-U19 più minibasket, squadre Serie D",B,DA INVIARE,,,NO,NO,,https://www.poggibonsibasket.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2135,Toscana,GR,GEA Basketball Grosseto,M/F,info.geabasketball@gmail.com,https://www.geabasketball.it/,NON RILEVATO,"Vivaio ampio M/F, vincitrice Coppa Toscana 2025-26, squadre Serie C/D",A,DA INVIARE,,,NO,NO,,https://www.geabasketball.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2136,Marche,MC,Basket Macerata,M,basketmacerata.mc@gmail.com,https://www.basketmacerata.it/,NON RILEVATO,"Fondata nel 1953, settore giovanile in collaborazione con Fochi Pollenza e CUS Macerata",B,DA INVIARE,,,NO,NO,,https://www.basketmacerata.it/contattaci/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2137,Marche,AN,CAB Stamura Basket Ancona,M/F,info@cabstamura.it,https://www.cabstamura.it/,NON RILEVATO,"Settore giovanile M/F completo U13-U19, campioni d'Italia Under 16 3x3",A,DA INVIARE,,,NO,NO,,https://www.cabstamura.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2138,Marche,MC,Vigor Basket Matelica,M,vigorbasketmatelica@gmail.com,https://vigorbasketmatelica.it/,LIMITATO,"Settore giovanile U13-U19 strutturato, canale YouTube attivo (Halley Vigor Basket)",B,DA INVIARE,,,NO,NO,,https://vigorbasketmatelica.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2139,Marche,MC,Attila Junior Basket Porto Recanati,M,juniorbasket.pr@gmail.com,https://www.attilabasket.it/,LIMITATO,"Serie B Interregionale, settore giovanile e minibasket, canale YouTube attivo",A,DA INVIARE,,,NO,NO,,https://www.attilabasket.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2141,Abruzzo,TE,Basketball Teramo,M/F,basketballteramo@gmail.com,https://www.basketballteramo.it/,NON RILEVATO,"Minibasket e settore giovanile strutturato, doposcuola e camp estivi",A,DA INVIARE,,,NO,NO,,https://www.basketballteramo.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2144,Abruzzo,CH,Virtus Basket Don Bosco Vasto,M/F,info@donboscovasto.it,https://www.donboscovasto.it/,NON RILEVATO,"Sezione sportiva dell'oratorio salesiano, squadre giovanili maschili e femminili storiche",B,DA INVIARE,,,NO,NO,,https://www.donboscovasto.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2145,Umbria,TR,Union Basket Terni,M,info@unionbasket.it,https://www.unionbasket.it/,NON RILEVATO,"Undici squadre attive tra minibasket e giovanile, circa 240 tesserati dai 5 ai 30 anni",A,DA INVIARE,,,NO,NO,,https://www.unionbasket.it/chi-siamo/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2146,Umbria,TR,Esseti Basket Terni,M,info@basketterni.it,https://www.basketterni.it/,NON RILEVATO,"Squadre affiliate giovanili strutturate, radicamento storico nel basket ternano",B,DA INVIARE,,,NO,NO,,https://www.basketterni.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2147,Umbria,PG,Perugia Basket,M,info@perugiabasket.it,https://www.perugiabasket.it/contact/,LIMITATO,"Rifondata nel 2005, riferimento del movimento cestistico umbro, minibasket e giovanili strutturati",A,DA INVIARE,,,NO,NO,,https://www.perugiabasket.it/torneo_esordienti/,2026-09-07,Verifica web 07/09/2026: email pubblicata dal club per informazioni/adesioni.
|
||||
2148,Campania,SA,Angri Pallacanestro,M/F,info@angripallacanestro.it,https://angripallacanestro.it/,NON RILEVATO,"Doppia sezione maschile (B Interregionale) e femminile (Serie B), club radicato dal 2007",B,DA INVIARE,,,NO,NO,,https://angripallacanestro.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2149,Campania,NA,Basket Femminile Stabia,F,basketfemmstabia@libero.it,https://www.basketfemminilestabia.it/,NON RILEVATO,Squadra Serie B femminile con settore giovanile strutturato (es. Under 14),A,DA INVIARE,,,NO,NO,,https://www.basketfemminilestabia.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2150,Campania,BN,Cestistica Benevento (Miwa Basket),M/F,info@cestisticabenevento.it,https://www.cestisticabenevento.it/,NON RILEVATO,"Doppio settore maschile e femminile, settore giovanile attivo con City Camp estivo",B,DA INVIARE,,,NO,NO,,https://www.cestisticabenevento.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2151,Campania,SA,Minibasket Battipaglia (Polisportiva Battipagliese PB63),M,minibasket@pb63.it,https://pb63.it/minibasket/,NON RILEVATO,"Vivaio storico dal 1963, 6 categorie minibasket + giovanili U13-U19",A,DA INVIARE,,,NO,NO,,https://pb63.it/minibasket/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2152,Campania,CE,Uniobasket Maddaloni,M/F,info@uniobasket.it,https://www.uniobasket.it/,NON RILEVATO,"Rete di 3 società confluite: maschile Serie C, femminile Serie B, forte progetto territoriale",B,DA INVIARE,,,NO,NO,,https://www.uniobasket.it/sample-page/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2154,Campania,CE,Basket Casapulla,M,basketcasapulla82@gmail.com,https://www.basketcasapulla.com/,NON RILEVATO,"Attiva dal 1982, 8 campionati giovanili in stagione, Under 19 Eccellenza",B,DA INVIARE,,,NO,NO,,https://www.basketcasapulla.com/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2155,Puglia,BA,Pallacanestro Molfetta,M,molfetta.pallacanestro@gmail.com,https://pallacanestromolfetta.it/,NON RILEVATO,"Squadra Serie C Interregionale, presenza social multi-canale (FB/IG/YouTube/TikTok)",A,DA INVIARE,,,NO,NO,,https://pallacanestromolfetta.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2156,Puglia,BT,Lions Bisceglie (Nuova Cestistica),M,comunicazione@lionsbisceglie.com,https://www.lionsbisceglie.com/,NON RILEVATO,"Milita in Serie B2, settore giovanile Under 17 Gold/Eccellenza, palazzetto PalaDolmen ristrutturato",B,DA INVIARE,,,NO,NO,,https://www.lionsbisceglie.com/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2157,Puglia,BR,Dinamo Basket Brindisi,M,info@dinamobasketbrindisi.it,https://www.dinamobasketbrindisi.it/,NON RILEVATO,"Realtà cittadina alternativa al club di A2, buon target per progetti di visibilità locale",A,DA INVIARE,,,NO,NO,,https://www.dinamobasketbrindisi.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2159,Puglia,BA,Seagulls Monopoli,M,monopoliseagulls@gmail.com,https://www.monopoliseagulls.it/,NON RILEVATO,"3 squadre competitive (DR1, DR3, U19), progetto giovanile in crescita",A,DA INVIARE,,,NO,NO,,https://www.monopoliseagulls.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2160,Puglia,BA,CUS Bari - sezione pallacanestro,M,informazioni@cusbari.it,https://www.cusbari.it/sport/pallacanestro-sezione/,NON RILEVATO,"Storica dal 1947, oltre 200 giovani atleti, partnership con 50+ scuole, U15/U16 di livello regionale",B,DA INVIARE,,,NO,NO,,https://www.cusbari.it/sport/pallacanestro-sezione/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2161,Calabria,RC,Pallacanestro Viola Reggio Calabria,M,info@pallacanestroviola.it,https://pallacanestroviola.it/,NON RILEVATO,"Principale società cestistica di Reggio Calabria, settore giovanile strutturato",A,DA INVIARE,,,NO,NO,,https://pallacanestroviola.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2162,Calabria,CS,Pirossigeno Cosenza,M,info@pirossigenocosenza.it,https://www.pirossigenocosenza.it/basket/,NON RILEVATO,"Polisportiva con minibasket in forte espansione, ex atlete Serie B/C come istruttrici",B,DA INVIARE,,,NO,NO,,https://www.pirossigenocosenza.it/futsal/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2163,Calabria,RC,Cestistica Gioiese (Gioia Tauro),M,partnership@cestisticagioiese.it,https://www.cestisticagioiese.it/,NON RILEVATO,"Squadra di Serie C, 16 gare interne stagionali, target interessante per contenuti locali",A,DA INVIARE,,,NO,NO,,https://www.cestisticagioiese.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2164,Basilicata,PZ,Nuovo Basket Club Potenza,M,info@nuovobasketpotenza.it,https://www.nuovobasketpotenza.it/,NON RILEVATO,"Settore giovanile ampio: U19 Gold, U17 Eccellenza, U13 regionale, minibasket Aquilotti/Esordienti",B,DA INVIARE,,,NO,NO,,https://www.nuovobasketpotenza.it/dove-siamo/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2165,Molise,CB,Magnolia Basket Campobasso,M/F,info@magnoliabasket.it,https://www.magnoliabasket.it/,LIMITATO,"Milita in Serie A1 femminile (massimo campionato nazionale) con squadra maschile parallela, settore giovanile strutturato",A,DA INVIARE,,,NO,NO,,https://www.magnoliabasket.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2166,Sicilia,TP,Golfobasket Alcamo,F,golfobasketwoman@gmail.com,https://www.golfobasket.it/,NON RILEVATO,"Sezione femminile Serie A2 con vivaio strutturato U13-U15-U17, buona visibilità nel trapanese",B,DA INVIARE,,,NO,NO,,https://www.golfobasket.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2167,Sicilia,CT,Sport Club Gravina,M,nadefino@gmail.com,https://www.sportclubgravina.it/,LIMITATO,"38 anni di storia, U17 Eccellenza e U15 Silver campione regionale, vivaio consolidato",A,DA INVIARE,,,NO,NO,,https://www.sportclubgravina.it/contatti/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2168,Sicilia,ME,Orsa Basket Barcellona,M,orsabarcellona@tiscali.it,https://www.orsabasketbarcellona.com/,NON RILEVATO,"Centro minibasket strutturato, due squadre U17 Eccellenza (2010/2011), forte radicamento giovanile nel messinese",B,DA INVIARE,,,NO,NO,,https://www.orsabasketbarcellona.com/contact,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2169,Sicilia,RG,Pegaso Ragusa,F,info@pegasobasket.it,https://www.pegasobasket.it/,NON RILEVATO,"Storica società ragusana, settore giovanile attivo da anni, squadra Serie C femminile",A,DA INVIARE,,,NO,NO,,https://www.pegasobasket.it/Home/Contatti,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2170,Sicilia,CT,Polisportiva Alfa Catania,M/F,polisportivaalfa@gmail.com,https://www.bealfabasket.it/,NON RILEVATO,"Doppio settore M/F, squadra femminile campionessa regionale U15 Eccellenza, squadra maschile Serie C",B,DA INVIARE,,,NO,NO,,https://www.bealfabasket.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2171,Sicilia,TP,102 Basket Erice,F,posta@102basketerice.it,https://www.102basketerice.it/,LIMITATO,"Squadra femminile Serie C, presenza attiva su canali social e YouTube, buona comunicazione",A,DA INVIARE,,,NO,NO,,https://www.102basketerice.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2172,Sicilia,PA,Balarm Basket Palermo,M,info@balarmbasket.it,https://www.balarmbasket.it/,NON RILEVATO,"Settore giovanile Under 17 Eccellenza, minibasket strutturato, sede propria a Palermo",B,DA INVIARE,,,NO,NO,,https://www.balarmbasket.it/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2173,Sardegna,SS,Pallacanestro Olimpia Olbia,M/F,info@olimpiaolbia.it,https://www.olimpiaolbia.it/,NON RILEVATO,"Scuola basket e minibasket attiva da oltre 30 anni, settore giovanile con tecnici qualificati FIP",A,DA INVIARE,,,NO,NO,,https://www.olimpiaolbia.it/societa/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2174,Sardegna,OR,Nuovo Basket Oristano,M/F,nuovobasketoristano@gmail.com,https://www.nuovobasket.it/,NON RILEVATO,"Corsi di minibasket in due sedi (Oristano e Riola Sardo), direttore tecnico storico, buona base giovanile",B,DA INVIARE,,,NO,NO,,https://www.nuovobasket.it/societa/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2175,Sardegna,NU,Ichnos Basket Nuoro,M,ichnosbasket.segreteria@gmail.com,https://golee.it/clubs/asd-ichnos-basket-nuoro/,NON RILEVATO,"Società storica dal 1999, affiliata FIP, presente nel movimento cestistico nuorese",A,DA INVIARE,,,NO,NO,,https://golee.it/clubs/asd-ichnos-basket-nuoro/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2176,Sardegna,CA,Virtus Basket Cagliari,M,ssvirtuscagliari@tiscali.it,https://virtuscagliari.it/,NON RILEVATO,"Prima squadra e settore giovanile strutturato dall'Under13 all'Under19, presenza consolidata a Cagliari",B,DA INVIARE,,,NO,NO,,https://virtuscagliari.it/societa/,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2177,Lombardia,PV,Nuova Olympia Basket Voghera A.S.D.,M,info@olympiavoghera.it,https://www.olympiavoghera.it,NON RILEVATO,"Settore giovanile ampio (5-25 anni), club in crescita in Divisione Regionale 3",A,DA INVIARE,,,NO,NO,,https://golee.it/clubs/nuova-olympia-basket-voghera,2026-09-07,Verifica web 07/09/2026: email pubblica reperita su scheda societaria Golee; sito ufficiale non espone chiaramente la mail.
|
||||
2178,Lombardia,MB,Basket Foxes (Giussano),F,info@basketfoxes.com,https://instagram.com/basketfoxes,NON RILEVATO,"Squadra femminile Serie A2 + settore giovanile Under13-19, ottimo profilo per copertura partite",B,DA INVIARE,,,NO,NO,,https://www.comune.giussano.mb.it/it/page/basket-foxes-ssdrl,2026-09-07,Contatto email non strutturato nel file origine: vedi form contatti Instagram | Verifica web 07/09/2026: email pubblicata dal Comune di Giussano tra i contatti dell'associazione.
|
||||
2182,Lombardia,LC,Basket Lecco,M,000927@spes.fip.it,https://www.basketlecco.it,LIMITATO,"Settore minibasket completo (Esordienti/Aquilotti/Scoiattoli/Pulcini) + U13-U19 Silver/Gold, canale YouTube non strutturato",B,DA INVIARE,,,NO,NO,,https://www.basketlecco.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2187,Piemonte,TO,"Moncalieri Basketball (Pallacanestro/Libertas Moncalieri, Kangaroos Sport)",M/F,segreteria@moncalieribasketball.com,https://www.moncalieribasketball.com,NON RILEVATO,"Squadra femminile Serie A1 + minibasket ""Kangaroos"" + settore giovanile maschile, struttura articolata",A,DA INVIARE,,,NO,NO,,https://www.moncalieribasketball.com,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2190,Piemonte,TO,Basket Venaria (femminile),F,basketvenariafemminile@gmail.com,https://www.basketvenaria.it,NON RILEVATO,"Attiva su più campionati femminili + settore giovanile U13-U17, contatto email diretto",B,DA INVIARE,,,NO,NO,,https://www.basketvenaria.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2192,Piemonte,NO,A.S.D. Basket Club Trecate,M,bctrecate@gmail.com,https://instagram.com/basketclubtrecate,NON RILEVATO,"Serie D regionale, provincia di Novara, realtà di provincia mai contattata",B,DA INVIARE,,,NO,NO,,https://www.basketclubtrecate.it/,2026-09-07,Contatto email non strutturato nel file origine: vedi form contatti Instagram | Verifica web 07/09/2026: email pubblicata sul sito ufficiale e confermata dal Comune di Trecate.
|
||||
2193,Liguria,GE,Futura Genova A.S.D.,M/F,info@futuragenova.it,https://www.futuragenova.it,NON RILEVATO,"Settore giovanile completo (Minibasket, Esordienti, U15/U17/U19), club in crescita dal 2016",A,DA INVIARE,,,NO,NO,,https://futuragenova.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2195,Liguria,IM,Sport Club Ventimiglia Basket,M,scvbasket@gmail.com,https://instagram.com/scventimigliabasket,NON RILEVATO,"Unica realtà cestistica strutturata di Ventimiglia, città di confine con bacino anche francese",A,DA INVIARE,,,NO,NO,,https://www.comune.ventimiglia.im.it/it-it/download/registro-associazioni-2025-2867-98-1508-1f7f56a9e557e51588a7b8c354d8df0b,2026-09-07,Contatto email non strutturato nel file origine: vedi form contatti Instagram | Verifica web 07/09/2026: email reperita nel registro comunale associazioni di Ventimiglia.
|
||||
2197,Veneto,VR,ASD Legnago Basket,M/F,info@legnagobasket.it,https://legnagobasket.it,NON RILEVATO,"Da Microbasket a DR1, settore giovanile strutturato, nessuna diretta trovata sul sito",A,DA INVIARE,,,NO,NO,,https://legnagobasket.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2198,Veneto,VR,Tobia Basket (Cavaion Veronese),M/F,info@tobiabasket.it,https://tobiabasket.it,NON RILEVATO,"6 squadre minibasket + 12 squadre U13-U19 M/F, area Lago di Garda, buon bacino utenti",B,DA INVIARE,,,NO,NO,,https://tobiabasket.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2199,Veneto,VR,A.S. Cestistica Verona,M,cestisticagiovanili@gmail.com,https://cestisticaverona.it,LIMITATO,"Prima squadra Serie C + giovanili; il sito invita a seguire ""le dirette delle partite"" via social",A,DA INVIARE,,,NO,NO,,https://cestisticaverona.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2205,Emilia-Romagna,RE,Pallacanestro Scandiano,M/F,info@pallacanestroscandiano.it,https://www.pallacanestroscandiano.it,NON RILEVATO,Serie C maschile + Serie B femminile + U13-U19 e minibasket: doppio target commerciale M/F,A,DA INVIARE,,,NO,NO,,https://www.pallacanestroscandiano.it/contatti.html,2026-09-07,(pallacanestroscandiano.it) | Verifica web 07/09/2026: email pubblicata nella pagina contatti del sito ufficiale.
|
||||
2207,Emilia-Romagna,RA,Hellas Basket Cervia,M,hellas@basketcerviacesenatico.it,https://www.scuolabasket.it/scuole/scheda/Ravenna-CERVIA/HELLAS-BASKET-CERVIA-A-DIL/2940,NON RILEVATO,"Club costiero (Cervia), bacino turistico-sportivo interessante",A,DA INVIARE,,,NO,NO,,https://golee.it/clubs/hellas-basket-cervia,2026-09-07,Contatto email non strutturato nel file origine: tel. 0544/72467 (email non reperita) | Verifica web 07/09/2026: email pubblica reperita su scheda societaria Golee.
|
||||
2217,Lazio,LT,A.D. Basket Serapo '85 Gaeta,M,segreteria@basketserapo.it,https://www.basketserapo.it,NON RILEVATO,"Settore giovanile ampio (U16-U20) e prima squadra regionale a Gaeta, nessuna diretta sul sito: buon target per pacchetto streaming partite",A,DA INVIARE,,,NO,NO,,https://www.basketserapo.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2218,Lazio,RM,A.S.D. Bracciano Basket 1999,M,segreteria@braccianobasket.it,https://www.braccianobasket.it,LIMITATO,"Attiva su più categorie (Serie C, U19-U16) con canale YouTube ma senza streaming strutturato delle partite: opportunità di formalizzare le dirette",B,DA INVIARE,,,NO,NO,,https://braccianobasket.it/contatti,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2219,Lazio,RM,A.S.D. Basket Città di Ladispoli 1998,M/F,info@basketladispoli.it,https://www.basketladispoli.it,NON RILEVATO,"Settore femminile strutturato (U13-U15, staff tecnico dedicato) oltre al maschile, nessuno streaming rilevato: buon fit per contenuti misti giovanili/femminili",A,DA INVIARE,,,NO,NO,,https://www.basketladispoli.it/contatti,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2221,Lazio,VT,A.S.D. Ants Basket Viterbo,F,info@basketants.it,https://www.basketants.it,NON RILEVATO,"Società femminile storica di Viterbo, milita in Serie B con una Coppa Italia vinta, nessuno streaming rilevato sul sito ufficiale: alto potenziale per copertura partite femminili",A,DA INVIARE,,,NO,NO,,https://www.basketants.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2224,Toscana,AR,Polisportiva A. Galli Basket (San Giovanni Valdarno),F,polisportiva.galli@gmail.com,https://www.polisbkgalli.it,LIMITATO,"Squadra di Serie A2 femminile 2026/27 in città medio-piccola non coperta da tv nazionale, canale YouTube proprio non strutturato per dirette: forte prospect streaming",B,DA INVIARE,,,NO,NO,,https://www.legabasketfemminile.it/it/squadre/san-giovanni-valdarno,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2225,Toscana,PI,ASD Basket Volterra,M,segreteria@basketvolterra.it,http://www.basketvolterra.it,NON RILEVATO,"Società storica (dal 1978) di città d'arte non capoluogo, Divisione Regionale 2 e settore giovanile ampio, nessuna diretta attuale: buon prospect base",A,DA INVIARE,,,NO,NO,,http://www.basketvolterra.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2227,Toscana,SI,A.S.D. Costone Siena Fides 1904,M,costone.fides@gmail.com,https://www.costone.it/basket-maschile,NON RILEVATO,"Prima squadra in B Interregionale (livello semi-pro) 2026/27, nessuno streaming ufficiale visibile sul sito: candidato ideale per pacchetto dirette",A,DA INVIARE,,,NO,NO,,https://www.costone.it/basket-maschile,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2230,Toscana,PO,Union Basket Prato (settori M e Rosa F),M/F,info@pallacanestroprato.com,https://www.pallacanestroprato.com,NON RILEVATO,"Società con settore sia maschile che femminile (circa 800 tesserati), nessuna diretta streaming rilevata sul sito: ottimo fit per proposta multi-squadra",B,DA INVIARE,,,NO,NO,,https://www.pallacanestroprato.com,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2231,Marche,PU,A.S.D. Nuovo Basket Fossombrone (Bartoli Mechanics),M,info@metaurobasket.academy,https://metaurobasket.academy,LIMITATO,"Attiva in Serie B Interregionale maschile 2026/27, canale YouTube/social ma nessuna diretta strutturata: buon target per proporre streaming",A,DA INVIARE,,,NO,NO,,https://metaurobasket.academy,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2232,Marche,FM,A.S.D. Virtus Porto San Giorgio 2008,M,virtuspsg@gmail.com,https://www.virtuspsg.it,LIMITATO,"Milita in Divisione Regionale 1, canale YouTube collegato ma diretta non regolare/strutturata: opportunità commerciale per MatchLiveTV",B,DA INVIARE,,,NO,NO,,https://www.virtuspsg.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2233,Marche,MC,A.S.D. Fe.Ba Civitanova,F,asdfebacivitanova@gmail.com,https://asdfebacivitanova.com,LIMITATO,"Squadra femminile di Serie A2, categoria di livello medio-alto che richiede copertura partite non ancora offerta su canali propri",A,DA INVIARE,,,NO,NO,,https://asdfebacivitanova.com/contatti,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2234,Marche,AN,A.S.D. Pietralacroce 73 (sezione basket),M,pietralacroce73@gmail.com,https://www.pietralacroce73.it,NON RILEVATO,"Polisportiva con sezione basket in Prima Divisione, nessuna menzione di dirette sul sito: potenziale per introdurre servizio streaming da zero",B,DA INVIARE,,,NO,NO,,https://www.pietralacroce73.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2236,Abruzzo,AQ,A.S.D. Basket Centro Abruzzo Sulmona (BCA Sulmona),M,info@bcasulmona.xyz,https://www.bcasulmona.xyz,LIMITATO,"Club storico (25+ anni) attivo da minibasket a C Silver, canale YouTube ma nessuna diretta strutturata: buon target per copertura streaming regolare del campionato regionale",B,DA INVIARE,,,NO,NO,,https://www.bcasulmona.xyz,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2237,Abruzzo,PE,Polisportiva Yale Pescara A.S.D.,M/F,asyalebasketpescara@yahoo.it,https://www.facebook.com/polisportiva.yalepescara,NON RILEVATO,"Polisportiva con squadre femminili (Serie C/B) e maschile (Serie D), nessuna diretta strutturata rilevata: target multi-categoria per pacchetto streaming",A,DA INVIARE,,,NO,NO,,https://www.comune.pescara.it/app/uploads/2025/07/CATALOGO_PESCARA_SOLIDALE__aggiornato_al_01.12.2022.pdf,2026-09-07,"Contatto email non strutturato nel file origine: vedi form contatti/pagina Facebook | Verifica web 07/09/2026: email reperita in documento del Comune di Pescara; fonte non recentissima, usare con cautela."
|
||||
2239,Abruzzo,TE,Pallacanestro Roseto SSD,M,info@pallacanestroroseto.it,https://www.pallacanestrorosetossd.com,SÌ,"Club professionistico di Serie A2 (PalaMaggetti, Roseto degli Abruzzi), partite già coperte da LNP Pass: utile come case study alto profilo o contenuti locali complementari",A,DA INVIARE,,,NO,NO,,https://www.pallacanestrorosetossd.com,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2240,Umbria,PG,Virtus Assisi,M/F,assisivirtus@gmail.com,https://www.virtusassisi.it,NON RILEVATO,"Società strutturata con settori Serie C, giovanile, minibasket e femminile: buon prospect multi-categoria per copertura streaming",B,DA INVIARE,,,NO,NO,,https://www.virtusassisi.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2241,Umbria,PG,A.D. Basket Gubbio,M/F,info@basketgubbio.it,https://www.basketgubbio.it,LIMITATO,Ha canale YouTube con video highlights ma nessuna diretta streaming strutturata: opportunità concreta per proporre servizio dirette live,A,DA INVIARE,,,NO,NO,,https://www.basketgubbio.it/page/822864105575/contatti,2026-09-07,(basketgubbio.it/page/822864105575) | Verifica web 07/09/2026: email generale pubblicata nella pagina contatti ufficiale.
|
||||
2242,Umbria,PG,A.S.D. Atomika Basket Spoleto,M,atomikabasketspoleto@libero.it,https://atomikabasketspoleto.com,NON RILEVATO,"Club maschile attivo in Divisione Regionale 1 Umbria, sito proprio ma nessuna sezione streaming: opportunità per introdurre copertura live delle partite",B,DA INVIARE,,,NO,NO,,https://atomikabasketspoleto.com/contatto/,2026-09-07,(atomikabasketspoleto.com) o pagina Facebook ufficiale | Verifica web 07/09/2026: email pubblicata nella pagina contatti ufficiale.
|
||||
2243,Campania,CE,Juvecaserta 2021,M,info@juvecaserta2021.it,https://www.juvecaserta2021.it,LIMITATO,"Storica società di Serie A2, forte radicamento in provincia di Caserta",A,DA INVIARE,,,NO,NO,,https://www.juvecaserta2021.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2244,Campania,NA,Pallacanestro Antoniana,M,info@pallacanestroantoniana.it,https://www.facebook.com/pallacanestroantoniana,NON RILEVATO,"Squadra di Serie B Interregionale a Sant'Antonio Abate (NA), settore giovanile attivo",B,DA INVIARE,,,NO,NO,,https://www.pallacanestroantoniana.it/contatti,2026-09-07,Verifica web 07/09/2026: email pubblicata nella pagina contatti ufficiale.
|
||||
2247,Campania,SA,Salerno Basket '92,F,asdsalernobasket@gmail.com,https://www.salernobasket92.it,LIMITATO,"Storica società femminile di Serie A2 (fondata 1992), forte identità cittadina, ottima per outreach femminile",A,DA INVIARE,,,NO,NO,,https://www.salernobasket92.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2248,Campania,BN,Virtus Academy Benevento,F,info@virtusacademybenevento.com,https://www.facebook.com/virtusacademybenevento,LIMITATO,"Squadra femminile di Serie A2 a Benevento, distinta dalla Cestistica Benevento maschile già usata",B,DA INVIARE,,,NO,NO,,https://virtusacademybenevento.com/,2026-09-07,Verifica web 07/09/2026: email pubblicata sul sito ufficiale; disponibile anche virtusacademybn@libero.it.
|
||||
2249,Puglia,FG,Cestistica San Severo,M,marketing@cestisticasansevero.com,https://www.cestisticasansevero.com,SÌ,"Serie B con streaming strutturato via LNP Pass, molto attiva su FB/IG/YouTube/TikTok",A,DA INVIARE,,,NO,NO,,https://www.cestisticasansevero.com,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2253,Calabria,RC,Dierre Basketball Reggio Calabria,M,dierrebasketballrc@gmail.com,https://www.facebook.com/DierreBasketballRC,NON RILEVATO,"Club di Serie C a Reggio Calabria, distinto dalla Pallacanestro Viola già usata",A,DA INVIARE,,,NO,NO,,https://www.reggioacanestro.com/a-dierre-presenta-la-tessera-del-tifoso/,2026-09-07,Verifica web 07/09/2026: contatto generale pubblico; per minibasket disponibile dierreminibasket@gmail.com.
|
||||
2258,Sicilia,AG,A.S.D. Ribera Knights Basketball,M,info@riberaknights.com,https://riberaknights.com,SÌ,Fa già dirette delle partite sul proprio canale YouTube (@RiberaKnightsBasketball): target ideale per proporre upgrade a piattaforma professionale MatchLiveTV,B,DA INVIARE,,,NO,NO,,https://riberaknights.com/contacts,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2261,Sicilia,TP,Nuova Pallacanestro Marsala,M,nuovapallacanestromarsala@gmail.com,https://pallacanestromarsala.it,NON RILEVATO,"Sito societario curato e attivo, nessun servizio streaming: buon prospect per pacchetto completo",A,DA INVIARE,,,NO,NO,,https://pallacanestromarsala.it/contatti,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2262,Sicilia,RG,A.Dil. Olympia Basket Comiso,M,olympiabasketcomiso@gmail.com,https://olympiabasketcomiso.it,NON RILEVATO,"Serie C radicata a Comiso (RG), nessuna copertura video trovata",B,DA INVIARE,,,NO,NO,,https://playbasket.it/sicilia/serie-c,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2267,Sardegna,SS,Mercede Basket Alghero,F,mercedebasket@gmail.com,https://facebook.com/mercede.alghero,NON RILEVATO,"Squadra femminile Serie A2 (livello alto per la Sardegna), nessuna diretta propria rilevata",A,DA INVIARE,,,NO,NO,,https://www.algherofamiglia.it/asd-mercede-basket-alghero/,2026-09-07,/social | Verifica web 07/09/2026: email pubblicata nella rete territoriale di Alghero.
|
||||
2269,Lombardia,BG,Sebino Basket A.S.D.,M,039076@spes.fip.it,https://sebinobasket.it,NON RILEVATO,"Club di Villongo/Sarnico con sito attivo e presenza social regolare, buon target per pacchetto streaming base",A,DA INVIARE,,,NO,NO,,https://sebinobasket.it/contatti,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2270,Lombardia,BG,Romano Lombardo Basket A.S.D.,M,info@romanobasket.it,https://www.scuolabasket.it/scuole/scheda/Bergamo-ROMANO-DI-LOMBARDIA/ROMANO-LOMBARDO-BASKET-ASD/3665,NON RILEVATO,"Società affiliata FIP attiva a Romano di Lombardia, nessuna presenza digitale strutturata: opportunità di ingresso senza concorrenza streaming",B,DA INVIARE,,,NO,NO,,https://www.romanobasket.it/societa/,2026-09-07,Contatto email non strutturato nel file origine: tel. 392 8325355 (email non reperita) | Verifica web 07/09/2026: email pubblicata sul sito ufficiale.
|
||||
2276,Lombardia,CR,A.S.D. Soresinese Basket 06,M,soresinesebasket06@hotmail.it,https://www.scuolabasket.it/scuole/scheda/Cremona-SORESINA/ASD-SORESINESE-BASKET-06/1443,NON RILEVATO,"Club di Soresina, nessuna presenza web rilevata",B,DA INVIARE,,,NO,NO,,https://golee.it/clubs/asd-soresinese-basket-06/,2026-09-07,Contatto email non strutturato nel file origine: tel. 0374/343344 (email non reperita) | Verifica web 07/09/2026: email pubblica confermata anche da documentazione locale recente.
|
||||
2277,Lombardia,LO,Basket Lodi A.DIL.,M,info@basketlodi.it,http://www.basketlodi.it,NON RILEVATO,"Sito ufficiale attivo e recente: società strutturata, target prioritario per proposta streaming",A,DA INVIARE,,,NO,NO,,https://basketlodi.it/contatti-e-iscrizioni/,2026-09-07,Verifica web 07/09/2026: email ufficiale confermata anche dal registro comunale delle associazioni.
|
||||
2284,Piemonte,VB,A.S.D. Virtus Verbania,M/F,info@virtusverbania.it,https://virtusverbania.it,LIMITATO,Sito curato con canale YouTube proprio ma senza streaming live delle gare strutturato: ottimo fit commerciale,B,DA INVIARE,,,NO,NO,,https://virtusverbania.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2299,Emilia-Romagna,FE,ASD Matilde Basket Bondeno,F,info.matildebasket@gmail.com,https://www.scuolabasket.it/scuole/scheda/Ferrara-BONDENO/ASD-MATILDE-BASKET-BONDENO/2028,NON RILEVATO,"Sezione femminile a Bondeno, categoria minore, nessuna copertura streaming",A,DA INVIARE,,,NO,NO,,https://www.matildebasket.com/news/,2026-09-07,Contatto email non strutturato nel file origine: tel. 0532 896691 (email non reperita) | Verifica web 07/09/2026: email pubblicata sul sito ufficiale.
|
||||
2300,Emilia-Romagna,FE,ASD Cestistica Argenta,M,cestisticaargenta@gmail.com,https://www.scuolabasket.it/scuole/scheda/Ferrara-ARGENTA/ASD-CESTISTICA-ARGENTA/1939,NON RILEVATO,"Comune di Argenta, bacino di pubblico locale, nessuna presenza digitale rilevata",B,DA INVIARE,,,NO,NO,,https://cestisticaargenta.com/contatti/,2026-09-07,Contatto email non strutturato nel file origine: tel. 338 7041736 (email non reperita) | Verifica web 07/09/2026: email pubblicata nella pagina contatti ufficiale.
|
||||
2303,Emilia-Romagna,RA,A.S.D. Basket Club Russi,M,basketclubrussiasd@gmail.com,https://www.scuolabasket.it/scuole/scheda/Ravenna-RUSSI/ASD-BASKET-CLUB-RUSSI/888,NON RILEVATO,"Comune di Russi, nessuna presenza streaming/sito verificabile",A,DA INVIARE,,,NO,NO,,https://www.comune.russi.ra.it/argomenti/associazioni/elenco-delle-associazioni-del-territorio-del-comune-di-russi/associazioni/associazioni-1/elenco-associazioni-e-societa-sportive,2026-09-07,Contatto email non strutturato nel file origine: tel. 338 8210353 (email non reperita) | Verifica web 07/09/2026: email pubblicata nell'elenco associazioni del Comune di Russi.
|
||||
2319,Toscana,PT,Pistoia Basket 2000,M,info@pistoiabasket2000.com,https://www.pistoiabasket2000.com,LIMITATO,"Club maschile di vertice (Serie A2, LumoSquare 3900 posti), settore giovanile strutturato, canali YouTube/social attivi ma nessuna diretta streaming propria sul sito",A,DA INVIARE,,,NO,NO,,https://pistoiabasket2000.com,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2320,Toscana,FI,Florence Basket,F,ciao@florencebasket.com,https://www.florencebasket.com,NON RILEVATO,"Storica società femminile di Firenze (dal 1974, Serie B), ampio settore minibasket (~90 tesserati, 10 squadre), nessuno streaming rilevato",B,DA INVIARE,,,NO,NO,,https://florencebasket.com,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2321,Toscana,LI,Basket Golfo Piombino,M/F,051196@spes.fip.it,https://www.basketgolfopiombino.it,NON RILEVATO,"Club misto con squadra maschile Serie B e femminile Serie B più ""Golfo Academy"" giovanile; nessuno streaming ufficiale, buon fit commerciale per doppio settore",A,DA INVIARE,,,NO,NO,,https://basketgolfopiombino.it/contatti,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2322,Toscana,SI,Pallacanestro Femminile Costone,F,info@adpfcostone.it,https://www.adpfcostone.it,NON RILEVATO,"Storica società femminile di Siena (dal 1954, ex Serie A2), gioca al PalaOrlandi, nessuno streaming rilevato",B,DA INVIARE,,,NO,NO,,https://www.costone.it/la-piaggia/articolo/4097-next-gen-project-al-costone-femminile,2026-09-07,Verifica web 07/09/2026: email pubblicata in comunicazione del club.
|
||||
2323,Toscana,LI,Basket Cecina,M,segreteria@basketcecina.org,https://www.basketcecina.com,NON RILEVATO,"Club maschile Serie B Interregionale, settore giovanile Under 15 Eccellenza, giocatori in rappresentative regionali; nessuno streaming ufficiale rilevato",A,DA INVIARE,,,NO,NO,,https://basketcecina.com,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2324,Toscana,FI,USE Basket Empoli,M/F,usebasket@usebasket.it,https://www.usebasket.it,LIMITATO,"Club strutturato con maschile Serie B (Computer Gross) e femminile Serie A2 (Use Rosa Scotti), canale YouTube proprio ma nessuna diretta streaming propria, solo canali di lega",B,DA INVIARE,,,NO,NO,,https://www.usebasket.it/assets/pdf/iscrizione-24-25/moduli-iscrizione-minibasket.pdf,07/09/2026,Verifica web 07/09/2026: reperita email pubblica USE Basket Empoli su documento ufficiale della società.
|
||||
2325,Toscana,LU,Vela Basket Viareggio 1946,M,velabasket012055@gmail.com,https://www.velabasket.com,SÌ,"Storico club (80 anni nel 2026) con presenza attiva su Twitch oltre a YouTube/Instagram/TikTok: già sensibile allo streaming, target caldo per proporre MatchLiveTV",A,DA INVIARE,,,NO,NO,,https://velabasket.com,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2326,Marche,AN,A.S.D. Basket Girls Ancona,F,info@basketgirlsancona.it,https://www.scuolabasket.it/scuole/provincia/Ancona/1,NON RILEVATO,"Sezione femminile dedicata, capoluogo, nessuna evidenza streaming",B,DA INVIARE,,,NO,NO,,https://www.tuttiaffari.com/basket-girls-ancona-071-286-4646,2026-09-07,Contatto email non strutturato nel file origine: non reperita | Verifica web 07/09/2026: email pubblica reperita e coerente con il dominio del sito ufficiale.
|
||||
2327,Marche,AN,A.S.D. Pallacanestro Senigallia,M,info@pallacanestrosenigallia.it,,NON RILEVATO,Città costiera con buon bacino sportivo,A,DA INVIARE,,,NO,NO,,https://www.comune.senigallia.an.it/vivere-il-comune/territorio/le-associazioni-sportive-basket/,2026-09-07,"Contatto email non strutturato nel file origine: tel. 071 7921184 (email non reperita) | Sito/profilo non normalizzabile dal file origine: Via Capanna 62, Senigallia | Verifica web 07/09/2026: email pubblicata dal Comune di Senigallia."
|
||||
2331,Abruzzo,CH,Chieti Basket 1974,M,info@chietibasket1974.it,https://it.wikipedia.org/wiki/Chieti_Basket_1974,NON RILEVATO,"Club storico di Chieti (Serie B Nazionale), gioca al PalaTricalle (2.600 posti), buon bacino cittadino, nessuno streaming proprio rilevato",A,DA INVIARE,,,NO,NO,,https://www.valetalgei.com/wp-content/uploads/2022/09/guida_lnp2022.pdf,2026-09-07,"""Chieti Basket 1974"" | Verifica web 07/09/2026: email reperita in guida LNP; fonte storica, verificare deliverability prima di invio massivo."
|
||||
2332,Abruzzo,TE,Panthers Roseto (ASD),F,giovanili@panthersroseto.it,https://panthersroseto.it,NON RILEVATO,"Prima squadra femminile abruzzese in Serie A1 (promozione storica 2024-25), forte richiamo mediatico regionale, nessuno streaming proprietario rilevato",B,DA INVIARE,,,NO,NO,,https://golee.it/clubs/asd-panthers-roseto,2026-09-07,"Verifica web 07/09/2026: contatto specifico settore giovanile, particolarmente adatto alla campagna."
|
||||
2334,Abruzzo,AQ,Avezzano Basket A.Dil.,M,avezzanobasket@gmail.com,https://www.scuolabasket.it/scuole/provincia/L-Aquila/45,NON RILEVATO,"Città media della Marsica, buon bacino d'utenza",B,DA INVIARE,,,NO,NO,,https://it.wikivoyage.org/wiki/Avezzano,2026-09-07,"Contatto email non strutturato nel file origine: non reperita | Verifica web 07/09/2026: email pubblica reperita; fonte secondaria, consigliato test di deliverability."
|
||||
2338,Campania,NA,PSA Pallacanestro Sant'Antimo (oggi PSA Napoli Est),M,info@psabasket.it,https://psabasket.it,NON RILEVATO,"Club di Serie B Nazionale a Napoli, buon bacino locale, nessuna copertura streaming propria",B,DA INVIARE,,,NO,NO,,https://psabasket.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2353,Puglia,BR,Cestistica Normanna,M/F,cestisticanormanna@libero.it,https://www.scuolabasket.it/scuole/scheda/Brindisi-San-Vito-Dei-Normanni/Cestistica-Normanna/4255,NON RILEVATO,Scuola basket di San Vito dei Normanni (BR),A,DA INVIARE,,,NO,NO,,https://cestistica-normanna.webnode.it/,2026-09-07,"Contatto email non strutturato nel file origine: tel. 347/4873318 (email non reperita) | Verifica web 07/09/2026: email pubblicata sul sito della società, ma sito datato; usare con cautela."
|
||||
2359,Sicilia,CT,S.S. Basket Giarre A.S.D.,M,info@giarrebasket.it,https://www.giarrebasket.it,NON RILEVATO,"Serie C maschile con obiettivo promozione (""il sogno si chiama Serie C""), sito proprio con contatti diretti e telefono: ottimo target commerciale",A,DA INVIARE,,,NO,NO,,https://giarrebasket.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2360,Sicilia,CL,S.B. Basket School Gela,M,basketschoolgela@gmail.com,https://www.facebook.com/gelabasket,NON RILEVATO,"Squadra capolista nel campionato regionale, forte seguito locale, nessuno streaming proprio",B,DA INVIARE,,,NO,NO,,https://playbasket.it/sicilia/club.php?obj=7606,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2362,Sicilia,RG,Virtus Eirene Ragusa (Passalacqua Ragusa),F,basket.eirene@gmail.com,https://www.passalacquabasket.it,LIMITATO,"Squadra femminile di punta in Sicilia (Serie A2 F, ex vincitrice Coppa Italia 2016), sito e contatti diretti, streaming lega solo su prima squadra",B,DA INVIARE,,,NO,NO,,https://passalacquabasket.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
2365,Sardegna,SS,"Dinamo Women (Polisportiva Dinamo Sassari, sez. femminile)",F,info@dinamobasket.com,https://www.dinamobasket.com,LIMITATO,"Massima serie femminile italiana (Serie A1 F), grande visibilità regionale, streaming di lega solo su prima squadra: partnership di prestigio per contenuti extra",A,DA INVIARE,,,NO,NO,,https://www.dinamobasket.com/club/info-contatti,2026-09-07,"Verifica web 07/09/2026: contatto generale della Polisportiva Dinamo pubblicato sul sito ufficiale, valido anche come primo contatto per la sezione Women."
|
||||
2366,Sardegna,CA,CUS Cagliari (sezione basket femminile),F,cus@unica.it,https://www.cuscagliari.it,LIMITATO,"Squadra universitaria in Serie A2 Femminile, sito ufficiale con contatti diretti, recruiting giocatrici in corso, streaming di lega parziale",B,DA INVIARE,,,NO,NO,,https://cuscagliari.it,2026-09-07,"Ricontrollo 07/09/2026: indirizzo già presente e sintatticamente valido. La fonte precedente è stata mantenuta salvo i casi in cui è stata reperita una conferma ufficiale aggiornata, indicata separatamente."
|
||||
|
@@ -0,0 +1,11 @@
|
||||
class AddEmailInvalidToOrganizationsAndContacts < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
add_column :organizations, :email_invalid, :boolean, default: false, null: false
|
||||
add_column :organizations, :bounced_email, :string
|
||||
add_column :contacts, :email_invalid, :boolean, default: false, null: false
|
||||
add_column :contacts, :bounced_email, :string
|
||||
|
||||
add_index :organizations, :email_invalid
|
||||
add_index :contacts, :email_invalid
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
class AddImapSettingsToMailIdentities < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
add_column :mail_identities, :imap_host, :string
|
||||
add_column :mail_identities, :imap_port, :integer, default: 993, null: false
|
||||
add_column :mail_identities, :imap_enabled, :boolean, default: true, null: false
|
||||
end
|
||||
end
|
||||
Generated
+10
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.1].define(version: 2026_09_02_220000) do
|
||||
ActiveRecord::Schema[8.1].define(version: 2026_09_08_184500) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_catalog.plpgsql"
|
||||
|
||||
@@ -79,9 +79,11 @@ ActiveRecord::Schema[8.1].define(version: 2026_09_02_220000) do
|
||||
end
|
||||
|
||||
create_table "contacts", force: :cascade do |t|
|
||||
t.string "bounced_email"
|
||||
t.datetime "created_at", null: false
|
||||
t.bigint "created_by_id"
|
||||
t.string "email"
|
||||
t.boolean "email_invalid", default: false, null: false
|
||||
t.string "first_name", null: false
|
||||
t.string "last_name", null: false
|
||||
t.string "mobile"
|
||||
@@ -94,6 +96,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_09_02_220000) do
|
||||
t.datetime "updated_at", null: false
|
||||
t.bigint "updated_by_id"
|
||||
t.index ["email"], name: "index_contacts_on_email"
|
||||
t.index ["email_invalid"], name: "index_contacts_on_email_invalid"
|
||||
t.index ["last_name", "first_name"], name: "index_contacts_on_last_name_and_first_name"
|
||||
t.index ["organization_id"], name: "index_contacts_on_organization_id"
|
||||
t.index ["phone"], name: "index_contacts_on_phone"
|
||||
@@ -107,6 +110,9 @@ ActiveRecord::Schema[8.1].define(version: 2026_09_02_220000) do
|
||||
t.string "encryption", default: "starttls", null: false
|
||||
t.string "from_email", null: false
|
||||
t.string "from_name", null: false
|
||||
t.boolean "imap_enabled", default: true, null: false
|
||||
t.string "imap_host"
|
||||
t.integer "imap_port", default: 993, null: false
|
||||
t.string "name", null: false
|
||||
t.string "reply_to"
|
||||
t.string "smtp_authentication", default: "plain", null: false
|
||||
@@ -247,12 +253,14 @@ ActiveRecord::Schema[8.1].define(version: 2026_09_02_220000) do
|
||||
create_table "organizations", force: :cascade do |t|
|
||||
t.string "address"
|
||||
t.bigint "assigned_user_id"
|
||||
t.string "bounced_email"
|
||||
t.string "city"
|
||||
t.text "commercial_fit"
|
||||
t.string "country", default: "Italia", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.bigint "created_by_id"
|
||||
t.string "email"
|
||||
t.boolean "email_invalid", default: false, null: false
|
||||
t.string "lead_source"
|
||||
t.string "legal_name"
|
||||
t.integer "list_position"
|
||||
@@ -276,6 +284,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_09_02_220000) do
|
||||
t.index ["city"], name: "index_organizations_on_city"
|
||||
t.index ["country"], name: "index_organizations_on_country"
|
||||
t.index ["email"], name: "index_organizations_on_email"
|
||||
t.index ["email_invalid"], name: "index_organizations_on_email_invalid"
|
||||
t.index ["lead_source"], name: "index_organizations_on_lead_source"
|
||||
t.index ["list_position"], name: "index_organizations_on_list_position"
|
||||
t.index ["name"], name: "index_organizations_on_name"
|
||||
|
||||
+78
-2
@@ -9,8 +9,9 @@ module EminuxCrmMcp
|
||||
1. list_projects per i project_code (matchlivetv, riskmeter, cardoo, …).
|
||||
2. today per capire cosa fare oggi.
|
||||
3. search / get_organization per il contesto.
|
||||
4. create_activity, complete_task, create_task, update_opportunity_stage per agire.
|
||||
4. create_activity, complete_task, create_task, update_opportunity_stage, update_organization_email, check_mail_bounces per agire.
|
||||
Non inviare mailing, non cancellare record, non gestire utenti.
|
||||
Per i bounce usa check_mail_bounces (legge IMAP dalle stesse credenziali SMTP della MailIdentity).
|
||||
TEXT
|
||||
|
||||
module_function
|
||||
@@ -50,7 +51,9 @@ module EminuxCrmMcp
|
||||
CreateTask,
|
||||
CompleteTask,
|
||||
CreateActivity,
|
||||
UpdateOpportunityStage
|
||||
UpdateOpportunityStage,
|
||||
UpdateOrganizationEmail,
|
||||
CheckMailBounces
|
||||
]
|
||||
)
|
||||
end
|
||||
@@ -254,4 +257,77 @@ module EminuxCrmMcp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class UpdateOrganizationEmail < MCP::Tool
|
||||
description "Aggiorna l'email di un'organizzazione (e del contatto principale): marca bounce/non valida, sostituisci con un indirizzo trovato, annota in timeline."
|
||||
input_schema(
|
||||
properties: {
|
||||
project_code: { type: "string" },
|
||||
organization_id: { type: "integer" },
|
||||
email: { type: "string", description: "Nuova email (se trovata). Lascia vuoto per solo marcare non valida." },
|
||||
email_invalid: { type: "boolean", description: "true = non usare in mailing; false se hai messo un'email nuova" },
|
||||
bounced_email: { type: "string", description: "Indirizzo che ha generato il bounce" },
|
||||
bounce_reason: { type: "string" },
|
||||
contact_id: { type: "integer", description: "Contatto specifico; default = primario" },
|
||||
website: { type: "string" },
|
||||
activity_subject: { type: "string" },
|
||||
activity_description: { type: "string" }
|
||||
},
|
||||
required: %w[project_code organization_id]
|
||||
)
|
||||
|
||||
class << self
|
||||
def call(project_code:, organization_id:, email: nil, email_invalid: nil, bounced_email: nil,
|
||||
bounce_reason: nil, contact_id: nil, website: nil, activity_subject: nil,
|
||||
activity_description: nil, server_context: nil)
|
||||
attrs = {
|
||||
organization_id: organization_id,
|
||||
email: email,
|
||||
email_invalid: email_invalid,
|
||||
bounced_email: bounced_email,
|
||||
bounce_reason: bounce_reason,
|
||||
contact_id: contact_id,
|
||||
website: website,
|
||||
activity_subject: activity_subject,
|
||||
activity_description: activity_description
|
||||
}.compact
|
||||
# Permetti email: null esplicito solo se chiave presente via email=""
|
||||
attrs[:email] = email if !email.nil?
|
||||
EminuxCrmMcp.json_response(
|
||||
EminuxCrmMcp.session_from(server_context).update_organization_email(project_code, attrs)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class CheckMailBounces < MCP::Tool
|
||||
description "Legge i bounce/DSN dalla casella IMAP dell'account email CRM (stesse credenziali SMTP della MailIdentity). Arricchisce con match alle organizzazioni del progetto."
|
||||
input_schema(
|
||||
properties: {
|
||||
project_code: { type: "string", description: "Progetto per matchare le società (es. matchlivetv)" },
|
||||
from_email: { type: "string", description: "Mittente account, es. info@matchlivetv.it (default)" },
|
||||
mail_identity_id: { type: "integer" },
|
||||
since_days: { type: "integer", description: "Giorni indietro da controllare (default 14)" },
|
||||
mailbox: { type: "string", description: "Cartella IMAP, default INBOX" },
|
||||
limit: { type: "integer", description: "Max messaggi bounce da analizzare" }
|
||||
},
|
||||
required: %w[project_code]
|
||||
)
|
||||
|
||||
class << self
|
||||
def call(project_code:, from_email: nil, mail_identity_id: nil, since_days: nil,
|
||||
mailbox: nil, limit: nil, server_context: nil)
|
||||
attrs = {
|
||||
from_email: from_email,
|
||||
mail_identity_id: mail_identity_id,
|
||||
since_days: since_days,
|
||||
mailbox: mailbox,
|
||||
limit: limit
|
||||
}.compact
|
||||
EminuxCrmMcp.json_response(
|
||||
EminuxCrmMcp.session_from(server_context).check_mail_bounces(project_code, attrs)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+78
-1
@@ -254,6 +254,81 @@ module EminuxCrmMcp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class UpdateOrganizationEmail < MCP::Tool
|
||||
description "Aggiorna email organizzazione/contatto: marca bounce, sostituisci indirizzo, annota timeline."
|
||||
input_schema(
|
||||
properties: {
|
||||
project_code: { type: "string" },
|
||||
organization_id: { type: "integer" },
|
||||
email: { type: "string", description: "Nuova email se trovata" },
|
||||
email_invalid: { type: "boolean" },
|
||||
bounced_email: { type: "string" },
|
||||
bounce_reason: { type: "string" },
|
||||
contact_id: { type: "integer" },
|
||||
website: { type: "string" },
|
||||
activity_subject: { type: "string" },
|
||||
activity_description: { type: "string" }
|
||||
},
|
||||
required: %w[project_code organization_id]
|
||||
)
|
||||
|
||||
class << self
|
||||
def call(project_code:, organization_id:, email: nil, email_invalid: nil, bounced_email: nil,
|
||||
bounce_reason: nil, contact_id: nil, website: nil, activity_subject: nil,
|
||||
activity_description: nil, server_context: nil)
|
||||
body = Client.compact(
|
||||
organization_id: organization_id,
|
||||
email: email,
|
||||
bounced_email: bounced_email,
|
||||
bounce_reason: bounce_reason,
|
||||
contact_id: contact_id,
|
||||
website: website,
|
||||
activity_subject: activity_subject,
|
||||
activity_description: activity_description
|
||||
)
|
||||
body[:email_invalid] = email_invalid unless email_invalid.nil?
|
||||
Client.request(
|
||||
:patch,
|
||||
"/api/v1/p/#{Today.encode(project_code)}/organizations/#{organization_id}/email",
|
||||
body: body
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class CheckMailBounces < MCP::Tool
|
||||
description "Legge bounce IMAP dall'account email CRM (credenziali SMTP). Matcha le società del progetto."
|
||||
input_schema(
|
||||
properties: {
|
||||
project_code: { type: "string" },
|
||||
from_email: { type: "string", description: "es. info@matchlivetv.it" },
|
||||
mail_identity_id: { type: "integer" },
|
||||
since_days: { type: "integer" },
|
||||
mailbox: { type: "string" },
|
||||
limit: { type: "integer" }
|
||||
},
|
||||
required: %w[project_code]
|
||||
)
|
||||
|
||||
class << self
|
||||
def call(project_code:, from_email: nil, mail_identity_id: nil, since_days: nil,
|
||||
mailbox: nil, limit: nil, server_context: nil)
|
||||
query = URI.encode_www_form(
|
||||
Client.compact(
|
||||
from_email: from_email,
|
||||
mail_identity_id: mail_identity_id,
|
||||
since_days: since_days,
|
||||
mailbox: mailbox,
|
||||
limit: limit
|
||||
)
|
||||
)
|
||||
path = "/api/v1/p/#{Today.encode(project_code)}/mail_bounces"
|
||||
path = "#{path}?#{query}" if query.present?
|
||||
Client.request(:get, path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
server = MCP::Server.new(
|
||||
@@ -267,7 +342,9 @@ server = MCP::Server.new(
|
||||
EminuxCrmMcp::CreateTask,
|
||||
EminuxCrmMcp::CompleteTask,
|
||||
EminuxCrmMcp::CreateActivity,
|
||||
EminuxCrmMcp::UpdateOpportunityStage
|
||||
EminuxCrmMcp::UpdateOpportunityStage,
|
||||
EminuxCrmMcp::UpdateOrganizationEmail,
|
||||
EminuxCrmMcp::CheckMailBounces
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -110,6 +110,26 @@ class Api::V1::ApiTest < ActionDispatch::IntegrationTest
|
||||
assert_equal "demo_trial", @opportunity.pipeline_stage
|
||||
end
|
||||
|
||||
test "updates organization email after bounce" do
|
||||
patch "/api/v1/p/matchlivetv/organizations/#{@org.id}/email",
|
||||
params: {
|
||||
organization_id: @org.id,
|
||||
bounced_email: "vecchia@example.com",
|
||||
email: "nuova@example.com",
|
||||
email_invalid: false,
|
||||
bounce_reason: "User unknown",
|
||||
activity_subject: "Email aggiornata dopo bounce"
|
||||
},
|
||||
headers: bearer(@admin_token.plaintext),
|
||||
as: :json
|
||||
assert_response :success
|
||||
@org.reload
|
||||
assert_equal "nuova@example.com", @org.email
|
||||
assert_equal "vecchia@example.com", @org.bounced_email
|
||||
assert_not @org.email_invalid?
|
||||
assert json_body["activity"].present?
|
||||
end
|
||||
|
||||
test "rejects lost stage without reason" do
|
||||
patch "/api/v1/p/matchlivetv/opportunities/#{@opportunity.id}/stage",
|
||||
params: { pipeline_stage: "lost" },
|
||||
|
||||
@@ -16,6 +16,52 @@ class MailingsControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_match(/Invii email/, response.body)
|
||||
end
|
||||
|
||||
test "dashboard shows hourly send throughput" do
|
||||
login_as users(:admin)
|
||||
follow_redirect! if response.redirect?
|
||||
|
||||
mailing = create_mailing(identity: @identity, audience: "to_send")
|
||||
org = organizations(:acme)
|
||||
mailing.mailing_recipients.create!(
|
||||
organization: org,
|
||||
contact: org.contacts.first,
|
||||
email: org.email,
|
||||
status: "sent",
|
||||
sent_at: 8.minutes.ago
|
||||
)
|
||||
|
||||
get dashboard_mailings_path(project_code: @project.code)
|
||||
assert_response :success
|
||||
assert_select "div", text: "Mail / ora"
|
||||
assert_match(/1 ultimi 15 min/, response.body)
|
||||
assert_select "div", text: "Storico invio"
|
||||
assert_match(/1 inviate · ultime 12 ore/, response.body)
|
||||
assert_select "span", text: "12h"
|
||||
assert_select "a", text: "24h"
|
||||
assert_select "a", text: "7g"
|
||||
end
|
||||
|
||||
test "dashboard history range can switch to the last week" do
|
||||
login_as users(:admin)
|
||||
follow_redirect! if response.redirect?
|
||||
|
||||
mailing = create_mailing(identity: @identity, audience: "to_send")
|
||||
org = organizations(:acme)
|
||||
mailing.mailing_recipients.create!(
|
||||
organization: org,
|
||||
contact: org.contacts.first,
|
||||
email: org.email,
|
||||
status: "sent",
|
||||
sent_at: 2.days.ago
|
||||
)
|
||||
|
||||
get dashboard_mailings_path(project_code: @project.code, history: "7d")
|
||||
assert_response :success
|
||||
assert_match(/1 inviate · ultima settimana/, response.body)
|
||||
assert_select "span", text: "7g"
|
||||
assert_select "a[href=?]", dashboard_mailings_path(project_code: @project.code, history: "24h")
|
||||
end
|
||||
|
||||
test "creates a draft and opens audience filters" do
|
||||
login_as users(:admin)
|
||||
follow_redirect! if response.redirect?
|
||||
@@ -303,6 +349,32 @@ class MailingsControllerTest < ActionDispatch::IntegrationTest
|
||||
assert mailing.reload.sending?
|
||||
end
|
||||
|
||||
test "pause keeps remaining recipients and resume continues from there" do
|
||||
login_as users(:admin)
|
||||
follow_redirect! if response.redirect?
|
||||
create_campaign_org(name: "Seconda societa", email: "seconda@example.com")
|
||||
mailing = create_mailing(identity: @identity, audience: "to_send")
|
||||
mailing.rebuild_recipients!
|
||||
recipients = mailing.mailing_recipients.order(:id).to_a
|
||||
assert recipients.size >= 2
|
||||
recipients.first.update!(status: "sent", sent_at: Time.current)
|
||||
recipients.second.update!(status: "queued")
|
||||
mailing.update!(status: "sending", queued_at: Time.current, test_sent_at: Time.current, test_sent_to: "test@example.com")
|
||||
|
||||
post pause_mailing_path(mailing, project_code: @project.code)
|
||||
|
||||
assert_redirected_to dashboard_mailings_path(project_code: @project.code)
|
||||
assert_equal "paused", mailing.reload.status
|
||||
assert_equal "sent", recipients.first.reload.status
|
||||
assert_equal "queued", recipients.second.reload.status
|
||||
|
||||
post resume_mailing_path(mailing, project_code: @project.code)
|
||||
|
||||
assert_redirected_to dashboard_mailings_path(project_code: @project.code)
|
||||
assert_equal "sending", mailing.reload.status
|
||||
assert_equal "queued", recipients.second.reload.status
|
||||
end
|
||||
|
||||
test "cancel stops pending recipients and keeps sent ones" do
|
||||
login_as users(:admin)
|
||||
follow_redirect! if response.redirect?
|
||||
|
||||
@@ -27,6 +27,7 @@ class McpControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_includes listed, "list_projects"
|
||||
assert_includes listed, "today"
|
||||
assert_includes listed, "create_activity"
|
||||
assert_includes listed, "check_mail_bounces"
|
||||
assert names || listed.any?
|
||||
end
|
||||
|
||||
|
||||
@@ -29,4 +29,19 @@ class ApplicationHelperTest < ActionView::TestCase
|
||||
assert_includes html, "title=\"Società affiliata FIPAV con un testo molto lungo da accorciare\""
|
||||
assert_includes html, "…"
|
||||
end
|
||||
|
||||
test "queued SMTP retries are not shown as delivery failures" do
|
||||
recipient = MailingRecipient.new(status: "queued", error_message: "Da ritentare (1/8): end of file reached")
|
||||
html = mailing_recipient_delivery_note(recipient)
|
||||
|
||||
assert_includes html, "Non risulta inviata"
|
||||
assert_not_includes html, "end of file reached"
|
||||
end
|
||||
|
||||
test "failed recipients still show the SMTP error" do
|
||||
recipient = MailingRecipient.new(status: "failed", error_message: "end of file reached")
|
||||
html = mailing_recipient_delivery_note(recipient)
|
||||
|
||||
assert_includes html, "end of file reached"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,6 +33,8 @@ class MailIdentityTest < ActiveSupport::TestCase
|
||||
settings = identity.smtp_settings
|
||||
assert settings[:ssl]
|
||||
assert_not settings[:enable_starttls_auto]
|
||||
assert_equal 15, settings[:open_timeout]
|
||||
assert_equal 60, settings[:read_timeout]
|
||||
end
|
||||
|
||||
test "rejects ssl encryption on port 587" do
|
||||
@@ -49,4 +51,29 @@ class MailIdentityTest < ActiveSupport::TestCase
|
||||
assert_not identity.valid?
|
||||
assert_includes identity.errors[:smtp_port], "con SSL/TLS va usata la porta 465 (es. smtps.aruba.it)"
|
||||
end
|
||||
|
||||
test "infers aruba imap host from smtp" do
|
||||
identity = MailIdentity.new(
|
||||
name: "Aruba",
|
||||
from_name: "Info",
|
||||
from_email: "info@example.com",
|
||||
smtp_host: "smtps.aruba.it",
|
||||
smtp_port: 465,
|
||||
encryption: "tls",
|
||||
smtp_authentication: "plain",
|
||||
smtp_username: "info@example.com",
|
||||
smtp_password: "secret",
|
||||
active: true
|
||||
)
|
||||
assert identity.valid?
|
||||
assert_equal "imaps.aruba.it", identity.effective_imap_host
|
||||
assert_equal 993, identity.effective_imap_port
|
||||
assert identity.imap_ready?
|
||||
settings = identity.imap_settings
|
||||
assert_equal "imaps.aruba.it", settings[:address]
|
||||
assert_equal 993, settings[:port]
|
||||
assert settings[:ssl]
|
||||
assert_equal "info@example.com", settings[:user_name]
|
||||
assert_equal "secret", settings[:password]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -107,4 +107,56 @@ class CampaignImportMatchlivetvLaunchTest < ActiveSupport::TestCase
|
||||
ensure
|
||||
csv.close!
|
||||
end
|
||||
|
||||
test "skips clubs without a verified email" do
|
||||
csv = Tempfile.new(["basket", ".csv"])
|
||||
csv.write(<<~CSV)
|
||||
N.,Regione,Prov.,Società,M/F,Email verificata,Sito / profilo,Streaming rilevato,Evidenza / fit commerciale,Test A/B,Stato invio,Data invio,Esito,Demo / Trial,Conversione,Piano,Fonte contatto / ricerca,Data verifica,Note follow-up
|
||||
1,Lombardia,MI,Olimpia Milano,M,olimpia@olimpiamilano.com,olimpiamilano.com,RILEVATO,Serie A,A,DA INVIARE,,,NO,NO,,fonte,2026-09-07,
|
||||
89,Lombardia,CR,Pallacanestro Crema,M,,cremabasket.it,NON RILEVATO,Serie B,B,DA INVIARE,,,NO,NO,,fonte,2026-09-07,
|
||||
CSV
|
||||
csv.flush
|
||||
|
||||
result = CampaignImport::MatchlivetvLaunch.new(
|
||||
path: csv.path,
|
||||
user: users(:admin),
|
||||
project: projects(:matchlivetv),
|
||||
wipe: false,
|
||||
sport: "Basket",
|
||||
campaign_name: "Campagna Basket"
|
||||
).call
|
||||
|
||||
assert_empty result.errors, result.errors.inspect
|
||||
assert_equal 1, result.imported
|
||||
assert_equal 1, result.skipped
|
||||
assert Organization.exists?(name: "Olimpia Milano")
|
||||
assert_equal "yes", Organization.find_by!(name: "Olimpia Milano").streaming_status
|
||||
assert_nil Organization.find_by(name: "Pallacanestro Crema")
|
||||
ensure
|
||||
csv.close!
|
||||
end
|
||||
|
||||
test "transliterates accented characters in campaign emails" do
|
||||
csv = Tempfile.new(["frascati", ".csv"])
|
||||
csv.write(<<~CSV)
|
||||
N.,Regione,Prov.,Società,M/F,Email verificata,Sito / profilo,Streaming rilevato,Evidenza / fit commerciale,Test A/B,Stato invio,Data invio,Esito,Demo / Trial,Conversione,Piano,Fonte contatto / ricerca,Data verifica,Note follow-up
|
||||
127,Lazio,RM,Club Basket Frascati,M/F,società@basketfrascati.com,basketfrascati.com,NON RILEVATO,Giovanile,A,DA INVIARE,,,NO,NO,,fonte,2026-09-07,
|
||||
CSV
|
||||
csv.flush
|
||||
|
||||
result = CampaignImport::MatchlivetvLaunch.new(
|
||||
path: csv.path,
|
||||
user: users(:admin),
|
||||
project: projects(:matchlivetv),
|
||||
wipe: false,
|
||||
sport: "Basket",
|
||||
campaign_name: "Campagna Basket"
|
||||
).call
|
||||
|
||||
assert_empty result.errors, result.errors.inspect
|
||||
assert_equal 1, result.imported
|
||||
assert_equal "societa@basketfrascati.com", Organization.find_by!(name: "Club Basket Frascati").email
|
||||
ensure
|
||||
csv.close!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
require "test_helper"
|
||||
|
||||
class Mailings::HourlyThroughputTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@project = projects(:matchlivetv)
|
||||
@identity = create_mail_identity
|
||||
@mailing = create_mailing(identity: @identity, audience: "all")
|
||||
end
|
||||
|
||||
test "counts confirmed sends in the last hour and last 15 minutes" do
|
||||
add_recipient status: "sent", sent_at: 10.minutes.ago
|
||||
add_recipient status: "sent", sent_at: 40.minutes.ago, mailing: extra_mailing
|
||||
add_recipient status: "sent", sent_at: 2.hours.ago, mailing: extra_mailing("Vecchia")
|
||||
add_recipient status: "queued", mailing: extra_mailing("In coda")
|
||||
|
||||
stats = Mailings::HourlyThroughput.new(@project)
|
||||
|
||||
assert_equal 2, stats.sent
|
||||
assert_equal 1, stats.recent_sent
|
||||
assert_equal 0, stats.failed
|
||||
assert_equal 2, stats.drained
|
||||
end
|
||||
|
||||
test "counts failed recipients that left the queue in the last hour" do
|
||||
recipient = add_recipient(status: "failed")
|
||||
recipient.update_columns(updated_at: 5.minutes.ago)
|
||||
|
||||
stale = add_recipient(status: "failed", mailing: extra_mailing)
|
||||
stale.update_columns(updated_at: 2.hours.ago)
|
||||
|
||||
stats = Mailings::HourlyThroughput.new(@project)
|
||||
|
||||
assert_equal 0, stats.sent
|
||||
assert_equal 1, stats.failed
|
||||
assert_equal 1, stats.drained
|
||||
end
|
||||
|
||||
test "ignores sends from other projects" do
|
||||
other = create_mailing(project: projects(:riskmeter), identity: @identity, audience: "all")
|
||||
add_recipient status: "sent", sent_at: 5.minutes.ago, mailing: other
|
||||
add_recipient status: "sent", sent_at: 5.minutes.ago
|
||||
|
||||
stats = Mailings::HourlyThroughput.new(@project)
|
||||
|
||||
assert_equal 1, stats.sent
|
||||
assert_equal 1, stats.recent_sent
|
||||
end
|
||||
|
||||
test "builds twelve hourly buckets of confirmed sends" do
|
||||
now = Time.zone.parse("2026-09-07 12:40")
|
||||
travel_to now do
|
||||
add_recipient status: "sent", sent_at: now - 20.minutes
|
||||
add_recipient status: "sent", sent_at: now - 70.minutes, mailing: extra_mailing("Ore 11")
|
||||
add_recipient status: "sent", sent_at: now - 80.minutes, mailing: extra_mailing("Ore 11 bis")
|
||||
add_recipient status: "sent", sent_at: now - 13.hours, mailing: extra_mailing("Fuori finestra")
|
||||
|
||||
stats = Mailings::HourlyThroughput.new(@project, now: now)
|
||||
history = stats.history
|
||||
|
||||
assert_equal 12, history.size
|
||||
assert_equal now.beginning_of_hour, history.last[:at]
|
||||
assert_equal 1, history.last[:count]
|
||||
assert_equal 2, history[-2][:count]
|
||||
assert_equal 0, history.first[:count]
|
||||
assert_equal 3, stats.history_total
|
||||
end
|
||||
end
|
||||
|
||||
test "includes the previous day when the range is 24 hours" do
|
||||
now = Time.zone.parse("2026-09-07 12:40")
|
||||
travel_to now do
|
||||
add_recipient status: "sent", sent_at: now - 20.hours
|
||||
|
||||
twelve = Mailings::HourlyThroughput.new(@project, now: now, range: "12h")
|
||||
day = Mailings::HourlyThroughput.new(@project, now: now, range: "24h")
|
||||
|
||||
assert_equal 0, twelve.history_total
|
||||
assert_equal 24, day.history.size
|
||||
assert_equal 1, day.history_total
|
||||
end
|
||||
end
|
||||
|
||||
test "groups the last week by day" do
|
||||
now = Time.zone.parse("2026-09-07 12:40")
|
||||
travel_to now do
|
||||
add_recipient status: "sent", sent_at: now - 3.days
|
||||
add_recipient status: "sent", sent_at: now - 8.days, mailing: extra_mailing("Vecchia")
|
||||
|
||||
stats = Mailings::HourlyThroughput.new(@project, now: now, range: "7d")
|
||||
history = stats.history
|
||||
|
||||
assert_equal 7, history.size
|
||||
assert_equal now.beginning_of_day, history.last[:at]
|
||||
assert_equal 1, history[-4][:count]
|
||||
assert_equal 0, history.last[:count]
|
||||
assert_equal 1, stats.history_total
|
||||
assert_equal "7d", stats.range_key
|
||||
end
|
||||
end
|
||||
|
||||
test "falls back to 12 hours for an unknown range" do
|
||||
stats = Mailings::HourlyThroughput.new(@project, range: "nope")
|
||||
|
||||
assert_equal "12h", stats.range_key
|
||||
assert_equal 12, stats.history.size
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extra_mailing(name = "Altra campagna")
|
||||
create_mailing(identity: @identity, audience: "all", name: name)
|
||||
end
|
||||
|
||||
def add_recipient(status:, sent_at: nil, mailing: @mailing)
|
||||
org = organizations(:acme)
|
||||
mailing.mailing_recipients.create!(
|
||||
organization: org,
|
||||
contact: org.contacts.first,
|
||||
email: org.email,
|
||||
status: status,
|
||||
sent_at: sent_at
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
require "test_helper"
|
||||
|
||||
class Mailings::ImapBounceReaderTest < ActiveSupport::TestCase
|
||||
test "parses aruba-style delivery failure from raw rfc822" do
|
||||
identity = create_mail_identity(
|
||||
from_email: "info@matchlivetv.it",
|
||||
smtp_username: "info@matchlivetv.it",
|
||||
smtp_host: "smtps.aruba.it",
|
||||
smtp_port: 465,
|
||||
encryption: "tls"
|
||||
)
|
||||
|
||||
raw = <<~EML
|
||||
From: mail-daemon@example.com
|
||||
To: info@matchlivetv.it
|
||||
Subject: Recapito fallito
|
||||
Date: Mon, 07 Sep 2026 08:14:05 +0200
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
|
||||
Delivery to the following recipients failed permanently:
|
||||
|
||||
* broken@club.it
|
||||
|
||||
Reason: 550 5.1.1 User unknown
|
||||
|
||||
Final-Recipient: rfc822; broken@club.it
|
||||
Diagnostic-Code: smtp; 550 5.1.1 User unknown
|
||||
|
||||
Subject: Più visibilità per ASD Test Club
|
||||
EML
|
||||
|
||||
reader = Mailings::ImapBounceReader.new(identity)
|
||||
parsed = reader.send(:parse_message, raw, uid: 42)
|
||||
assert_not_nil parsed
|
||||
assert_includes parsed[:failed_emails], "broken@club.it"
|
||||
assert_equal "ASD Test Club", parsed[:club_hint]
|
||||
assert_match(/550|User unknown/i, parsed[:reason].to_s)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,135 @@
|
||||
require "test_helper"
|
||||
|
||||
class Mailings::OutboundQueueTest < ActiveSupport::TestCase
|
||||
include ActionMailer::TestHelper
|
||||
|
||||
setup do
|
||||
Mailings::OutboundQueue.min_interval = 0
|
||||
Mailings::OutboundQueue.fail_interval = 0
|
||||
Mailings::OutboundQueue.reset!
|
||||
Mailings::SmtpGate.min_gap = 0
|
||||
Mailings::SmtpGate.backoff_base = 0
|
||||
Mailings::SmtpGate.reset!
|
||||
end
|
||||
|
||||
teardown do
|
||||
Mailings::OutboundQueue.min_interval = 0
|
||||
Mailings::OutboundQueue.fail_interval = 0
|
||||
Mailings::OutboundQueue.reset!
|
||||
end
|
||||
|
||||
test "campaign jobs only enqueue, drain sends one email at a time" do
|
||||
first = create_campaign_org(name: "Club Uno", email: "uno@example.com")
|
||||
second = create_campaign_org(name: "Club Due", email: "due@example.com")
|
||||
mailing_a = create_sending_mailing(name: "Campagna A")
|
||||
mailing_b = create_sending_mailing(name: "Campagna B")
|
||||
recipient_a = enqueue_org(mailing_a, first)
|
||||
recipient_b = enqueue_org(mailing_b, second)
|
||||
|
||||
assert_equal "queued", recipient_a.reload.status
|
||||
assert_equal "queued", recipient_b.status
|
||||
|
||||
assert_emails 1 do
|
||||
assert_equal :sent, Mailings::OutboundQueue.drain_one!
|
||||
end
|
||||
assert_equal "sent", recipient_a.reload.status
|
||||
assert_equal "queued", recipient_b.reload.status
|
||||
|
||||
assert_emails 1 do
|
||||
assert_equal :sent, Mailings::OutboundQueue.drain_one!
|
||||
end
|
||||
assert_equal "sent", recipient_b.reload.status
|
||||
assert_equal :empty, Mailings::OutboundQueue.drain_one!
|
||||
end
|
||||
|
||||
test "does not start a second SMTP session while the lock is held" do
|
||||
org = create_campaign_org(name: "Club Lock", email: "lock@example.com")
|
||||
mailing = create_sending_mailing(name: "Campagna lock")
|
||||
enqueue_org(mailing, org)
|
||||
|
||||
started = Queue.new
|
||||
release = Queue.new
|
||||
result = nil
|
||||
thread = Thread.new do
|
||||
Mailings::OutboundQueue.exclusive do
|
||||
started << true
|
||||
release.pop
|
||||
end
|
||||
end
|
||||
started.pop
|
||||
result = Mailings::OutboundQueue.drain_one!
|
||||
release << true
|
||||
thread.join
|
||||
|
||||
assert_equal :busy, result
|
||||
assert_equal "queued", mailing.mailing_recipients.first.reload.status
|
||||
end
|
||||
|
||||
test "transient SMTP errors stay queued and the next drain tries another recipient" do
|
||||
first = create_campaign_org(name: "Club EOF", email: "eof@example.com")
|
||||
second = create_campaign_org(name: "Club Ok", email: "ok@example.com")
|
||||
mailing = create_sending_mailing(name: "Campagna eof")
|
||||
recipient_a = enqueue_org(mailing, first)
|
||||
recipient_b = enqueue_org(mailing, second)
|
||||
gate = Mailings::SmtpGate
|
||||
original = gate.method(:deliver)
|
||||
gate.define_singleton_method(:deliver) { raise EOFError, "end of file reached" }
|
||||
|
||||
begin
|
||||
assert_equal :deferred, Mailings::OutboundQueue.drain_one!
|
||||
ensure
|
||||
gate.define_singleton_method(:deliver, original)
|
||||
end
|
||||
|
||||
recipient_a.reload
|
||||
assert_equal "queued", recipient_a.status
|
||||
assert_match(/Da ritentare \(1\/8\)/, recipient_a.error_message)
|
||||
assert_equal recipient_b.id, Mailings::OutboundQueue.next_queued.id
|
||||
|
||||
# Il contatore deve avanzare sullo stesso destinatario (prima il regex
|
||||
# non matchava "Da ritentare (N/8)" e restava sempre tentativo 1).
|
||||
begin
|
||||
gate.define_singleton_method(:deliver) { raise EOFError, "end of file reached" }
|
||||
assert_equal :deferred, recipient_a.deliver_queued!
|
||||
ensure
|
||||
gate.define_singleton_method(:deliver, original)
|
||||
end
|
||||
assert_match(/Da ritentare \(2\/8\)/, recipient_a.reload.error_message)
|
||||
end
|
||||
|
||||
test "next_queued skips recipients whose send window is closed" do
|
||||
open_org = create_campaign_org(name: "Club Aperto", email: "aperto@example.com")
|
||||
closed_org = create_campaign_org(name: "Club Chiuso", email: "chiuso@example.com")
|
||||
open_mailing = create_sending_mailing(name: "Finestra aperta")
|
||||
closed_mailing = create_sending_mailing(name: "Finestra chiusa")
|
||||
closed_mailing.update!(send_window_enabled: true, send_window_start_minutes: 8 * 60, send_window_end_minutes: 9 * 60)
|
||||
open_mailing.update!(send_window_enabled: false)
|
||||
|
||||
closed_recipient = enqueue_org(closed_mailing, closed_org)
|
||||
open_recipient = enqueue_org(open_mailing, open_org)
|
||||
closed_recipient.update_columns(updated_at: 1.hour.ago)
|
||||
|
||||
travel_to Time.zone.local(2026, 9, 10, 20, 0, 0) do
|
||||
assert_equal open_recipient.id, Mailings::OutboundQueue.next_queued.id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_sending_mailing(name:)
|
||||
mailing = create_mailing(name: name, audience: "to_send", interval_seconds: 0)
|
||||
mailing.update!(status: "sending", queued_at: Time.current, test_sent_at: Time.current, test_sent_to: "test@example.com")
|
||||
mailing
|
||||
end
|
||||
|
||||
def enqueue_org(mailing, org)
|
||||
recipient = mailing.mailing_recipients.create!(
|
||||
organization: org,
|
||||
contact: org.contacts.first,
|
||||
email: org.email,
|
||||
status: "pending"
|
||||
)
|
||||
assert Mailings::OutboundQueue.enqueue(recipient.reload)
|
||||
recipient
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,69 @@
|
||||
require "test_helper"
|
||||
|
||||
class Mailings::SmtpGateTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
Mailings::SmtpGate.min_gap = 0
|
||||
Mailings::SmtpGate.backoff_base = 0
|
||||
Mailings::SmtpGate.max_attempts = 4
|
||||
Mailings::SmtpGate.reset!
|
||||
end
|
||||
|
||||
teardown do
|
||||
Mailings::SmtpGate.min_gap = 0
|
||||
Mailings::SmtpGate.backoff_base = 0
|
||||
Mailings::SmtpGate.max_attempts = 4
|
||||
Mailings::SmtpGate.reset!
|
||||
end
|
||||
|
||||
test "retries EOFError then succeeds" do
|
||||
hits = 0
|
||||
result = Mailings::SmtpGate.deliver do
|
||||
hits += 1
|
||||
raise EOFError, "end of file reached" if hits < 3
|
||||
|
||||
:sent
|
||||
end
|
||||
|
||||
assert_equal :sent, result
|
||||
assert_equal 3, hits
|
||||
end
|
||||
|
||||
test "gives up after max attempts" do
|
||||
hits = 0
|
||||
error = assert_raises(EOFError) do
|
||||
Mailings::SmtpGate.deliver do
|
||||
hits += 1
|
||||
raise EOFError, "end of file reached"
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal "end of file reached", error.message
|
||||
assert_equal Mailings::SmtpGate.max_attempts, hits
|
||||
end
|
||||
|
||||
test "does not retry permanent SMTP errors" do
|
||||
hits = 0
|
||||
assert_raises(Net::SMTPAuthenticationError) do
|
||||
Mailings::SmtpGate.deliver do
|
||||
hits += 1
|
||||
raise Net::SMTPAuthenticationError, "535 authentication failed"
|
||||
end
|
||||
end
|
||||
assert_equal 1, hits
|
||||
end
|
||||
|
||||
test "serializes concurrent deliveries" do
|
||||
order = Queue.new
|
||||
threads = 2.times.map do |i|
|
||||
Thread.new do
|
||||
Mailings::SmtpGate.deliver do
|
||||
order << i
|
||||
sleep 0.05
|
||||
end
|
||||
end
|
||||
end
|
||||
threads.each(&:join)
|
||||
|
||||
assert_equal 2, order.size
|
||||
end
|
||||
end
|
||||
@@ -30,6 +30,14 @@ class MailingsAudienceQueryTest < ActiveSupport::TestCase
|
||||
assert_not_includes ids, @volley.id
|
||||
end
|
||||
|
||||
test "filters by country" do
|
||||
@volley.update!(country: "Francia")
|
||||
@acme.update!(country: "Italia")
|
||||
ids = query({ "preset" => "all", "countries" => ["Francia"] }).relation.pluck(:id)
|
||||
assert_includes ids, @volley.id
|
||||
assert_not_includes ids, @acme.id
|
||||
end
|
||||
|
||||
test "filters by list position range" do
|
||||
ids = query({ "preset" => "all", "list_min" => 1, "list_max" => 5 }).relation.pluck(:id)
|
||||
assert_includes ids, @acme.id
|
||||
|
||||
Reference in New Issue
Block a user