最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#調(diào)用郵箱應(yīng)用發(fā)送帶附件的郵件

 更新時(shí)間:2025年07月30日 09:05:10   作者:藍(lán)點(diǎn)lilac  
這篇文章主要為大家詳細(xì)介紹了C#如何調(diào)用郵箱應(yīng)用發(fā)送帶附件的郵件,文中的示例代碼簡(jiǎn)潔易懂,具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下

郵件的幾大要素

發(fā)件人 From

收件人(主要收件人 To,抄送 CC,密送 BCC)

主題 Subject

正文 Body

附件 Attachments

SmtpClient 和 MailKit

如果有郵箱服務(wù)器并且已知發(fā)件人郵箱和密碼,可以通過(guò) C# 自帶的 SmtpClient 或者使用開(kāi)源庫(kù) MailKit

調(diào)用第三方郵箱應(yīng)用

C# 自帶的 MailMessage 類(lèi)中的 Attachments 會(huì)直接打開(kāi)文件流,且沒(méi)有屬性可以獲取文件路徑

我們可以創(chuàng)建一個(gè)簡(jiǎn)單的郵件信息類(lèi),調(diào)用第三方郵箱客戶端一般不需要發(fā)件人,可去掉發(fā)件人屬性

using System.Collections.Generic;
using System.Net.Mail;

public sealed class MailInfo
{
    ///// <summary>發(fā)件人</summary>
    //public MailAddress From { get; set; }

    /// <summary>主要收件人</summary>
    public List<MailAddress> Recipients { get; } = new List<MailAddress>();

    /// <summary>抄送收件人</summary>
    public List<MailAddress> CcRecipients { get; } = new List<MailAddress>();

    /// <summary>密送收件人</summary>
    public List<MailAddress> BccRecipients { get; } = new List<MailAddress>();

    /// <summary>主題</summary>
    public string Subject { get; set; }

    /// <summary>正文</summary>
    public string Body { get; set; }

    /// <summary>附件文件列表</summary>
    /// <remarks>Key 為顯示文件名, Value 為文件路徑</remarks>
    public Dictionary<string, string> Attachments { get; } = new Dictionary<string, string>();
}

mailto 協(xié)議

mailto 是全平臺(tái)支持的協(xié)議,支持多個(gè)收件人,抄送和密送,但不支持添加附件

mailto 關(guān)聯(lián)應(yīng)用

在 Windows 上使用 mailto 會(huì)調(diào)用其關(guān)聯(lián)應(yīng)用,未設(shè)置關(guān)聯(lián)應(yīng)用時(shí),會(huì)彈出打開(kāi)方式對(duì)話框詢問(wèn)使用什么應(yīng)用打開(kāi)

關(guān)聯(lián)注冊(cè)表位置HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\Shell\Associations\UrlAssociations\mailto\UserChoice

// 常見(jiàn)的郵箱應(yīng)用 mailto ProgID
const string OutlookNewProgID = "AppXbx2ce4vcxjdhff3d1ms66qqzk12zn827"; // Outlook(New)
const string EMClientProgID = "eM Client.Url.mailto"; // eM Client
const string ThunderbirdProgID = "Thunderbird.Url.mailto"; // Mozilla Thunderbird
const string MailMasterProgID = "MailMaster"; // 網(wǎng)易郵箱大師

/// <summary>查找 mailto 協(xié)議關(guān)聯(lián)的郵箱應(yīng)用 ProgID</summary>
private static string FindMailToClientProgID()
{
    // Win10 以上支持 AssocQueryString 查找 ProgID, 為兼容低版本使用注冊(cè)表查詢
    const string keyPath = @"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\Shell\Associations\UrlAssociations\mailto\UserChoice";
    return Registry.GetValue(keyPath, "ProgId", null)?.ToString();
}

/// <summary>判斷是否是 Outlook 關(guān)聯(lián)的 ProgID</summary>
private static bool IsOutlookProgID(string progID)
{
    var st = StringComparison.OrdinalIgnoreCase;
    return progID.IndexOf("Outlook", st) >= 0 // Outlook(Classic) 版本相關(guān),如 Outlook.URL.mailto.15
        || progID.Equals(OutlookNewProgID, st);
}

mailto 標(biāo)準(zhǔn)

語(yǔ)法:mailto:sAddress[sHeaders]

示例:mailto:example@to.com?subject=Test%20Subject

主要收件人寫(xiě)在 sAddress,抄送、密送、主題和正文都放在 sHeaders 里面,需要對(duì)所有 URL 保留字符進(jìn)行編碼轉(zhuǎn)義

大部分郵箱應(yīng)用都使用較新的 RFC 6068 標(biāo)準(zhǔn)(收件人、抄送、密送使用逗號(hào)分隔),且部分應(yīng)用同時(shí)兼容分號(hào)和逗號(hào)

但是  Microsoft Outlook 還在使用著比較舊的 RFC 2368 標(biāo)準(zhǔn)(收件人、抄送、密送使用分號(hào)分隔)

故當(dāng)關(guān)聯(lián)應(yīng)用為 Outlook 時(shí),包括 Classic 版本和 UWP 新版,都無(wú)法正確解析逗號(hào)連接的多個(gè)收件人、抄送、密送

Classic 版本支持的 COM Interop 方式中也是使用的分號(hào)分隔

另外 PDF 表單 JavaScript 動(dòng)作中 mailDoc、mailForm 等發(fā)送郵件的方法也是使用的分號(hào)分隔符

因此我們可以給上文中的 MailInfo 類(lèi)添加幾個(gè)獲取指定分隔符連接的收件人地址字符串的方法

/// <summary>獲取指定分隔符連接的收件地址</summary>
/// <param name="separator">遵循 mailto RFC 6068 規(guī)范默認(rèn)為逗號(hào),部分郵箱客戶端支持逗號(hào)和分號(hào),
/// <para>但 Outlook 僅支持分號(hào); PDF 表單 JavaScript 動(dòng)作中使用分號(hào)</para></param>
public string GetTO(string separator = ",")
{
    return string.Join(separator, Recipients.ToArray());
}

/// <summary>獲取指定分隔符連接的抄送地址</summary>
public string GetCC(string separator = ",")
{
    return string.Join(separator, CcRecipients.ToArray());
}

/// <summary>獲取指定分隔符連接的密送地址</summary>
public string GetBCC(string separator = ",")
{
    return string.Join(separator, BccRecipients.ToArray());
}

調(diào)用 mailto 關(guān)聯(lián)郵箱

/// <summary>通過(guò) mailto 協(xié)議調(diào)用默認(rèn)郵箱客戶端發(fā)送郵件</summary>
/// <remarks>不支持附件, 支持 Outlook(New)</remarks>
public static bool SendByProtocol(MailInfo info)
{
    bool isOutlook = IsOutlookProgID(FindMailToClientProgID());
    string separator = isOutlook ? ";" : ","; // Outlook 僅支持分號(hào), 其他客戶端支持標(biāo)準(zhǔn)的逗號(hào)
    var url = new StringBuilder("mailto:");
    url.Append(info.GetTO(separator));
    url.Append("?");
    string cc = info.GetCC(separator);
    string bcc = info.GetBCC(separator);
    if (!string.IsNullOrEmpty(cc))
    {
        url.Append($"cc={Uri.EscapeDataString(cc)}&");
    }
    if (!string.IsNullOrEmpty(bcc))
    {
        url.Append($"bcc={Uri.EscapeDataString(bcc)}&");
    }
    if (!string.IsNullOrEmpty(info.Subject))
    {
        url.Append($"subject={Uri.EscapeDataString(info.Subject)}&");
    }
    if (!string.IsNullOrEmpty(info.Body))
    {
        url.Append($"body={Uri.EscapeDataString(info.Body)}&");
    }
    url.Remove(url.Length - 1, 1);
    var startInfo = new ProcessStartInfo
    {
        FileName = url.ToString(),
        UseShellExecute = true,
    };
    try
    {
        Process.Start(startInfo);
        return true;
    }
    catch
    {
        return false;
    }
}

Win32 MAPI

Windows 定義了 MAPI 接口供第三方郵箱應(yīng)用實(shí)現(xiàn)集成,例如 Outlook(Classic)、eM Client、Thunderbird

C# 中可以使用 MAPISendMail 進(jìn)行調(diào)用,需要注意不一定成功,可能會(huì)遇到未知的MAPI_E_FAILURE錯(cuò)誤

另外 MAPI 方式支持設(shè)置是否顯示 UI (MAPI_DIALOG、MAPI_DIALOG_MODELESS、MAPI_LOGON_UI)

可以為上文中的 MailInfo 類(lèi)添加一個(gè)是否顯示 UI 的屬性

/// <summary>是否不顯示UI自動(dòng)發(fā)送, 至少需要一名收件人</summary>
public bool WithoutUI { get; set; }

MAPI 關(guān)聯(lián)應(yīng)用

支持 MAPI 的郵箱應(yīng)用一般會(huì)在{HKLM|HKCU}\SOFTWARE\Clients\Mail下寫(xiě)入子項(xiàng)

通過(guò)修改 Mail 項(xiàng)默認(rèn)鍵值修改默認(rèn) MAPI 郵箱,HKCU 優(yōu)先,鍵值需要與 Mail 子項(xiàng)名稱一致

/// <summary>查找 MAPI 郵箱客戶端</summary>
private static string FindMAPIClientName()
{
    using (var cuKey = Registry.CurrentUser.OpenSubKey(MapiKeyPath))
    using (var lmKey = Registry.LocalMachine.OpenSubKey(MapiKeyPath))
    {
        var cuKeyNames = cuKey?.GetSubKeyNames() ?? new string[0];
        var lmKeyNames = lmKey?.GetSubKeyNames() ?? new string[0];
        // HKCU 可獲取 HKLM 子健, HKLM 不可反向獲取 HKCU 子健
        cuKeyNames = cuKeyNames.Concat(lmKeyNames).ToArray();
        string cuValue = cuKey?.GetValue(null)?.ToString();
        if (cuKeyNames.Contains(cuValue, StringComparer.OrdinalIgnoreCase))
        {
            return cuValue;
        }
        string lmValue = lmKey?.GetValue(null)?.ToString();
        if (lmKeyNames.Contains(lmValue, StringComparer.OrdinalIgnoreCase))
        {
            return lmValue;
        }
    }
    return null;
}

調(diào)用 MAPI 關(guān)聯(lián)郵箱

文件系統(tǒng)對(duì)象右鍵菜單的發(fā)送到子菜單中的就是調(diào)用的 MAPI 關(guān)聯(lián)郵箱

未設(shè)置 MAPI 關(guān)聯(lián)郵箱時(shí)調(diào)用會(huì)彈窗提示,如果 Mail 項(xiàng)中PreFirstRun鍵值不為空,則彈窗優(yōu)先顯示其內(nèi)容,*分隔內(nèi)容和標(biāo)題

但此彈窗內(nèi)容會(huì)誤導(dǎo)用戶,因?yàn)榭刂泼姘迥J(rèn)程序中只能設(shè)置 mailto 關(guān)聯(lián)郵箱而不能設(shè)置 MAPI 關(guān)聯(lián)郵箱,兩者無(wú)關(guān)

另外建議異步調(diào)用,否則外部出錯(cuò)可能會(huì)卡死進(jìn)程

比如同時(shí)安裝了 Outlook(Classic) 和 Outlook(New) 且啟用 New 時(shí),Outlook(Classic)后臺(tái)啟動(dòng)后會(huì)調(diào)起 Outlook(New)并結(jié)束自身,提前關(guān)閉了 Outlook(New) 主窗口, 或設(shè)置了收件人、抄送、密送

而且下文中的 Outlook COM 和命令行方式也都是只支持 Classic 不支持 New,所以我們需要一個(gè)判斷是否啟用了 Outlook(New) 的方法

這里我們可以使用 AssocQueryString 根據(jù) ProgID 獲取其友好名稱來(lái)判斷是否安裝了新版 Outlook,上文代碼中也提到了 Win10 以上系統(tǒng)可以用 AssocQueryString 直接查詢 mailto 關(guān)聯(lián)的 ProgID,而下文中也會(huì)用其根據(jù) ProgID 獲取關(guān)聯(lián)可執(zhí)行文件路徑

/// <summary>是否同時(shí)安裝了 Outlook Classic 和 New 兩個(gè)版本,且啟用 New</summary>
public static bool IsUseNewOutlook()
{
    string name = NativeMethods.AssocQueryString(AssocStr.FriendlyAppName, OutlookNewProgID);
    bool existsNew = name.Equals("Outlook", StringComparison.OrdinalIgnoreCase);
    if (existsNew)
    {
        string regPath = @"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\Outlook\Preferences";
        bool useNew = Convert.ToInt32(Registry.GetValue(regPath, "UseNewOutlook", 0)) == 1;
        if (useNew)
        {
            return true;
        }
    }
    return false;
}

另外如果只安裝了 Outlook(New)(Win11 默認(rèn)預(yù)裝)的情況下,無(wú)法通過(guò) MAPI 方式調(diào)起,如若可獲知 Outlook(Classic) 是如何啟動(dòng) Outlook(New) 即可有方法單獨(dú)啟動(dòng) Outlook(New)?,F(xiàn)今未找到只安裝了 Outlook(New) 創(chuàng)建帶附件郵件的方法

const string OutlookClientName = "Microsoft Outlook";

/// <summary>通過(guò) Win32 MAPI 發(fā)送郵件</summary>
/// <remarks>?: 調(diào)用 MAPI 方式是同步執(zhí)行,外部出錯(cuò)可能會(huì)卡死進(jìn)程,
/// <para>比如同時(shí)安裝了 Outlook(Classic) 和 Outlook(New) 且啟用 New 時(shí),</para>
/// <para>提前關(guān)閉了 Outlook(New) 主窗口, 或設(shè)置了收件人、抄送、密送</para></remarks>
public static bool SendByMAPI(MailInfo info)
{
    var msg = new MapiMessage
    {
        subject = info.Subject,
        noteText = info.Body,
    };
    var recipients =
        info.Recipients.Select(x => MapiRecipDesc.Create(x, RecipClass.TO)).Concat(
        info.CcRecipients.Select(x => MapiRecipDesc.Create(x, RecipClass.CC))).Concat(
        info.BccRecipients.Select(x => MapiRecipDesc.Create(x, RecipClass.BCC))).ToArray();
    if (recipients.Length > 0)
    {
        // 測(cè)試設(shè)置了收件人、抄送、密送 Outlook(New) 會(huì)卡住
        if (OutlookClientName.Equals(FindMAPIClientName(), StringComparison.OrdinalIgnoreCase) && IsUseNewOutlook())
            return false;
        IntPtr pRecips = NativeMethods.GetStructArrayPointer(recipients);
        if (pRecips != IntPtr.Zero)
        {
            msg.recips = pRecips;
            msg.recipCount = recipients.Length;
        }
    }
    var attachments = info.Attachments.Select(x => MapiFileDesc.Create(x.Value, x.Key)).ToArray();
    if (attachments.Length > 0)
    {
        IntPtr pFiles = NativeMethods.GetStructArrayPointer(attachments);
        if (pFiles != IntPtr.Zero)
        {
            msg.files = pFiles;
            msg.fileCount = attachments.Length;
        }
    }
    var flags = MapiFlags.ForceUnicode;
    if (!(info.WithoutUI && info.Recipients.Count > 0))
    {
        flags |= MapiFlags.DialogModeless | MapiFlags.LogonUI;
    }
    try
    {
        var error = NativeMethods.MAPISendMail(IntPtr.Zero, IntPtr.Zero, msg, flags, 0);
        if (error == MapiError.UnicodeNotSupported)
        {
            flags &= ~MapiFlags.ForceUnicode; // 不支持 Unicode 時(shí)移除標(biāo)志
            error = NativeMethods.MAPISendMail(IntPtr.Zero, IntPtr.Zero, msg, flags, 0);
        }
        return error == MapiError.Success || error == MapiError.UserAbort;
    }
    finally
    {
        NativeMethods.FreeStructArrayPointer<MapiRecipDesc>(msg.recips, recipients.Length);
        NativeMethods.FreeStructArrayPointer<MapiFileDesc>(msg.files, attachments.Length);
    }
}

用到的本機(jī)方法、結(jié)構(gòu)體、枚舉

static class NativeMethods
{
    [DllImport("mapi32.dll", CharSet = CharSet.Auto)]
    public static extern MapiError MAPISendMail(IntPtr session, IntPtr hWndParent, MapiMessage message, MapiFlags flags, int reserved);

    [DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
    public static extern int AssocQueryString(AssocFlags assocFlag, AssocStr assocStr, string pszAssoc, string pszExtra, StringBuilder pszOut, ref int pcchOut);

    public static string AssocQueryString(AssocStr type, string assocStr, AssocFlags flags = AssocFlags.None)
    {
        int length = 0;
        AssocQueryString(flags, type, assocStr, null, null, ref length); // 獲取長(zhǎng)度
        var sb = new StringBuilder(length);
        AssocQueryString(flags, type, assocStr, null, sb, ref length);
        return sb.ToString();
    }

    /// <summary>獲取結(jié)構(gòu)體數(shù)組指針</summary>
    public static IntPtr GetStructArrayPointer<T>(T[] array) where T : struct
    {
        IntPtr hglobal = IntPtr.Zero;
        int copiedCount = 0;
        try
        {
            int size = Marshal.SizeOf(typeof(T));
            hglobal = Marshal.AllocHGlobal(size * array.Length);
            for (int i = 0; i < array.Length; i++)
            {
                IntPtr ptr = new IntPtr(hglobal.ToInt64() + i * size);
                Marshal.StructureToPtr(array[i], ptr, false);
                copiedCount++;
            }
        }
        catch
        {
            FreeStructArrayPointer<T>(hglobal, copiedCount);
            throw;
        }
        return hglobal;
    }

    /// <summary>釋放結(jié)構(gòu)體數(shù)組指針</summary>
    public static void FreeStructArrayPointer<T>(IntPtr ptr, int count) where T : struct
    {
        if (ptr != IntPtr.Zero && count > 0)
        {
            int size = Marshal.SizeOf(typeof(T));
            for (int i = 0; i < count; i++)
            {
                IntPtr itemPtr = new IntPtr(ptr.ToInt64() + i * size);
                Marshal.DestroyStructure(itemPtr, typeof(T));
            }
            Marshal.FreeHGlobal(ptr);
        }
    }
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct MapiMessage
{
    public int reserved;
    public string subject;
    public string noteText;
    public string messageType;
    public string dateReceived;
    public string conversationID;
    public int flags;
    public IntPtr originator;
    public int recipCount;
    public IntPtr recips;
    public int fileCount;
    public IntPtr files;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct MapiRecipDesc
{
    public int reserved;
    public RecipClass recipClass;
    public string name;
    public string address;
    public int eIDSize;
    public IntPtr entryID;

    public static MapiRecipDesc Create(MailAddress address, RecipClass recipClass = RecipClass.TO)
    {
        var result = new MapiRecipDesc
        {
            name = address.DisplayName,
            address = address.Address,
            recipClass = recipClass,
        };
        if (string.IsNullOrEmpty(result.name))
        {
            // Outlook name 不可為空, em Client 可設(shè) address 或 name
            result.name = result.address;
        }
        return result;
    }
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct MapiFileDesc
{
    public int reserved;
    public int flags;
    public int position;
    public string pathName;
    public string fileName;
    public IntPtr fileType; // MapiFileTagExt

    public static MapiFileDesc Create(string filePath, string fileName = null)
    {
        return new MapiFileDesc
        {
            pathName = filePath,
            fileName = fileName ?? Path.GetFileName(filePath),
            position = -1, // 不指示附件位置
        };
    }
}

[Flags]
enum MapiFlags
{
    LogonUI = 0x1,
    NewSession = 0x2,
    Dialog = 0x8,
    DialogModeless = 0x4 | Dialog,
    ForceUnicode = 0x40000,
}

enum MapiError
{
    /// <summary>成功</summary>
    Success = 0,
    /// <summary>用戶中止</summary>
    UserAbort = 1,
    /// <summary>發(fā)生一個(gè)或多個(gè)未指定錯(cuò)誤</summary>
    Failure = 2,
    /// <summary>登錄失敗</summary>
    LoginFailure = 3,
    /// <summary>內(nèi)存不足</summary>
    InsufficientMemory = 5,
    /// <summary>文件附件太多</summary>
    TooManyFiles = 9,
    /// <summary>收件人太多</summary>
    TooManyRecipients = 10,
    /// <summary>找不到附件</summary>
    AttachmentNotFound = 11,
    /// <summary>無(wú)法打開(kāi)附件</summary>
    AttachmentOpenFailure = 12,
    /// <summary>收件人未顯示在地址列表中</summary>
    UnknownRecipient = 14,
    /// <summary>收件人類(lèi)型錯(cuò)誤</summary>
    BadRecipient = 15,
    /// <summary>消息中文本太大</summary>
    TextTooLarge = 18,
    /// <summary>收件人與多個(gè)收件人描述符結(jié)構(gòu)匹配,且未設(shè)置 MAPI_DIALOG</summary>
    AmbiguousRecipient = 21,
    /// <summary>一個(gè)或多個(gè)收件人無(wú)效</summary>
    InvalidRecips = 25,
    /// <summary>指定了 MAPI_FORCE_UNICODE 標(biāo)志,但不支持 Unicode</summary>
    UnicodeNotSupported = 27,
    /// <summary>附件太大</summary>
    AttachmentTooLarge = 28,
}

[Flags]
enum AssocFlags
{
    None = 0,
    InitNoreMapClsid = 0x1,
    InitByExeName = 0x2,
    InitDefaultToStar = 0x4,
    InitDefaultToFolder = 0x8,
    NoUserSettings = 0x10,
    NotRunCate = 0x20,
    Verify = 0x40,
    RemapRunDll = 0x80,
    NoFixups = 0x100,
    IgnoreBaseClass = 0x200,
    InitIgnoreUnknown = 0x400,
    InitFixedProgID = 0x800,
    IsProtocol = 0x1000,
    InitForFile = 0x2000
}

enum AssocStr
{
    Command = 1,
    Executable,
    FriendlyDocName,
    FriendlyAppName,
    NoOpen,
    ShellNewValue,
    DDECommand,
    DDEIfExec,
    DDEApplication,
    DDEToPIC,
    InfoTip,
    QuickTip,
    TileInfo,
    ContentType,
    DefaultIcon,
    ShellExtension,
    DropTarget,
    DelegateExecute,
    SupportedURIProtocols,
    ProgID,
    AppID,
    AppPublisher,
    AppIconReference
}

調(diào)用其他 MAPI 郵箱

已知第三方郵箱應(yīng)用包含 MAPI 相關(guān)導(dǎo)出函數(shù)的 dll 位置時(shí),可通過(guò) GetProcAddress 來(lái)調(diào)用

[DllImport("kernel32.dll")]
extern static IntPtr LoadLibrary(string lpLibFileName);

[DllImport("kernel32.dll")]
extern static IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

[DllImport("kernel32.dll")]
extern static bool FreeLibrary(IntPtr hLibModule);

// 定義與 MAPISendMail 方法相同簽名的委托
delegate MapiError MAPISendMailDelegate(IntPtr session, IntPtr hWndParent, MapiMessage message, MapiFlags flags, int reserved);

public static bool SendMail(MapiMessage msg, MapiFlags flags, string dllPath)
{
    IntPtr hLib = LoadLibrary(dllPath);
    if(hLib != IntPtr.Zero)
    {
        try
        {
            IntPtr hProc = GetProcAddress(hLib, "MAPISendMail");
            if(hProc != IntPtr.Zero)
            {
                var func = Marshal.GetDelegateForFunctionPointer(hProc, typeof(MAPISendMailDelegate) as MAPISendMailDelegate);
                var error = func?.Invoke(IntPtr.Zero, IntPtr.Zero, msg, flags, 0);
                return error == MapiError.Success || error == MapiError.UserAbort;
            }
        }
        finally
        {
            FreeLibrary(hLib);
        }
    }
    return false;
}

上述方法需要調(diào)用程序和 dll 位數(shù)相同,故為了兼容不同郵箱應(yīng)用可能需要分別編譯 x64 和 x86 的程序

這里提供一種兼容不同位數(shù)郵箱應(yīng)用的方法:臨時(shí)將目標(biāo)郵箱設(shè)為 MAPI 關(guān)聯(lián)郵箱,調(diào)用 MAPISendMail 后還原

/// <summary>通過(guò) Win32 MAPI 發(fā)送郵件</summary>
/// <remarks>Microsoft Outlook、eM Client、Mozilla Thunderbird 支持, 其他待發(fā)現(xiàn)</remarks>
private static bool SendByMAPI(MailInfo info, string clientName)
{
    if (string.IsNullOrEmpty(clientName))
    {
        return false;
    }
    if (FindMAPIClientName() == clientName)
    {
        return SendByMAPI(info);
    }
    else
    {
        try
        {
            using (var key = Registry.CurrentUser.OpenSubKey(MapiKeyPath, true)
                ?? Registry.CurrentUser.CreateSubKey(MapiKeyPath))
            {
                string currentValue = key.GetValue(null)?.ToString();
                key.SetValue(null, clientName);
                bool success = SendByMAPI(info);
                if (currentValue != null)
                {
                    key.SetValue(null, currentValue);
                }
                else
                {
                    key.DeleteValue(null, false);
                }
                return success;
            }
        }
        catch
        {
            return false;
        }
    }
}

Outlook(Classic)

Outlook(Classic) 還支持 COM 互操作和命令行的方式創(chuàng)建帶附件的郵件,Outlook(New) 兩種方式都不支持

/// <summary>通過(guò) Outlook 發(fā)送郵件</summary>
public static bool SendByOutlook(MailInfo info)
{
    return SendByOutlookMAPI(info) || SendByOutlookWithoutMAPI(info);
}

/// <summary>通過(guò) Outlook MAPI 發(fā)送郵件</summary>
public static bool SendByOutlookMAPI(MailInfo info)
{
    return SendByMAPI(info, OutlookClientName);
}

/// <summary>通過(guò) Outlook COM 或進(jìn)程方式發(fā)送郵件</summary>
public static bool SendByOutlookWithoutMAPI(MailInfo info)
{
    return !IsUseNewOutlook() && (SendByOutlookCOM(info) || SendByOutlookProcess(info));
}

Outlook COM

通過(guò)引用 Microsoft.Office.Interop.Outlook 互操作庫(kù)可用 COM 對(duì)象來(lái)創(chuàng)建帶附件的郵件,支持添加多個(gè)附件

當(dāng)同時(shí)安裝了 Classic 和 New 且啟用 New 時(shí)此方式無(wú)效:會(huì)卡在創(chuàng)建 app 對(duì)象

using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Outlook;

/// <summary>通過(guò) Outlook COM 對(duì)象發(fā)送郵件</summary>
/// <remarks>?: 當(dāng)同時(shí)安裝了 Outlook(Classic) 和 Outlook(New) 且啟用 New 時(shí)會(huì)卡在創(chuàng)建 app 對(duì)象</remarks>
public static bool SendByOutlookCOM(MailInfo info)
{
    Application app = null;
    MailItem mail = null;
    Attachments attachments = null;
    try
    {
        app = (Application)Marshal.GetActiveObject("Outlook.Application");
    }
    catch
    {
        // 未找到活動(dòng)的 Outlook 實(shí)例
    }
    bool isRunning = app != null; // Outlook 同時(shí)只允許一個(gè)實(shí)例進(jìn)程
    try
    {
        if (!isRunning)
        {
            app = new Application(); // 同時(shí)安裝 Classic 和 New 且啟用 New 時(shí)會(huì)卡在這里
        }
        mail = app.CreateItem(OlItemType.olMailItem) as MailItem;
        mail.Subject = info.Subject;
        mail.Body = info.Body;
        mail.To = info.GetTO(";");
        mail.CC = info.GetCC(";");
        mail.BCC = info.GetBCC(";");
        if (info.Attachments != null)
        {
            attachments = mail.Attachments;
            foreach (var file in info.Attachments.Values)
            {
                attachments.Add(file);
            }
        }
        if (info.WithoutUI && info.Recipients.Count > 0)
        {
            mail.Send();
        }
        else
        {
            mail.Display(false);
        }
        return true;
    }
    catch
    {
        if (!isRunning)
        {
            app?.Quit(); // 之前未運(yùn)行時(shí),啟動(dòng)的新實(shí)例遇到錯(cuò)誤時(shí)關(guān)閉程序
        }
        return false;
    }
    finally
    {
        if (attachments != null)
        {
            Marshal.ReleaseComObject(attachments);
        }
        if (mail != null)
        {
            Marshal.ReleaseComObject(mail);
        }
        if (app != null)
        {
            Marshal.ReleaseComObject(app);
        }
    }
}

Outlook 命令行

命令行方式只能添加一個(gè)附件

當(dāng)同時(shí)安裝了 Outlook(Classic) 和 Outlook(New) 且啟用 New 時(shí)此方式無(wú)效

示例:outlook.exe /c ipm.note /m example@to.com?subject=Test%20Subject /a C:\dir\file

/// <summary>獲取 Outlook 程序文件位置</summary>
public static string GetOutlookPath()
{
    // 此 CLSID 為固定值,與 Microsoft.Office.Interop.Outlook.ApplicationClass 的 GUID 值相同
    string regPath = @"HKEY_CLASSES_ROOT\CLSID\{0006F03A-0000-0000-C000-000000000046}\LocalServer32";
    string filePath = Registry.GetValue(regPath, null, null)?.ToString();
    return filePath;
}

/// <summary>通過(guò) Outlook 命令行方式發(fā)送郵件</summary>
/// <remarks>僅支持添加一個(gè)附件
/// <para>?: 當(dāng)同時(shí)安裝了 Outlook(Classic) 和 Outlook(New) 且啟用 New 時(shí)命令行方式無(wú)效</para></remarks>
public static bool SendByOutlookProcess(MailInfo info)
{
    string fileName = GetOutlookPath();
    if (File.Exists(fileName))
    {
        var args = new StringBuilder($"/c ipm.note");
        bool hasTO = info.Recipients.Count > 0;
        bool hasCC = info.CcRecipients.Count > 0;
        bool hasBCC = info.BccRecipients.Count > 0;
        bool hasSubject = !string.IsNullOrEmpty(info.Subject);
        bool hasBody = !string.IsNullOrEmpty(info.Body);
        if (hasTO || hasSubject || hasBody)
        {
            args.Append(" /m ");
            if (hasTO)
            {
                args.Append($"{Uri.EscapeDataString(info.GetTO(";"))}");
            }
            args.Append("?");
            if (hasCC)
            {
                args.Append($"cc={Uri.EscapeDataString(info.GetCC(";"))}&");
            }
            if (hasBCC)
            {
                args.Append($"bcc={Uri.EscapeDataString(info.GetBCC(";"))}&");
            }
            if (hasSubject)
            {
                args.Append($"subject={Uri.EscapeDataString(info.Subject)}&");
            }
            if (hasBody)
            {
                args.Append($"body={Uri.EscapeDataString(info.Body)}&");
            }
            args.Remove(args.Length - 1, 1);
        }
        if (info.Attachments.Count > 0)
        {
            args.Append($" /a \"{info.Attachments.First().Value}\""); // 僅支持添加一個(gè)附件
        }
        Process.Start(fileName, args.ToString());
        return true;
    }
    return false;
}

其他郵箱應(yīng)用

針對(duì)下方已知 ProgID 且支持命令行方式的郵箱應(yīng)用,利用 AssocQueryString 可以快速獲取可執(zhí)行文件路徑

/// <summary>通過(guò)關(guān)聯(lián)字符串查找可執(zhí)行文件位置</summary>
private static string GetExecutePath(string assocString)
{
    return NativeMethods.AssocQueryString(AssocStr.Executable, assocString);
}

Mozilla Thunderbird

Command line arguments - Thunderbird - MozillaZine Knowledge Base

const string ThunderbirdClientName = "Mozilla Thunderbird";

/// <summary>獲取 Mozilla Thunderbird 程序文件位置</summary>
public static string GetThunderbirdPath()
{
    return GetExecutePath(ThunderbirdProgID);
}

/// <summary>通過(guò) Mozilla Thunderbird 發(fā)送郵件</summary>
public static bool SendByThunderbird(MailInfo info)
{
    return SendByMAPI(info, ThunderbirdClientName) || SendByThunderbirdProcess(info);
}

/// <summary>通過(guò) Mozilla Thunderbird 程序發(fā)送郵件</summary>
public static bool SendByThunderbirdProcess(MailInfo info)
{
    string exePath = GetThunderbirdPath();
    if (File.Exists(exePath))
    {
        var options = new List<string>();
        if (info.Recipients.Count > 0)
        {
            options.Add($"to='{info.GetTO()}'");
        }
        if (info.CcRecipients.Count > 0)
        {
            options.Add($"cc='{info.GetCC()}'");
        }
        if (info.BccRecipients.Count > 0)
        {
            options.Add($"bcc='{info.GetBCC()}'");
        }
        if (!string.IsNullOrEmpty(info.Subject))
        {
            string subject = info.Subject.Replace("',", "' ,"); // ',截?cái)鄷?huì)導(dǎo)致參數(shù)解析錯(cuò)誤
            options.Add($"subject='{subject}'");
        }
        if (!string.IsNullOrEmpty(info.Body))
        {
            string body = info.Body.Replace("',", "' ,"); // 同上
            options.Add($"body='{body}'");
        }
        if (info.Attachments.Count > 0)
        {
            var files = info.Attachments.Values.Select(x => new Uri(x).AbsoluteUri).ToArray();
            options.Add($"attachment='{string.Join("','", files)}'");
        }
        string args = "-compose";
        if (options.Count > 0)
        {
            args += " \"" + string.Join(",", options.ToArray()) + "\"";
        }
        Process.Start(exePath, args);
        return true;
    }
    return false;
}

eM Client

New mail with multiple attachments with command line

eM Client 命令行方式是通過(guò)創(chuàng)建 .eml 文件并打開(kāi)的方式創(chuàng)建郵件

const string EMClientClientName = "eM Client";

/// <summary>獲取 eM Client 程序文件位置</summary>
public static string GetEmClientPath()
{
    return GetExecutePath(EMClientProgID);
}

/// <summary>通過(guò) eM Client 發(fā)送郵件</summary>
public static bool SendByEmClient(MailInfo info)
{
    return SendByMAPI(info, EMClientClientName) || SendByEmClientProcess(info);
}

/// <summary>通過(guò) eM Client 程序發(fā)送郵件</summary>
/// <remarks>通過(guò)創(chuàng)建 .eml 臨時(shí)文件的方式發(fā)送</remarks>
public static bool SendByEmClientProcess(MailInfo info)
{
    string exePath = GetEmClientPath();
    if (File.Exists(exePath))
    {
        using (var mail = new MailMessage())
        {
            mail.Subject = info.Subject;
            mail.Body = info.Body;
            info.Recipients.ForEach(mail.To.Add);
            info.CcRecipients.ForEach(mail.CC.Add);
            info.BccRecipients.ForEach(mail.Bcc.Add);
            foreach (var file in info.Attachments.Values)
            {
                mail.Attachments.Add(new System.Net.Mail.Attachment(file));
            }
            string from = "from@exmple.com";
            string to = null;
            mail.From = new MailAddress(from); // 必須設(shè)置發(fā)件人地址, 否則會(huì)報(bào)錯(cuò)
            if (info.Recipients.Count == 0)
            {
                to = "to@exmple.com";
                mail.To.Add(to); // 至少有一個(gè)收件人, 否則會(huì)報(bào)錯(cuò)
            }
            var client = new SmtpClient();
            client.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;

            string tempDir = Path.Combine(Path.GetTempPath(), "tempMail");
            try
            {
                Directory.Delete(tempDir, true);
            }
            catch
            {
                // ignore
            }
            Directory.CreateDirectory(tempDir);
            client.PickupDirectoryLocation = tempDir;
            client.Send(mail);

            var emlFile = new DirectoryInfo(tempDir).GetFiles("*.eml").OrderByDescending(x => x.LastWriteTime).FirstOrDefault();
            if (emlFile != null)
            {
                string emlPath = emlFile.FullName;
                var lines = File.ReadAllLines(emlPath, Encoding.UTF8).ToList();
                lines.Remove($"X-Sender: {from}");
                lines.Remove($"From: {from}");
                if (to != null)
                {
                    lines.Remove($"X-Receiver: {to}");
                    lines.Remove($"To: {to}");
                }
                lines.Insert(0, "X-Unsent: 1"); // 標(biāo)記為未發(fā)送
                File.WriteAllLines(emlPath, lines.ToArray(), Encoding.UTF8);
                var process = Process.Start(exePath, $"/open \"{emlPath}\"");
                process.EnableRaisingEvents = true;
                process.Exited += (s, e) =>
                {
                    try
                    {
                        Directory.Delete(tempDir, true);
                    }
                    catch
                    {
                        // ignore
                    }
                };
                return true;
            }
        }
    }
    return false;
}

網(wǎng)易郵箱大師

命令行來(lái)自于文件系統(tǒng)對(duì)象發(fā)送到子菜單中的快捷方式

const string MailMasterProgID = "MailMaster";

/// <summary>獲取網(wǎng)易郵箱大師程序文件位置</summary>
public static string GetMailMasterPath()
{
    return GetExecutePath(MailMasterProgID);
}

/// <summary>通過(guò)網(wǎng)易郵箱大師發(fā)送郵件</summary>
/// <remarks>命令來(lái)自于"發(fā)送到"菜單目錄快捷方式</remarks>
public static bool SendByMailMaster(MailInfo info)
{
    string exePath = GetMailMasterPath();
    if (File.Exists(exePath))
    {
        var args = new StringBuilder();
        if (info.Attachments.Count > 0)
        {
            args.Append($"--send-as-attachment \"{info.Attachments.First().Value}\"");
        }
        Process.Start(exePath, args.ToString());
        return true;
    }
    return false;
}

調(diào)用默認(rèn)郵箱

綜上,Windows 上默認(rèn)郵箱有 mailto 關(guān)聯(lián)郵箱和 MAPI 關(guān)聯(lián)郵箱,但不懂注冊(cè)表的普通用戶可能只會(huì)在控制面板更改 mailto 關(guān)聯(lián)郵箱,為提高兼容性,我們可以用以下步驟一一嘗試調(diào)用默認(rèn)郵箱:

  • 當(dāng) MAPI 關(guān)聯(lián)郵箱存在時(shí)(避免系統(tǒng)彈窗提示無(wú)關(guān)聯(lián)郵箱),直接調(diào)用 MAPI 關(guān)聯(lián)郵箱
  • 讀取 mailto 關(guān)聯(lián)郵箱 ProgID,并嘗試在 MAPI Mail 注冊(cè)表子項(xiàng)下找到對(duì)應(yīng)的項(xiàng),臨時(shí)設(shè)為 MAPI 關(guān)聯(lián)郵箱調(diào)用
  • MAPI 方式失敗后,嘗試使用 COM 或命令行方式
  • 以上支持添加附件的方式都失敗后,最后使用 mailto 方式
/// <summary>通過(guò)默認(rèn)的郵箱客戶端發(fā)送郵件</summary>
public static bool SendByDefault(MailInfo info)
{
    string progID = null;
    string clientName = FindMAPIClientName();
    if (clientName == null)
    {
        // 未設(shè)置 MAPI 客戶端時(shí), 嘗試查找 mailto 協(xié)議關(guān)聯(lián)的客戶端是否支持 MAPI
        progID = FindMailToClientProgID();
        clientName = FindMAPIClientName(progID);
    }
    // 優(yōu)先使用 MAPI 發(fā)送郵件
    bool success = SendByMAPI(info, clientName);
    if (!success)
    {
        progID = progID ?? FindMailToClientProgID();
        var st = StringComparison.OrdinalIgnoreCase;
        if (IsOutlookProgID(progID))
        {
            success = SendByOutlookWithoutMAPI(info);
        }
        else if (progID.Equals(EMClientProgID, st))
        {
            success = SendByEmClientProcess(info);
        }
        else if (progID.Equals(ThunderbirdProgID, st))
        {
            success = SendByThunderbirdProcess(info);
        }
        else if (progID.Equals(MailMasterProgID, st))
        {
            success = SendByMailMaster(info);
        }
        if (!success)
        {
            // 如果以上方式都失敗了最后嘗試 mailto 協(xié)議
            success = SendByProtocol(info);
        }
    }
    return success;
}

/// <summary>根據(jù) ProgID 查找 MAPI 郵箱客戶端名稱</summary>
private static string FindMAPIClientName(string progID)
{
    if (string.IsNullOrEmpty(progID))
    {
        return null;
    }
    using (var cuKey = Registry.CurrentUser.OpenSubKey(MapiKeyPath))
    using (var lmKey = Registry.LocalMachine.OpenSubKey(MapiKeyPath))
    {
        var cuKeyNames = cuKey?.GetSubKeyNames().ToList() ?? new List<string>();
        var lmKeyNames = lmKey?.GetSubKeyNames().ToList() ?? new List<string>();
        if (IsOutlookProgID(progID))
        {
            string name = OutlookClientName; // Microsoft Outlook 沒(méi)有 Capabilities\URLAssociations 子項(xiàng)
            if (lmKeyNames.Contains(name, StringComparer.OrdinalIgnoreCase)
             || cuKeyNames.Contains(name, StringComparer.OrdinalIgnoreCase))
            {
                return name;
            }
        }
        else
        {
            var dic = new Dictionary<RegistryKey, List<string>>
            {
                [lmKey] = lmKeyNames,
                [cuKey] = cuKeyNames
            };
            foreach (var item in dic)
            {
                foreach (var keyName in item.Value)
                {
                    using (var key = item.Key.OpenSubKey($@"{keyName}\Capabilities\URLAssociations"))
                    {
                        string value = key?.GetValue("mailto")?.ToString();
                        if (progID.Equals(value, StringComparison.OrdinalIgnoreCase))
                        {
                            return keyName;
                        }
                    }
                }
            }
        }
    }
    return null;
}

以上就是C#調(diào)用郵箱應(yīng)用發(fā)送帶附件的郵件的詳細(xì)內(nèi)容,更多關(guān)于C#發(fā)送郵件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • c# 幾個(gè)常見(jiàn)的TAP異步操作

    c# 幾個(gè)常見(jiàn)的TAP異步操作

    這篇文章主要介紹了c# 幾個(gè)常見(jiàn)的TAP異步操作,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下
    2021-04-04
  • C# SDK實(shí)現(xiàn)百度云OCR的文字識(shí)別功能

    C# SDK實(shí)現(xiàn)百度云OCR的文字識(shí)別功能

    這篇文章主要為大家詳細(xì)介紹了C# SDK實(shí)現(xiàn)百度云OCR的文字識(shí)別功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • C#特性 迭代器(下) yield以及流的延遲計(jì)算

    C#特性 迭代器(下) yield以及流的延遲計(jì)算

    這篇文章主要介紹了C#特性 迭代器(下) yield以及流的延遲計(jì)算,需要的朋友可以參考下
    2014-12-12
  • 實(shí)例代碼講解c# 線程(下)

    實(shí)例代碼講解c# 線程(下)

    這篇文章主要介紹了c# 線程的的相關(guān)資料,文中示例代碼非常細(xì)致,對(duì)大家的學(xué)習(xí)有很大幫助,感興趣的朋友可以了解下
    2020-06-06
  • 詳解C#中的依賴注入和IoC容器

    詳解C#中的依賴注入和IoC容器

    這篇文章主要介紹了C#中的依賴注入和IoC容器的相關(guān)資料,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下
    2020-12-12
  • Unity3D基于陀螺儀實(shí)現(xiàn)VR相機(jī)功能

    Unity3D基于陀螺儀實(shí)現(xiàn)VR相機(jī)功能

    這篇文章主要為大家詳細(xì)介紹了Unity3D基于陀螺儀實(shí)現(xiàn)VR相機(jī)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • C#實(shí)現(xiàn)Word轉(zhuǎn)Excel的兩種實(shí)用方法分享

    C#實(shí)現(xiàn)Word轉(zhuǎn)Excel的兩種實(shí)用方法分享

    在實(shí)際項(xiàng)目里,經(jīng)常碰到這種需求:客戶發(fā)來(lái)一份 Word 報(bào)告,里面有好幾個(gè)數(shù)據(jù)表格,或者整篇文檔需要轉(zhuǎn)成 Excel 做進(jìn)一步處理,因此本文介紹了使用Spire.Docfor.NET庫(kù)將Word文檔轉(zhuǎn)換為Excel文件的兩種方法,需要的朋友可以參考下
    2026-05-05
  • c#入門(mén)之類(lèi)型轉(zhuǎn)換詳解

    c#入門(mén)之類(lèi)型轉(zhuǎn)換詳解

    這篇文章主要介紹了c#的類(lèi)型轉(zhuǎn)換詳解,類(lèi)型轉(zhuǎn)換分兩種形式:隱式轉(zhuǎn)換、顯示轉(zhuǎn)換,下面是詳細(xì)介紹
    2014-04-04
  • C#實(shí)現(xiàn)字母與ASCII碼互相轉(zhuǎn)換

    C#實(shí)現(xiàn)字母與ASCII碼互相轉(zhuǎn)換

    ASCII是基于拉丁字母的編碼系統(tǒng),也是現(xiàn)今最通用的單字節(jié)編碼系統(tǒng),本文主要為大家詳細(xì)介紹了如何使用C#實(shí)現(xiàn)字母與ASCII碼互轉(zhuǎn),需要的可以參考下
    2024-01-01
  • 在C#中調(diào)用Windows防火墻界面的常見(jiàn)方式

    在C#中調(diào)用Windows防火墻界面的常見(jiàn)方式

    在 C# 中調(diào)用 Windows 防火墻界面(基礎(chǔ)設(shè)置或高級(jí)安全設(shè)置),可以使用 進(jìn)程啟動(dòng)(Process.Start) 或 Win32 API 來(lái)實(shí)現(xiàn),所以本文給大家介紹了C#調(diào)用Windows防火墻界面的常見(jiàn)方式,需要的朋友可以參考下
    2025-11-11

最新評(píng)論

弥渡县| 稻城县| 新民市| 封开县| 常宁市| 泸水县| 武鸣县| 昌平区| 亳州市| 仙居县| 濮阳市| 防城港市| 安吉县| 保靖县| 郑州市| 云浮市| 乐安县| 连平县| 梓潼县| 隆昌县| 汉川市| 巍山| 定州市| 邯郸市| 轮台县| 象山县| 嘉黎县| 襄汾县| 历史| 天水市| 十堰市| 康乐县| 郓城县| 攀枝花市| 宜丰县| 潮安县| 禄丰县| 玉门市| 虞城县| 神农架林区| 班玛县|