Masowe wysyłanie e-maili.

0

Cześć,
W ramach nauki tworzę sobie prostą aplikację, która pobiera z bazy adresy email i wysyła do nich wiadomości.
Ponieważ, liczba adresów to ok 30 używam wysyłania asynchronicznego aby nie zabić aplikacji.
Korzystam z przykładu z poniższego linku:
(https://docs.microsoft.com/pl-pl/dotnet/api/system.net.mail.smtpclient.sendasync?view=net-5.0)
Poniżej przykład

using System;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Threading;
using System.ComponentModel;
namespace Examples.SmtpExamples.Async
{
    public class SimpleAsynchronousExample
    {
        static bool mailSent = false;
        private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
        {
            // Get the unique identifier for this asynchronous operation.
             String token = (string) e.UserState;

            if (e.Cancelled)
            {
                 Console.WriteLine("[{0}] Send canceled.", token);
            }
            if (e.Error != null)
            {
                 Console.WriteLine("[{0}] {1}", token, e.Error.ToString());
            } else
            {
                Console.WriteLine("Message sent.");
            }
            mailSent = true;
        }
        public static void Main(string[] args)
        {
            // Command-line argument must be the SMTP host.
            SmtpClient client = new SmtpClient(args[0]);
            // Specify the email sender.
            // Create a mailing address that includes a UTF8 character
            // in the display name.
            MailAddress from = new MailAddress("[email protected]",
               "Jane " + (char)0xD8+ " Clayton",
            System.Text.Encoding.UTF8);
            // Set destinations for the email message.
            MailAddress to = new MailAddress("[email protected]");
            // Specify the message content.
            MailMessage message = new MailMessage(from, to);
            message.Body = "This is a test email message sent by an application. ";
            // Include some non-ASCII characters in body and subject.
            string someArrows = new string(new char[] {'\u2190', '\u2191', '\u2192', '\u2193'});
            message.Body += Environment.NewLine + someArrows;
            message.BodyEncoding =  System.Text.Encoding.UTF8;
            message.Subject = "test message 1" + someArrows;
            message.SubjectEncoding = System.Text.Encoding.UTF8;
            // Set the method that is called back when the send operation ends.
            client.SendCompleted += new
            SendCompletedEventHandler(SendCompletedCallback);
            // The userState can be any object that allows your callback
            // method to identify this send operation.
            // For this example, the userToken is a string constant.
            string userState = "test message1";
            client.SendAsync(message, userState);
            Console.WriteLine("Sending message... press c to cancel mail. Press any other key to exit.");
            string answer = Console.ReadLine();
            // If the user canceled the send, and mail hasn't been sent yet,
            // then cancel the pending operation.
            if (answer.StartsWith("c") && mailSent == false)
            {
                client.SendAsyncCancel();
            }
            // Clean up.
            message.Dispose();
            Console.WriteLine("Goodbye.");
        }
    }
}

Wszystko działa dobrze, niestety nie wiem w jaki sposób mógłbym wyświetlić informację o zakończonej wysyłce.
Tzn jest to metoda która jest wywoływana po każdej popranej wysyłce "SendCompletedCallback" niestety nie wiem jak poinformować program, że cała wysyłka została zakończona.
Próbowałem z licznikiem niestety nie wiem jak w głównej aplikacji automatycznie sprawdzać jego wartość ( np. progressbarem) lub wyświetlić komunikat, że cała wysyłka została zakończona.
Mam nadzieje, że jasno się wyraziłem.

Pozdrawiam

1

A więc:

  1. Dlaczego wysłanie 30 maili ma "zabić aplikację"?
  2. Przykładowy kod nie jest twoim kodem, ponieważ nie ma w nim pętli po tych 30 adresach wyciąganych z bazy.
0

@AdamWox: Tzn. może źle się wyraziłem nie tyle zabija aplikację co ją "wiesza" na czas wysyłki (wysyłam też załączniki), która trwa kilkadziesiąt sekund. Kod jest przykładem na którym się oparłem ( zrobiłem z niego metodę) do której przekazuję adres email i której następnie wywołuje w pętli ( foreach var email in lista_emaili) . Wieczorem prześlę uzupełnię post o mój kod.

0

Dawno wysyłki mailem nie robiłem w C# ale jeśli to jest ta sama wiadomość to można wysłać jednego maila to wielu odbiorców :P

0
  1. Zamiast System.Net.Mail proponuję użyć https://www.nuget.org/packages/MailKit/ i https://www.nuget.org/packages/MimeKit/ , jeżeli serwer źródłowy SMTP zapewnia wyłącznie połączenie szyfrowane SSL, SMTP z System.Net.Mail może nie działać w przypadku niektórych serwerów SMTP.
  2. Jeżeli z jakiegoś powodu musisz zrealizować 30 maili (bo np. czymś się różnią), to proponuję uruchamiać wątki używając System.Threading.Thread. Ponieważ wysłanie jednego maila trochę trwa, to jak będziesz realizować w od 3 do 5 wątkach (każdy wątek to osobne podłączenie do SMTP), to uzyskasz wymierne przyspieszenie całej akcji. Liczbę wątków trzeba dobrać doświadczalnie, serwer może ograniczać liczbę połączeń z jednego adresu IP. W przypadku pełnego wykorzystania Twojego łącza upstream, dalsze zwiększanie liczby wątków nic nie da.
  3. Przy 30 mailach to nie problem, ale przy wysyłkach rzędu od 200 maili w górę lub przesyle od 2 GB danych w górę, w większości przypadków objawią się ograniczenia serwerów SMTP polegające na limicie liczby maili lub ilości danych w ciągu godziny lub doby (różnie to bywa, co serwer to inaczej). Jeżeli będziesz mieć taki problem, to jedynym rozwiązaniem jest użycie kilku kont e-mail lub uzbrojenie się w cierpliwość, a program napisać tak, że będzie próbować wysyłać maile aż do skutku.
0

No i gdzie ten kod z wczoraj? :D

0

@gswidwa1: Tutaj. Niestety co pewnie może zaskoczyć jest jeszcze życie poza komputerem .

CO do kodu to jest tutaj.

 private void btWyslij(object sender, RoutedEventArgs e)
        {
           
            List<Mail> daneDoWysylki = _db.mail.ToList();
            
            foreach ( mail item in daneDoWysylki)
            {
                SendMailAsync(mail);
                
            }
        }

public static bool SendMailAsync(Mail mail)
        {

            List<string> listAttachment = _db.pliki.Where(u => u.mail_id == mail.id).Select(u => u.sciezka).ToList());
            
            try
            {
                MailMessage wysylka = new MailMessage();
                SmtpClient SmtpServer = new SmtpClient("smtp.gmail.com");
                wysylka.From = new MailAddress("xxxxxxx");
                wysylka.To.Add(sending.getEmailCustomers);
                wysylka.Subject = getParametr("temat");
                wsylka.Body = Tresc.text;
                
                Attachment attachment;
                foreach (var item in listAttachment)
                {
                    attachment = new Attachment(item);
                    wysylka.Attachments.Add(attachment);
                }

                SmtpServer.Port = 587;
                SmtpServer.Credentials = new System.Net.NetworkCredential("xxxxx", "zxxxxxx");
                SmtpServer.EnableSsl = true;
                SmtpServer.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback);
                
                SmtpServer.SendAsync(mail,mail.id);
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.ShowError(ex.ToString());
                return false;
            }

        }

        private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
        {
           
            
           MessageBox.ShowError("Email został wysłany");
            _licznik++;
          
            

Jak pisałem wyżej kod niczym się nie różni od przykładu.
Nie chodzi o samą wysyłkę, ponieważ ona działa w tle, ale o informację, że cała wysyłka została zakończona. W obecnej formie dostaję testowo wiadomość że email został wysłany ale po każdym wysłanym mailu. Natomiast sam licznik się zmienia ale nie wiem jak to np połączyć z labelm lub progressbarem w głównym programie.

Mimo wszystko cieszę się, że dostarczyłem taki emocji związanych z wklejeniem kodu. ( choć wydaje mi się że przykład, który wkleiłem był wystarczający aby otrzymać odpowiedź no ale widać się myliłem). Widzę że klimat jak na elektrodzie.

andrzejlisek:
Każdy email jest inny i ma inne załączniki. Ogólnie ta funkcja sobie z tym radzi dość dobrze ale jeżeli będzie tego więcej pomyślę aby to rozbić na wątki.
Pozdrawiam

0

Brzydkie rozwiązanie ale wystarczające abyś poczytał trochę o tym i poprawił.

        private static void Program_Failed(object sender, EventArgs e)
        {
            Dispatcher?.Invoke(() =>
            {
                Failed++;
                hProgressBar.Value ++;
            });         
        }
        private static void Program_Success(object sender, EventArgs e)
        {
            Dispatcher?.Invoke(() =>
            {
                Success++;
                hProgressBar.Value++;
            });
        }

        public static event EventHandler Success;
        public static event EventHandler Failed;
        
        public async Task SendEmails()
        {
            hProgressBar.Maximum = MyEmails.Count();
            hProgressBar.Value = 0;
            await Task.Run(() => SendEmail(MyEmails, Success, Failed));
        }
        public void SendEmail(MyEmail[] email, EventHandler Success, EventHandler Failed)
        {
            try
            {
                Success?.Invoke(null, EventArgs.Empty);
            }
            catch(Exception)
            {
                Failed?.Invoke(null, EventArgs.Empty);
            }
        }
0

@gswidwa1: Dzięki, będę przeglądnę.

0

Nie chodzi o samą wysyłkę, ponieważ ona działa w tle, ale o informację, że cała wysyłka została zakończona. W obecnej formie dostaję testowo wiadomość że email został wysłany ale po każdym wysłanym mailu. Natomiast sam licznik się zmienia ale nie wiem jak to np połączyć z labelm lub progressbarem w głównym programie.

Jedno z możliwych rozwiązań: Zamiast SendAsync zastosuj zwykły Send, Uruchomienie w pętli iterującej po mailach (jakaś struktura przechowująca obiekty, w których jeden obiekt to adres odbiorcy, treść maila i lista załączników) sprawi, że maile będą wysyłane jeden za drugim. Dajesz globalną zmienną liczbową, której wartość na początku jest 0, i inkrementowana co iterację pętli. Całą procedurę wysyłki uruchamiasz w innym wątku. Potem na formatkę wstawiasz timer, tykajacy co sekundę. W procedurze tyknięcia timera odczytujesz opisaną zmienną globalną, mają jej wartość oraz wiedząc, ile jest wszystkich maili do wysłania, możesz zrobić pasek postępu, etykietę, czy co am chcesz.

Każdy email jest inny i ma inne załączniki. Ogólnie ta funkcja sobie z tym radzi dość dobrze ale jeżeli będzie tego więcej pomyślę aby to rozbić na wątki.
Pozdrawiam

Sama czynność wysłania maila to nie problem. Pewien opór w działaniu zacznie się, jak maile będą szły w setki sztuk lub dane w gigabajty. Tego nie przeskoczysz, bo to wynika z decyzji właścicieli serwerów SMTP. Jeżeli tempo nie będzie akceptowalne, to jedynie, co możesz, to zwiększyć ilość kont e-mail, wtedy w jednostce czasu wyślesz tyle, ile przewiduje limit razy liczba kont e-mail (o ile wszystkie są na tym samym serwerze).

0

@gswidwa1: Zrobiłem tak ja radziłeś. Tzn skorzystałem z samego sendMaila ale użyłem go asynchronicznie przez await await SendMail(item); następnie dodałem progressbara do którego podłączyłem licznik. Ogólnie działa więc bardzo dziękuje.

@andrzejlisek: Docelowo chciałbym wysyłać ok 100 do 150 maili i faktycznie to będzie trochę trwało ( zależy od załączników). Nie chciałbym jednak wysyłać ich z kilku kont. Zastanawiałem się czy nie zrobić pętli i każdą wysyłkę robić w nowym wątku ( zadania równoległe). Nie wiem tylko czy ma to sens ( tworzyć 100 zadań) czy lepiej tak jak pisałeś wyżej podzielić to tylko na kilka zadań.

0

Czy wysyłanie tych maili musi odbywać się w aplikacji desktop?
Może jest możliwość aby w desktop kliknąć "Rozpocznij wysyłkę", która da znak sygnał innej aplikacji, która wyśle te maile.

0

Toż to idealnie nadaje się do użycia kolejki na Azure ;)

0

Wystarczy zwykła usługa uruchomiona na jakimś serwerze, która cyklicznie sprawdzi czy jest w bazie coś do wysłania - autor zaznaczył, że pobiera rekordy z bazy, więc coś już jest.

No chyba, że to musi być w desktopie i nie ma możliwości uruchomienia usługi na innym serwerze.

0

Tak, wszystko mam w bazie, więc można zrobić tak, że tutaj zmieniane są tylko statusy a inny program w tle na serwerze co jakiś czas odpytuje bazę i sprawdza status wiadomości). Nawet na początku o tym myślałem ale te maile będą wysyłane tylko raz w miesiącu i założyłem, że szkoda to tak rozdrobnić (nie wiedziałem jednak ile to będzie trwało). Co do kolejki na Azure to moje doświadczenie w tym temacie jest jeszcze żadne:).
Co do przesyłania argumentów do inne aplikacji to rozumiem, że chodzi o wywołanie programu z przekazaniem argumentu w programie głównym ?.
Co do wysyłania w jednym programie to chciałem aby użytkownik widział, będąc w programie co zostało wysłane w czasie powiedzmy to "rzeczywistym". Rozumiem jednak, że mogę zrobić odświeżanie grida co jakiś czas i wyłapać w ten sposób zmianę statusu.

0

Przy takiej ilości / częstotliwości, zrobienie tego w desktop też wydaje się rozsądne - a niech użytkownik nawet czeka.
Jednak to są dwa różne rozwiązania - jak zrobisz to po stronie usługi to tam będzie faktyczne połączenie z serwerem poczty. Wyeliminujesz w ten sposób potencjalnie zablokowane porty/hosty, konfiguracja dobywać się będzie tylko na serwerze (bo pewnego dnia będziesz wysyłał z innej skrzynki) + np. generowanie treści maila może odbywać się po stronie usługi - nie musisz wówczas dostarczać nowej wersji klientowi.

0
Blacha napisał(a):

Docelowo chciałbym wysyłać ok 100 do 150 maili i faktycznie to będzie trochę trwało ( zależy od załączników). Nie chciałbym jednak wysyłać ich z kilku kont. Zastanawiałem się czy nie zrobić pętli i każdą wysyłkę robić w nowym wątku ( zadania równoległe). Nie wiem tylko czy ma to sens ( tworzyć 100 zadań) czy lepiej tak jak pisałeś wyżej podzielić to tylko na kilka zadań.

Zwiększanie liczby wątków ma sens tylko do momentu "wypełnienia" dostępnej przepustowości sieci. W praktyce, w większości przypadków optymalnym rozwiązaniem będzie ok. 5 wątków, gdzie jeden wysyła maile sekwencyjnie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1