E-mail küldés kódból

.NET-es kódból e-mailt küldeni nem éppen atomfizikusnak való feladat, mindenki hamar rájön, hogy milyen egyszerű spam gyárat írni a System.Net.Mail névtér segítségével:

    MailMessage msg = new MailMessage(
        "felado@example.com",
        "cimzett@example.com",
        "Próba levél tárgysora kódból",
        "Ide jön az érdekes tartalom..." );

    SmtpClient smtp = new SmtpClient( "mail.example.com" );
    smtp.Send( msg );

Ez eddig ment .NET 1.0-n is, ami viszont macerás volt, az a feladó és a címzettek megadása, amire a megoldást a .NET 2.0-ban megjelent MailAddress osztály hozta meg. Így már barátságosabban címezhetünk levelet:

    MailMessage msg = new MailMessage();
    msg.From = new MailAddress( "felado@example.com", "Feladó szép neve" );
    msg.To.Add( new MailAddress( "cimzett@example.com", "Címzett szép neve" ) );
    msg.Subject = "Próba levél tárgysora";
    msg.Body = "A tartalom helye...";

Ez mind szép, de a címzettel kár sokat erőlködni, egy Exchange Serverre kötött Outlook ragaszkodik az AD-ben lévő Display Name-hez. Egyébként pedig a webes levelező kliensek közül egyik-másik (pl. a Freemail biztosan) nem tud megbírkózni a címmezőkben lévő ékezetekkel, hiába adunk meg a MailAddress konstruktornak harmadik paraméterben egy Encodingot. Érdekes módon a Gmailnek meg se kottyan…

Ha már a címzetteknél járunk, bizony mindenkinek akadnak olyan levelezőpartnerei, akiknél nem ártana a kézbesítési és az olvasási nyugta használata. Örüljünk, mert kézbesítési nyugtát kérhetünk a DeliveryNotificationOptions tulajdonság használatával. Mivel ez flages enum bátran összevagyolhatjuk az egyes elemeit, például:

    msg.DeliveryNotificationOptions =
        DeliveryNotificationOptions.OnFailure |
        DeliveryNotificationOptions.OnSuccess;

Az olvasási nyugtára viszont nincs közvetlenül property, így nincs más lehetőségünk, mint kézzel beletúrni a fejléc mezőkbe:

    msg.Headers.Add( "Disposition-Notification-To", "felado@example.com" );

Ennyit a címzésről, lépjünk tovább a tartalomra, például legyen inkább HTML:

  msg.Body =
    "<html><body><h1>Hello Szivi!</h1>Hoztál <i>nekem</i> kismajmot?</body></html>";
  msg.IsBodyHtml = true;

Na ezt már nem sikerült elküldeni az Exchange-en keresztül:

Unhandled Exception: System.Net.Mail.SmtpException: Mailbox unavailable. The server response was: 5.7.1 Requested action not taken: message refused

A finnyás mindenit, túl spam gyanúsnak találta a levelet, ezért kénytelen voltam a lokális SMTP szerveremen keresztül elküldeni. Mert XP-n még van olyan, Vistán nincs, de majd a Windows Server 2008-ban lesz.

Az AlternateView osztály segítségével még arra is van lehetőségünk, hogy egyetlen levélben különböző formátumokban küldjük el a mondandónkat.

    string htmlBody =
      "<html><body><h1>Hello Szivi!</h1>Hoztál <i>nekem</i> kismajmot?</body></html>";
    string textBody = "Szia Szivi! Hoztál nekem kismajmot?";

    AlternateView htmlView = AlternateView.CreateAlternateViewFromString(
        htmlBody, Encoding.Default, MediaTypeNames.Text.Html );
    msg.AlternateViews.Add( htmlView );

    AlternateView textView = AlternateView.CreateAlternateViewFromString(
        textBody, Encoding.Default, MediaTypeNames.Text.Plain );
    msg.AlternateViews.Add( textView );

Ez (legalább) két ok miatt érdekes:

  1. Az AlternateView osztálynak van olyan konstruktora, amelyet fájl név vagy stream megadásával használhatunk, ha tehát nem változik a szöveg, akkor egyszerűen felnyalhatjuk fájlrendszerből.
  2. Így van lehetőségünk arra, hogy a HTML változathoz – és csak ahhoz – képet fűzzünk. Nosza!
    string htmlBody =
      @"<html><body><h1>Képes</h1><img src=""cid:Kep1""></body></html>";

    AlternateView htmlView = AlternateView.CreateAlternateViewFromString(
        htmlBody, Encoding.Default, MediaTypeNames.Text.Html );

    LinkedResource pic = new LinkedResource(
        @"C:\WINDOWS\Web\Wallpaper\Crystal.jpg", MediaTypeNames.Image.Jpeg );
    pic.ContentId = "Kep1";
    htmlView.LinkedResources.Add( pic );

    msg.AlternateViews.Add( htmlView );

Aláhúztam a trükkös részeket, a ContentId tulajdonságra kell figyelni, és az IMG elem SRC attribútumában cid:ContentId formátumban hivatkozhatunk a képre.

Így már persze jó nagy leveleket fogunk küldözgetni, nem is fog gyorsan menni. Küldjük hát aszinkron módon, mert 2.0-ban már azt is lehet:

    smtp.SendCompleted += new SendCompletedEventHandler( OnSendCompleted );
    smtp.SendAsync( msg, "Levél a Kedvesnek" );

A küldés eredményét pedig egy eseménykezelővel tudhatjuk meg:

    static void OnSendCompleted( object sender, AsyncCompletedEventArgs e )
    {
        string state = e.UserState as string;
        if( e.Cancelled )
        {
            Console.WriteLine( @"""{0}"" küldése megszakítva.", state );
        }
        if( e.Error != null )
        {
            Console.WriteLine( @"Hiba a ""{0}"" küldése közben: {1}",
              state, e.Error.Message );
        }
        else
        {
            Console.WriteLine( @"A ""{0}"" elküldve.", state );
        }
    }

A teljességhez hozzátartozik, hogy az SmtpClient.SendAsyncCancel() metódus hívásával leállíthatjuk a levél elküldését. Ezt jelenlegi formájában a valóságban aligha fogjuk használni, hiszen nem tudunk hivatkozni egy konkrét MailMessage objektumra.

Ha valami miatt mégis ragaszkodnánk a szinkron küldéshez, ne feledkezzünk meg a Timeout tulajdonságról, ami alapértelmezés szerint 100 másodperc, addig próbálkozik a küldéssel. Apropó, ha már a küldésnél tartunk, igen örvendetes, hogy már van lehetőségünk a kapcsolat titkosítására (SmtpClient.EnableSsl) és hitelesítésre is (SmtpClient.Credentials).

Aki pedig arra kíváncsi, hogy mi történik a háttérben, ne habozzon felhúzni néhány trace listenert az app.config fájlban, egészen részletes olvasnivalóban lehet része:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <system.diagnostics>
            <trace autoflush="true" />

            <sources>
                <source name="System.Net" >
                    <listeners>
                        <add name="MyTraceFile"/>
                    </listeners>
                </source>
                <source name="System.Net.Sockets">
                    <listeners>
                        <add name="MyTraceFile"/>
                    </listeners>
                </source>
            </sources>

            <sharedListeners>
                <add
                    name="MyTraceFile"
                    type="System.Diagnostics.TextWriterTraceListener"
                    initializeData="System.Net.trace.log" />
            </sharedListeners>

            <switches>
                <add name="System.Net" value="Verbose" />
                <add name="System.Net.Sockets" value="Verbose" />
            </switches>

        </system.diagnostics>
    </configuration>

És hogy valami stílszerű maradjon a végére, ezt soha ne felejtsük el:

    msg.Dispose();

Technorati tags: , ,

3 thoughts on “E-mail küldés kódból

  1. Tamás

    Köszi a példát!
    Van azonban egy problémám, hogy tudok levelet küldeni, ha az smtp szerver felhasználó nevet és jelszót kér a küldéshez.
    Ha ezzel kapcsolatban tudnál némi útmutatással szolgálni azért hálás lennék!Üdv! Mato

  2. Zoltán

    Az SmtpClient példánynak van egy Credentials nevű property-je
    client.Credentials = new NetworkCredential(username, password); //(System.Net  névtérből)

  3. Visszajelzés: E-mail küldés kódból – sablonnal « Balássy György szakmai blogja

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s