├── .gitignore ├── .nuget ├── NuGet.Config ├── NuGet.exe └── NuGet.targets ├── Postal.sln ├── build.bat ├── build ├── Postal.Mvc3.nuspec ├── Postal.Mvc4.nuspec ├── Postal.Mvc5.nuspec ├── Web.config.transform ├── _ViewStart.cshtml └── build.xml ├── license.txt ├── readme.txt ├── src ├── Postal.Mvc3 │ ├── Postal.Mvc3.csproj │ └── packages.config ├── Postal.Mvc4 │ ├── Postal.Mvc4.csproj │ └── packages.config ├── Postal.Mvc5 │ ├── Postal.Mvc5.csproj │ └── packages.config ├── Postal.Tests │ ├── EmailParserTests.cs │ ├── EmailServiceTests.cs │ ├── EmailTests.cs │ ├── EmailViewRendererTests.cs │ ├── EmailViewResultTests.cs │ ├── FileSystemRazorViewTests.cs │ ├── ImageEmbedderTests.cs │ ├── ParserUtilsTests.cs │ ├── Postal.Tests.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ └── packages.config ├── Postal.snk ├── Postal │ ├── AssemblyInfo.cs │ ├── Email.cs │ ├── EmailParser.cs │ ├── EmailService.cs │ ├── EmailViewRenderer.cs │ ├── EmailViewResult.cs │ ├── FileSystemRazorView.cs │ ├── FileSystemRazorViewEngine.cs │ ├── HtmlExtensions.cs │ ├── IEmailParser.cs │ ├── IEmailService.cs │ ├── IEmailViewRenderer.cs │ ├── ImageEmbedder.cs │ ├── ParserUtils.cs │ ├── Postal.csproj │ ├── ResourceRazorView.cs │ ├── ResourceRazorViewEngine.cs │ ├── app.config │ └── packages.config └── Samples │ ├── ConsoleSample │ ├── ConsoleSample.csproj │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Views │ │ └── Test.cshtml │ ├── app.config │ └── packages.config │ ├── ResourceSample │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ResourceSample.csproj │ ├── Resources │ │ └── Views │ │ │ └── Test.cshtml │ ├── app.config │ └── packages.config │ └── WebSample │ ├── App_Start │ ├── FilterConfig.cs │ └── RouteConfig.cs │ ├── Content │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ └── postal.png │ ├── Controllers │ ├── EmailController.cs │ ├── HomeController.cs │ └── PreviewController.cs │ ├── Global.asax │ ├── Global.asax.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Scripts │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── jquery-1.9.1.intellisense.js │ ├── jquery-2.1.0.intellisense.js │ ├── jquery-2.1.0.js │ ├── jquery-2.1.0.min.js │ └── jquery-2.1.0.min.map │ ├── Views │ ├── Emails │ │ ├── MultiPart.Html.cshtml │ │ ├── MultiPart.Text.cshtml │ │ ├── MultiPart.cshtml │ │ ├── Simple.cshtml │ │ ├── SimpleHtml.cshtml │ │ ├── Typed.cshtml │ │ └── _ViewStart.cshtml │ ├── Home │ │ ├── Index.cshtml │ │ ├── Samples.cshtml │ │ └── Sent.cshtml │ ├── Shared │ │ └── _Layout.cshtml │ ├── Web.config │ └── _ViewStart.cshtml │ ├── Web.Debug.config │ ├── Web.Release.config │ ├── Web.config │ ├── WebSample.csproj │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff │ └── packages.config └── tools ├── smtp4dev-license.txt └── smtp4dev.exe /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | Bin 3 | obj 4 | Obj 5 | packages 6 | _ReSharper.* 7 | *.csproj.user 8 | *.resharper.user 9 | *.ReSharper.user 10 | *.suo 11 | *.sln.docstates 12 | *.cache 13 | *.Cache 14 | *.user 15 | build/*.nupkg -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewdavey/postal/9b0e426aa79af93ce67b8b1a6f08d561f0f07248/.nuget/NuGet.exe -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 6 | $(NuGetToolsPath)\nuget.exe 7 | $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) 8 | $([System.IO.Path]::Combine($(SolutionDir), "packages")) 9 | $(TargetDir.Trim('\\')) 10 | 11 | 12 | "" 13 | 14 | 15 | false 16 | 17 | 18 | false 19 | 20 | 21 | "$(NuGetExePath)" install "$(PackagesConfig)" -source $(PackageSources) -o "$(PackagesDir)" 22 | "$(NuGetExePath)" pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols 23 | 24 | 25 | 26 | RestorePackages; 27 | $(BuildDependsOn); 28 | 29 | 30 | 31 | 32 | $(BuildDependsOn); 33 | BuildPackage; 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | 51 | 52 | -------------------------------------------------------------------------------- /Postal.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Postal", "src\Postal\Postal.csproj", "{779257EF-F8DD-495B-8B54-1F2E6AC5FA9B}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Postal.Tests", "src\Postal.Tests\Postal.Tests.csproj", "{8F7B0DA6-277A-40A8-9C2A-6100112D35F6}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{DDA63F90-0BB7-4438-ACE7-03E71A69950B}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleSample", "src\Samples\ConsoleSample\ConsoleSample.csproj", "{2C24E942-E0E1-4116-838B-ACD25653D5B2}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceSample", "src\Samples\ResourceSample\ResourceSample.csproj", "{AB26BB59-F976-4361-A041-21C7EF1F2AE5}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{F70EF96E-BECF-4500-BF12-6D982CE1C39B}" 15 | ProjectSection(SolutionItems) = preProject 16 | build\_ViewStart.cshtml = build\_ViewStart.cshtml 17 | build\build.xml = build\build.xml 18 | build\Postal.Mvc3.nuspec = build\Postal.Mvc3.nuspec 19 | build\Postal.Mvc4.nuspec = build\Postal.Mvc4.nuspec 20 | build\Postal.Mvc5.nuspec = build\Postal.Mvc5.nuspec 21 | build\Web.config.transform = build\Web.config.transform 22 | EndProjectSection 23 | EndProject 24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSample", "src\Samples\WebSample\WebSample.csproj", "{20E5601B-354D-4393-951C-C3BCC970EAAC}" 25 | EndProject 26 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Postal.Mvc3", "src\Postal.Mvc3\Postal.Mvc3.csproj", "{FC488BE2-1C99-468A-85F3-12FBB539CFE9}" 27 | EndProject 28 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Postal.Mvc4", "src\Postal.Mvc4\Postal.Mvc4.csproj", "{4EF69363-FA1E-4850-8028-44A419ADA249}" 29 | EndProject 30 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Postal.Mvc5", "src\Postal.Mvc5\Postal.Mvc5.csproj", "{88F76823-BD53-4195-8FFF-43F0FFCE7A9C}" 31 | EndProject 32 | Global 33 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 34 | Debug|Any CPU = Debug|Any CPU 35 | Release|Any CPU = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 38 | {779257EF-F8DD-495B-8B54-1F2E6AC5FA9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {779257EF-F8DD-495B-8B54-1F2E6AC5FA9B}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {779257EF-F8DD-495B-8B54-1F2E6AC5FA9B}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {779257EF-F8DD-495B-8B54-1F2E6AC5FA9B}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {8F7B0DA6-277A-40A8-9C2A-6100112D35F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {8F7B0DA6-277A-40A8-9C2A-6100112D35F6}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {8F7B0DA6-277A-40A8-9C2A-6100112D35F6}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {8F7B0DA6-277A-40A8-9C2A-6100112D35F6}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {2C24E942-E0E1-4116-838B-ACD25653D5B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {2C24E942-E0E1-4116-838B-ACD25653D5B2}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {2C24E942-E0E1-4116-838B-ACD25653D5B2}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {2C24E942-E0E1-4116-838B-ACD25653D5B2}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {AB26BB59-F976-4361-A041-21C7EF1F2AE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {AB26BB59-F976-4361-A041-21C7EF1F2AE5}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {AB26BB59-F976-4361-A041-21C7EF1F2AE5}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {AB26BB59-F976-4361-A041-21C7EF1F2AE5}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {20E5601B-354D-4393-951C-C3BCC970EAAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {20E5601B-354D-4393-951C-C3BCC970EAAC}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {20E5601B-354D-4393-951C-C3BCC970EAAC}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {20E5601B-354D-4393-951C-C3BCC970EAAC}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {FC488BE2-1C99-468A-85F3-12FBB539CFE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {FC488BE2-1C99-468A-85F3-12FBB539CFE9}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {FC488BE2-1C99-468A-85F3-12FBB539CFE9}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {FC488BE2-1C99-468A-85F3-12FBB539CFE9}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {4EF69363-FA1E-4850-8028-44A419ADA249}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {4EF69363-FA1E-4850-8028-44A419ADA249}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {4EF69363-FA1E-4850-8028-44A419ADA249}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {4EF69363-FA1E-4850-8028-44A419ADA249}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {88F76823-BD53-4195-8FFF-43F0FFCE7A9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {88F76823-BD53-4195-8FFF-43F0FFCE7A9C}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {88F76823-BD53-4195-8FFF-43F0FFCE7A9C}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {88F76823-BD53-4195-8FFF-43F0FFCE7A9C}.Release|Any CPU.Build.0 = Release|Any CPU 70 | EndGlobalSection 71 | GlobalSection(SolutionProperties) = preSolution 72 | HideSolutionNode = FALSE 73 | EndGlobalSection 74 | GlobalSection(NestedProjects) = preSolution 75 | {2C24E942-E0E1-4116-838B-ACD25653D5B2} = {DDA63F90-0BB7-4438-ACE7-03E71A69950B} 76 | {AB26BB59-F976-4361-A041-21C7EF1F2AE5} = {DDA63F90-0BB7-4438-ACE7-03E71A69950B} 77 | {20E5601B-354D-4393-951C-C3BCC970EAAC} = {DDA63F90-0BB7-4438-ACE7-03E71A69950B} 78 | EndGlobalSection 79 | EndGlobal 80 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | cd %~dp0 2 | 3 | %windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe build\build.xml -------------------------------------------------------------------------------- /build/Postal.Mvc3.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Postal.Mvc3 5 | 1.2.0 6 | Postal for MVC3 7 | Andrew Davey 8 | Andrew Davey 9 | http://www.opensource.org/licenses/mit-license.php 10 | https://github.com/andrewdavey/postal 11 | http://aboutcode.net/postal/favicon.ico 12 | false 13 | Generate emails using ASP.NET MVC views 14 | Copyright Andrew Davey 2014 15 | Email 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /build/Postal.Mvc4.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Postal.Mvc4 5 | 1.2.0 6 | Postal for MVC4 7 | Andrew Davey 8 | Andrew Davey 9 | http://www.opensource.org/licenses/mit-license.php 10 | https://github.com/andrewdavey/postal 11 | http://aboutcode.net/postal/favicon.ico 12 | false 13 | Generate emails using ASP.NET MVC views 14 | Copyright Andrew Davey 2014 15 | Email 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /build/Postal.Mvc5.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Postal.Mvc5 5 | 1.2.0 6 | Postal for MVC5 7 | Andrew Davey 8 | Andrew Davey 9 | http://www.opensource.org/licenses/mit-license.php 10 | https://github.com/andrewdavey/postal 11 | http://aboutcode.net/postal/favicon.ico 12 | false 13 | Generate emails using ASP.NET MVC views 14 | Copyright Andrew Davey 2014 15 | Email 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /build/Web.config.transform: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /build/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ Layout = null; /* Overrides the Layout set for regular page views. */ } -------------------------------------------------------------------------------- /build/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Andrew Davey (andrew@equin.co.uk) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | Postal - A handy email sending library for ASP.NET MVC. 2 | 3 | by Andrew Davey ( http://aboutcode.net , http://twitter.com/andrewdavey ) 4 | 5 | Postal uses the MVC view engine infrastructure to render emails. 6 | 7 | Read the introduction: http://aboutcode.net/postal 8 | The wiki has documentation: https://github.com/andrewdavey/postal/wiki 9 | See the MvcSample project for a basic overview of usage. 10 | -------------------------------------------------------------------------------- /src/Postal.Mvc3/Postal.Mvc3.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FC488BE2-1C99-468A-85F3-12FBB539CFE9} 8 | Library 9 | Properties 10 | Postal 11 | Postal 12 | v4.0 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | bin\Release\Postal.xml 34 | 35 | 36 | true 37 | 38 | 39 | ..\Postal.snk 40 | 41 | 42 | 43 | True 44 | ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 45 | 46 | 47 | ..\..\packages\RazorEngine.2.1\lib\.NetFramework 4.0\RazorEngine.dll 48 | 49 | 50 | 51 | 52 | 53 | True 54 | ..\packages\Microsoft.AspNet.WebPages.1.0.20105.408\lib\net40\System.Web.Helpers.dll 55 | 56 | 57 | True 58 | ..\packages\Microsoft.AspNet.Mvc.3.0.20105.1\lib\net40\System.Web.Mvc.dll 59 | 60 | 61 | True 62 | ..\..\packages\RazorEngine.2.1\lib\.NetFramework 4.0\System.Web.Razor.dll 63 | 64 | 65 | True 66 | ..\packages\Microsoft.AspNet.WebPages.1.0.20105.408\lib\net40\System.Web.WebPages.dll 67 | 68 | 69 | True 70 | ..\packages\Microsoft.AspNet.WebPages.1.0.20105.408\lib\net40\System.Web.WebPages.Deployment.dll 71 | 72 | 73 | True 74 | ..\packages\Microsoft.AspNet.WebPages.1.0.20105.408\lib\net40\System.Web.WebPages.Razor.dll 75 | 76 | 77 | 78 | 79 | 80 | AssemblyInfo.cs 81 | 82 | 83 | Email.cs 84 | 85 | 86 | EmailParser.cs 87 | 88 | 89 | EmailService.cs 90 | 91 | 92 | EmailViewRenderer.cs 93 | 94 | 95 | EmailViewResult.cs 96 | 97 | 98 | FileSystemRazorView.cs 99 | 100 | 101 | FileSystemRazorViewEngine.cs 102 | 103 | 104 | HtmlExtensions.cs 105 | 106 | 107 | IEmailParser.cs 108 | 109 | 110 | IEmailService.cs 111 | 112 | 113 | IEmailViewRenderer.cs 114 | 115 | 116 | ImageEmbedder.cs 117 | 118 | 119 | ParserUtils.cs 120 | 121 | 122 | ResourceRazorView.cs 123 | 124 | 125 | ResourceRazorViewEngine.cs 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 139 | 140 | 141 | 142 | 149 | -------------------------------------------------------------------------------- /src/Postal.Mvc3/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Postal.Mvc4/Postal.Mvc4.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {4EF69363-FA1E-4850-8028-44A419ADA249} 8 | Library 9 | Properties 10 | Postal 11 | Postal 12 | v4.0 13 | 512 14 | ..\..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | bin\Release\Postal.xml 34 | 35 | 36 | true 37 | 38 | 39 | ..\Postal.snk 40 | 41 | 42 | 43 | True 44 | ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 45 | 46 | 47 | False 48 | ..\..\packages\RazorEngine.3.3.0\lib\net40\RazorEngine.dll 49 | 50 | 51 | 52 | 53 | 54 | 55 | True 56 | ..\..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.Helpers.dll 57 | 58 | 59 | True 60 | ..\..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll 61 | 62 | 63 | True 64 | ..\..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll 65 | 66 | 67 | True 68 | ..\..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Deployment.dll 69 | 70 | 71 | True 72 | ..\..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.Razor.dll 73 | 74 | 75 | 76 | 77 | AssemblyInfo.cs 78 | 79 | 80 | Email.cs 81 | 82 | 83 | EmailParser.cs 84 | 85 | 86 | EmailService.cs 87 | 88 | 89 | EmailViewRenderer.cs 90 | 91 | 92 | EmailViewResult.cs 93 | 94 | 95 | FileSystemRazorView.cs 96 | 97 | 98 | FileSystemRazorViewEngine.cs 99 | 100 | 101 | HtmlExtensions.cs 102 | 103 | 104 | IEmailParser.cs 105 | 106 | 107 | IEmailService.cs 108 | 109 | 110 | IEmailViewRenderer.cs 111 | 112 | 113 | ImageEmbedder.cs 114 | 115 | 116 | ParserUtils.cs 117 | 118 | 119 | ResourceRazorView.cs 120 | 121 | 122 | ResourceRazorViewEngine.cs 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 136 | 137 | 138 | 139 | 146 | -------------------------------------------------------------------------------- /src/Postal.Mvc4/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Postal.Mvc5/Postal.Mvc5.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {88F76823-BD53-4195-8FFF-43F0FFCE7A9C} 8 | Library 9 | Properties 10 | Postal 11 | Postal 12 | v4.5 13 | 512 14 | ..\..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | bin\Release\Postal.xml 34 | 35 | 36 | true 37 | 38 | 39 | ..\Postal.snk 40 | 41 | 42 | 43 | True 44 | ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 45 | 46 | 47 | ..\..\packages\RazorEngine.3.4.1\lib\net45\RazorEngine.dll 48 | 49 | 50 | 51 | 52 | 53 | 54 | False 55 | ..\..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.Helpers.dll 56 | 57 | 58 | False 59 | ..\..\packages\Microsoft.AspNet.Mvc.5.1.2\lib\net45\System.Web.Mvc.dll 60 | 61 | 62 | False 63 | ..\..\packages\Microsoft.AspNet.Razor.3.1.2\lib\net45\System.Web.Razor.dll 64 | 65 | 66 | False 67 | ..\..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.dll 68 | 69 | 70 | False 71 | ..\..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.Deployment.dll 72 | 73 | 74 | False 75 | ..\..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.Razor.dll 76 | 77 | 78 | 79 | 80 | AssemblyInfo.cs 81 | 82 | 83 | Email.cs 84 | 85 | 86 | EmailParser.cs 87 | 88 | 89 | EmailService.cs 90 | 91 | 92 | EmailViewRenderer.cs 93 | 94 | 95 | EmailViewResult.cs 96 | 97 | 98 | FileSystemRazorView.cs 99 | 100 | 101 | FileSystemRazorViewEngine.cs 102 | 103 | 104 | HtmlExtensions.cs 105 | 106 | 107 | IEmailParser.cs 108 | 109 | 110 | IEmailService.cs 111 | 112 | 113 | IEmailViewRenderer.cs 114 | 115 | 116 | ImageEmbedder.cs 117 | 118 | 119 | ParserUtils.cs 120 | 121 | 122 | ResourceRazorView.cs 123 | 124 | 125 | ResourceRazorViewEngine.cs 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 139 | 140 | 141 | 142 | 149 | -------------------------------------------------------------------------------- /src/Postal.Mvc5/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Postal.Tests/EmailServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Mail; 2 | using Moq; 3 | using Should; 4 | using Xunit; 5 | using System.IO; 6 | using System; 7 | 8 | namespace Postal 9 | { 10 | public class EmailServiceTests 11 | { 12 | [Fact] 13 | public void CreateMessage_returns_MailMessage_created_by_parser() 14 | { 15 | var renderer = new Mock(); 16 | var parser = new Mock(); 17 | var service = new EmailService(renderer.Object, parser.Object, () => null); 18 | var email = new Email("Test"); 19 | var expectedMailMessage = new MailMessage(); 20 | parser.Setup(p => p.Parse(It.IsAny(), email)).Returns(expectedMailMessage); 21 | 22 | var actualMailMessage = service.CreateMailMessage(email); 23 | 24 | actualMailMessage.ShouldBeSameAs(expectedMailMessage); 25 | } 26 | 27 | [Fact] 28 | public void SendAync_returns_a_Task_and_sends_email() 29 | { 30 | var dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); 31 | Directory.CreateDirectory(dir); 32 | try 33 | { 34 | using (var smtp = new SmtpClient()) 35 | { 36 | smtp.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory; 37 | smtp.PickupDirectoryLocation = dir; 38 | smtp.Host = "localhost"; // HACK: required by SmtpClient, but not actually used! 39 | 40 | var renderer = new Mock(); 41 | var parser = new Mock(); 42 | var service = new EmailService(renderer.Object, parser.Object, () => smtp); 43 | parser.Setup(p => p.Parse(It.IsAny(), It.IsAny())) 44 | .Returns(new MailMessage("test@test.com", "test@test.com")); 45 | 46 | var sending = service.SendAsync(new Email("Test")); 47 | sending.Wait(); 48 | 49 | Directory.GetFiles(dir).Length.ShouldEqual(1); 50 | } 51 | } 52 | finally 53 | { 54 | Directory.Delete(dir, true); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Postal.Tests/EmailTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Moq; 3 | using Should; 4 | using Xunit; 5 | using System.Net.Mail; 6 | using System.IO; 7 | 8 | namespace Postal 9 | { 10 | public class EmailTests 11 | { 12 | [Fact] 13 | public void ViewName_is_set_by_constructor() 14 | { 15 | var email = new Email("Test"); 16 | email.ViewName.ShouldEqual("Test"); 17 | } 18 | 19 | [Fact] 20 | public void Cannot_create_Email_with_null_view_name() 21 | { 22 | Assert.Throws(delegate 23 | { 24 | new Email(null); 25 | }); 26 | } 27 | 28 | [Fact] 29 | public void Cannot_create_Email_with_empty_view_name() 30 | { 31 | Assert.Throws(delegate 32 | { 33 | new Email(""); 34 | }); 35 | } 36 | 37 | [Fact] 38 | public void Dynamic_property_setting_assigns_ViewData_value() 39 | { 40 | dynamic email = new Email("Test"); 41 | email.Subject = "SubjectValue"; 42 | 43 | var email2 = (Email)email; 44 | email2.ViewData["Subject"].ShouldEqual("SubjectValue"); 45 | } 46 | 47 | [Fact] 48 | public void Getting_dynamic_property_reads_from_ViewData() 49 | { 50 | var email = new Email("Test"); 51 | email.ViewData["Subject"] = "SubjectValue"; 52 | 53 | dynamic email2 = email; 54 | Assert.Equal("SubjectValue", email2.Subject); 55 | } 56 | 57 | [Fact] 58 | public void Send_creates_EmailService_and_calls_Send() 59 | { 60 | var emailService = new Mock(); 61 | Email.CreateEmailService = () => emailService.Object; 62 | var email = new Email("Test"); 63 | 64 | email.Send(); 65 | 66 | emailService.Verify(s => s.Send(email)); 67 | } 68 | 69 | [Fact] 70 | public void Derived_Email_sets_ViewData_Model() 71 | { 72 | var email = new TestEmail(); 73 | email.ViewData.Model.ShouldBeSameAs(email); 74 | } 75 | 76 | [Fact] 77 | public void Derived_Email_sets_ViewName_from_class_name() 78 | { 79 | var email = new TestEmail(); 80 | email.ViewName.ShouldEqual("Test"); 81 | } 82 | 83 | class TestEmail : Email 84 | { 85 | } 86 | 87 | [Fact] 88 | public void Derived_Email_can_manually_set_ViewName() 89 | { 90 | var email = new NonDefaultViewNameEmail(); 91 | email.ViewName.ShouldEqual("Test"); 92 | } 93 | 94 | class NonDefaultViewNameEmail : Email 95 | { 96 | public NonDefaultViewNameEmail() : base("Test") 97 | { 98 | 99 | } 100 | } 101 | 102 | [Fact] 103 | public void Attach_adds_attachment() 104 | { 105 | dynamic email = new Email("Test"); 106 | var attachment = new Attachment(new MemoryStream(), "name"); 107 | email.Attach(attachment); 108 | ((Email)email).Attachments.ShouldContain(attachment); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Postal.Tests/EmailViewRendererTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Web.Mvc; 4 | using Moq; 5 | using Should; 6 | using Xunit; 7 | 8 | namespace Postal 9 | { 10 | public class EmailViewRendererTests 11 | { 12 | [Fact] 13 | public void Render_returns_email_string_created_by_view() 14 | { 15 | var viewEngines = new Mock(); 16 | var view = new FakeView(); 17 | viewEngines.Setup(e => e.FindView(It.IsAny(), "Test", null)) 18 | .Returns(new ViewEngineResult(view, Mock.Of())); 19 | var renderer = new EmailViewRenderer(viewEngines.Object); 20 | 21 | var actualEmailString = renderer.Render(new Email("Test")); 22 | 23 | actualEmailString.ShouldEqual("Fake"); 24 | } 25 | 26 | class FakeView : IView 27 | { 28 | public void Render(ViewContext viewContext, TextWriter writer) 29 | { 30 | writer.Write("Fake"); 31 | } 32 | } 33 | 34 | [Fact] 35 | public void Render_throws_exception_when_email_view_not_found() 36 | { 37 | var viewEngines = new Mock(); 38 | viewEngines.Setup(e => e.FindView(It.IsAny(), "Test", It.IsAny())) 39 | .Returns(new ViewEngineResult(new[] { "Test" })); 40 | var renderer = new EmailViewRenderer(viewEngines.Object); 41 | 42 | Assert.Throws(delegate 43 | { 44 | renderer.Render(new Email("Test")); 45 | }); 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Postal.Tests/EmailViewResultTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net.Mail; 4 | using Moq; 5 | using Xunit; 6 | 7 | namespace Postal 8 | { 9 | public class EmailViewResultTests 10 | { 11 | [Fact] 12 | public void ExecuteResult_should_write() 13 | { 14 | var result = Create(SimpleTextOutput); 15 | var output = GetOutput(result); 16 | Assert.NotEmpty(output); 17 | } 18 | 19 | [Fact] 20 | public void ExecuteResult_returns_text_content_type() 21 | { 22 | var result = Create(SimpleTextOutput); 23 | var contentType = result.ExecuteResult(TextWriter.Null); 24 | Assert.Equal("text/plain", contentType); 25 | } 26 | 27 | [Fact] 28 | public void ExecuteResult_should_write_correctly() 29 | { 30 | var result = Create(SimpleTextOutput); 31 | var output = GetOutput(result); 32 | Assert.Equal(SimpleTextOutput, output); 33 | } 34 | 35 | [Fact] 36 | public void ExecuteResult_on_html_writers_header_in_comment() 37 | { 38 | var result = Create(SimpleHtmlOutput); 39 | var output = GetOutput(result); 40 | Assert.Contains("", output); 46 | } 47 | 48 | [Fact] 49 | public void ExecuteResult_on_html_returns_html_content_type() 50 | { 51 | var result = Create(SimpleHtmlOutput); 52 | var contentType = result.ExecuteResult(TextWriter.Null); 53 | Assert.Equal("text/html", contentType); 54 | } 55 | 56 | [Fact] 57 | public void ExecuteResult_with_text_format_on_html_fails() 58 | { 59 | var result = Create(SimpleHtmlOutput); 60 | Assert.Throws(() => GetOutput(result, "text")); 61 | } 62 | 63 | [Fact] 64 | public void ExecuteResult_with_html_format_on_text_fails() 65 | { 66 | var result = Create(SimpleTextOutput); 67 | Assert.Throws(() => GetOutput(result, "html")); 68 | } 69 | 70 | [Fact] 71 | public void ExecuteResult_on_multipart_without_format_renders_default() 72 | { 73 | var result = Create(MultiPartOutput, MultiPartTextOutput, MultiPartHtmlOutput); 74 | var output = GetOutput(result); 75 | Assert.Equal(MultiPartOutput, output); 76 | } 77 | 78 | [Fact] 79 | public void ExecuteResult_on_multipart_without_format_returns_text_content_type() 80 | { 81 | var result = Create(MultiPartOutput, MultiPartTextOutput, MultiPartHtmlOutput); 82 | var contentType = result.ExecuteResult(TextWriter.Null); 83 | Assert.Equal("text/plain", contentType); 84 | } 85 | 86 | [Fact] 87 | public void ExecuteResult_on_multipart_with_text_format_renders_text() 88 | { 89 | var result = Create(MultiPartOutput, MultiPartTextOutput, MultiPartHtmlOutput); 90 | var output = GetOutput(result, format: "text"); 91 | Assert.Equal(@"This is a plain text message 92 | 93 | Generated by Postal on 2014/06/20", output); 94 | } 95 | 96 | [Fact] 97 | public void ExecuteResult_on_multipart_with_html_format_renders_html() 98 | { 99 | var result = Create(MultiPartOutput, MultiPartTextOutput, MultiPartHtmlOutput); 100 | var output = GetOutput(result, format: "html"); 101 | Assert.Equal(@" 102 | 103 |

This is an HTML message

104 |

Generated by Postal on @ViewBag.Date

105 | 106 | ", output); 107 | } 108 | 109 | [Fact] 110 | public void ExecuteResult_on_multipart_with_html_format_returns_html_content_type() 111 | { 112 | var result = Create(MultiPartOutput, MultiPartTextOutput, MultiPartHtmlOutput); 113 | var contentType = result.ExecuteResult(TextWriter.Null, "html"); 114 | Assert.Equal("text/html", contentType); 115 | } 116 | 117 | [Fact] 118 | public void ExecuteResult_on_multipart_with_text_format_returns_text_content_type() 119 | { 120 | var result = Create(MultiPartOutput, MultiPartTextOutput, MultiPartHtmlOutput); 121 | var contentType = result.ExecuteResult(TextWriter.Null, "text"); 122 | Assert.Equal("text/plain", contentType); 123 | } 124 | 125 | [Fact] 126 | public void ReplaceLinkedImagesWithEmbeddedImages_replaces_cid_reference() 127 | { 128 | var embedder = new ImageEmbedder(); 129 | var resource = embedder.ReferenceImage("postal.png"); 130 | 131 | string body = ""; 132 | var view = AlternateView.CreateAlternateViewFromString(body); 133 | embedder.AddImagesToView(view); 134 | 135 | string replaced = EmailViewResult.ReplaceLinkedImagesWithEmbeddedImages(view, body); 136 | Assert.DoesNotContain("cid:", replaced); 137 | } 138 | 139 | [Fact] 140 | public void ReplaceLinkedImagesWithEmbeddedImages_replaces_cid_reference_with_correct_mime() 141 | { 142 | var embedder = new ImageEmbedder(); 143 | var resource = embedder.ReferenceImage("postal.png"); 144 | 145 | string body = ""; 146 | var view = AlternateView.CreateAlternateViewFromString(body); 147 | embedder.AddImagesToView(view); 148 | 149 | string replaced = EmailViewResult.ReplaceLinkedImagesWithEmbeddedImages(view, body); 150 | Assert.Contains("data:image/png;base64,", replaced); 151 | } 152 | 153 | EmailViewResult Create(string template, string textTemplate = null, string htmlTemplate = null) 154 | { 155 | var email = new Email("~/Views/Emails/Test.cshtml"); 156 | var renderer = new Mock(); 157 | 158 | renderer.Setup(r => r.Render(email, null)).Returns(template); 159 | if(!string.IsNullOrEmpty(textTemplate)) 160 | renderer.Setup(r => r.Render(email, "~/Views/Emails/Test.Text.cshtml")).Returns(textTemplate); 161 | if (!string.IsNullOrEmpty(htmlTemplate)) 162 | renderer.Setup(r => r.Render(email, "~/Views/Emails/Test.Html.cshtml")).Returns(htmlTemplate); 163 | 164 | return new EmailViewResult(email, renderer.Object, null); 165 | } 166 | 167 | string GetOutput(EmailViewResult result, string format = null) 168 | { 169 | using (var writer = new StringWriter()) 170 | { 171 | result.ExecuteResult(writer, format); 172 | 173 | return writer.GetStringBuilder().ToString(); 174 | } 175 | } 176 | 177 | const string SimpleTextOutput = @"To: test@example.org 178 | From: test@example.org 179 | Subject: Simple email example 180 | 181 | Hello, world! 182 | 183 | The date is: 2014/06/20"; 184 | 185 | const string SimpleHtmlOutput = @"To: test@example.org 186 | From: test@example.org 187 | Subject: Simple email example 188 | 189 | 190 | 191 |

The date is 2014/06/20

192 | 193 | "; 194 | 195 | const string MultiPartOutput = @"To: test@example.org 196 | From: test@example.org 197 | Subject: Multi-part email example 198 | Views: Html,Text"; 199 | 200 | const string MultiPartTextOutput = @"Content-Type: text/plain; charset=utf8 201 | 202 | This is a plain text message 203 | 204 | Generated by Postal on 2014/06/20"; 205 | 206 | const string MultiPartHtmlOutput = @"Content-Type: text/html; charset=utf8 207 | 208 | 209 | 210 |

This is an HTML message

211 |

Generated by Postal on @ViewBag.Date

212 | 213 | "; 214 | 215 | 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/Postal.Tests/FileSystemRazorViewTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using System.IO; 3 | using Moq; 4 | using System.Web.Mvc; 5 | 6 | namespace Postal 7 | { 8 | public class FileSystemRazorViewTests 9 | { 10 | [Fact] 11 | public void GivenViewFoundOnce_WhenFileChanged_ThenViewIsReloaded() 12 | { 13 | var filename = Path.Combine(Path.GetTempPath(), "test.cshtml"); 14 | File.WriteAllText(filename, "test-1"); 15 | try 16 | { 17 | var view1 = new FileSystemRazorView(filename); 18 | var context = new Mock(); 19 | context.Setup(c => c.ViewData).Returns(new ViewDataDictionary(new object())); 20 | using (var writer = new StringWriter()) 21 | { 22 | view1.Render(context.Object, writer); 23 | var first = writer.GetStringBuilder().ToString(); 24 | Assert.Equal("test-1", first); 25 | } 26 | 27 | // Overwrite the file with new content. 28 | File.WriteAllText(filename, "test-2"); 29 | // Views are single-use, so create a new one. 30 | var view2 = new FileSystemRazorView(filename); 31 | using (var writer = new StringWriter()) 32 | { 33 | view2.Render(context.Object, writer); 34 | var second = writer.GetStringBuilder().ToString(); 35 | Assert.Equal("test-2", second); 36 | } 37 | } 38 | finally 39 | { 40 | File.Delete(filename); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Postal.Tests/ImageEmbedderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | using Should; 4 | using System.Net.Mail; 5 | using System.Net.Mime; 6 | using System.IO; 7 | 8 | namespace Postal 9 | { 10 | public class ImageEmbedderTests 11 | { 12 | LinkedResource StubLinkedResource(string s) 13 | { 14 | return new LinkedResource(new MemoryStream()); 15 | } 16 | 17 | [Fact] 18 | public void ReferenceImage_returns_LinkedResource() 19 | { 20 | var embedder = new ImageEmbedder(StubLinkedResource); 21 | var resource = embedder.ReferenceImage("test.png"); 22 | resource.ShouldNotBeNull(); 23 | } 24 | 25 | [Fact] 26 | public void Repeated_images_use_the_same_LinkedResource() 27 | { 28 | var embedder = new ImageEmbedder(StubLinkedResource); 29 | var r1 = embedder.ReferenceImage("test-a.png"); 30 | var r2 = embedder.ReferenceImage("test-a.png"); 31 | Assert.Same(r1, r2); 32 | } 33 | 34 | [Fact] 35 | public void Determine_content_type_from_PNG_file_extension() 36 | { 37 | var embedder = new ImageEmbedder(StubLinkedResource); 38 | var resource = embedder.ReferenceImage("test.png"); 39 | resource.ContentType.ShouldEqual(new ContentType("image/png")); 40 | } 41 | 42 | [Fact] 43 | public void Determine_content_type_from_PNG_http_file_extension() 44 | { 45 | var embedder = new ImageEmbedder(StubLinkedResource); 46 | var resource = embedder.ReferenceImage("http://test.com/test.png"); 47 | resource.ContentType.ShouldEqual(new ContentType("image/png")); 48 | } 49 | 50 | [Fact] 51 | public void Determine_content_type_from_JPEG_file_extension() 52 | { 53 | var embedder = new ImageEmbedder(StubLinkedResource); 54 | var resource = embedder.ReferenceImage("test.jpeg"); 55 | resource.ContentType.ShouldEqual(new ContentType("image/jpeg")); 56 | } 57 | 58 | [Fact] 59 | public void Determine_content_type_from_JPG_file_extension() 60 | { 61 | var embedder = new ImageEmbedder(StubLinkedResource); 62 | var resource = embedder.ReferenceImage("test.jpg"); 63 | resource.ContentType.ShouldEqual(new ContentType("image/jpeg")); 64 | } 65 | 66 | [Fact] 67 | public void Determine_content_type_from_GIF_file_extension() 68 | { 69 | var embedder = new ImageEmbedder(StubLinkedResource); 70 | var resource = embedder.ReferenceImage("test.gif"); 71 | resource.ContentType.ShouldEqual(new ContentType("image/gif")); 72 | } 73 | 74 | [Fact] 75 | public void Can_read_image_from_file_system() 76 | { 77 | var embedder = new ImageEmbedder(); 78 | var filename = Path.GetTempFileName(); 79 | try 80 | { 81 | File.WriteAllBytes(filename, new byte[] { 42 }); 82 | using (var resource = embedder.ReferenceImage(filename)) 83 | { 84 | resource.ContentStream.Length.ShouldEqual(1); 85 | } 86 | } 87 | finally 88 | { 89 | File.Delete(filename); 90 | } 91 | } 92 | 93 | [Fact] 94 | public void Can_read_image_from_http_url() 95 | { 96 | var embedder = new ImageEmbedder(); 97 | using (var resource = embedder.ReferenceImage("http://upload.wikimedia.org/wikipedia/commons/6/63/Wikipedia-logo.png")) 98 | { 99 | resource.ContentStream.Length.ShouldNotEqual(0); 100 | } 101 | } 102 | 103 | [Fact] 104 | public void AddImagesToView_adds_linked_resources() 105 | { 106 | var embedder = new ImageEmbedder(s => new LinkedResource(new MemoryStream())); 107 | var cid = embedder.ReferenceImage("test.png"); 108 | using (var view = AlternateView.CreateAlternateViewFromString("", new ContentType("text/html"))) 109 | { 110 | embedder.AddImagesToView(view); 111 | 112 | view.LinkedResources.Count.ShouldEqual(1); 113 | view.LinkedResources[0].ShouldBeSameAs(cid); 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Postal.Tests/ParserUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using Should; 4 | using Xunit; 5 | 6 | namespace Postal 7 | { 8 | public class ParserUtilsTests 9 | { 10 | [Fact] 11 | public void Can_parse_header() 12 | { 13 | var header= ParseHeader("hello: world"); 14 | header.Key.ShouldEqual("hello"); 15 | header.Value.ShouldEqual("world"); 16 | } 17 | 18 | [Fact] 19 | public void White_space_is_removed() 20 | { 21 | var header = ParseHeader(" hello : world "); 22 | header.Key.ShouldEqual("hello"); 23 | header.Value.ShouldEqual("world"); 24 | } 25 | 26 | [Fact] 27 | public void Skips_initial_blank_lines() 28 | { 29 | var header = ParseHeader("\n\nfirst: test"); 30 | header.Key.ShouldEqual("first"); 31 | header.Value.ShouldEqual("test"); 32 | } 33 | 34 | [Fact] 35 | public void Can_parse_header_with_hyphen() 36 | { 37 | var header = ParseHeader("reply-to: foo@test.com"); 38 | header.Key.ShouldEqual("reply-to"); 39 | header.Value.ShouldEqual("foo@test.com"); 40 | } 41 | 42 | KeyValuePair ParseHeader(string line) 43 | { 44 | var header = default(KeyValuePair); 45 | using (var reader = new StringReader(line)) 46 | { 47 | ParserUtils.ParseHeaders(reader, (key, value) => header = new KeyValuePair(key, value)); 48 | } 49 | return header; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Postal.Tests/Postal.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8F7B0DA6-277A-40A8-9C2A-6100112D35F6} 8 | Library 9 | Properties 10 | Postal 11 | Postal.Tests 12 | v4.5 13 | 512 14 | ..\..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | True 37 | ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 38 | 39 | 40 | ..\..\packages\Moq.4.2.1402.2112\lib\net40\Moq.dll 41 | 42 | 43 | ..\..\packages\Should.1.1.20\lib\Should.dll 44 | 45 | 46 | 47 | 48 | 49 | False 50 | ..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.Helpers.dll 51 | 52 | 53 | False 54 | ..\..\packages\Microsoft.AspNet.Mvc.5.1.1\lib\net45\System.Web.Mvc.dll 55 | 56 | 57 | False 58 | ..\..\packages\Microsoft.AspNet.Razor.3.1.1\lib\net45\System.Web.Razor.dll 59 | 60 | 61 | False 62 | ..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.WebPages.dll 63 | 64 | 65 | False 66 | ..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.WebPages.Deployment.dll 67 | 68 | 69 | False 70 | ..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.WebPages.Razor.dll 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | ..\..\packages\xunit.1.9.2\lib\net20\xunit.dll 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | {779257ef-f8dd-495b-8b54-1f2e6ac5fa9b} 98 | Postal 99 | 100 | 101 | 102 | 103 | postal.png 104 | Always 105 | 106 | 107 | 108 | 109 | 116 | -------------------------------------------------------------------------------- /src/Postal.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Postal.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Postal.Tests")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("3ebf13cc-2e61-4bff-85da-37851417d413")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Postal.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Postal.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewdavey/postal/9b0e426aa79af93ce67b8b1a6f08d561f0f07248/src/Postal.snk -------------------------------------------------------------------------------- /src/Postal/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("Postal")] 10 | [assembly: AssemblyDescription("Create emails from ASP.NET MVC views")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("Andrew Davey")] 13 | [assembly: AssemblyProduct("Postal")] 14 | [assembly: AssemblyCopyright("Copyright © Andrew Davey 2014")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | [assembly: Guid("5279c130-0531-4cb5-9fea-39406a93443b")] 25 | 26 | // Version information for an assembly consists of the following four values: 27 | // 28 | // Major Version 29 | // Minor Version 30 | // Build Number 31 | // Revision 32 | // 33 | // You can specify all the values or you can default the Build and Revision Numbers 34 | // by using the '*' as shown below: 35 | // [assembly: AssemblyVersion("1.0.*")] 36 | [assembly: AssemblyVersion("1.0.0.0")] 37 | [assembly: AssemblyFileVersion("1.0.0.0")] 38 | 39 | [assembly: CLSCompliant(true)] 40 | 41 | #if TESTABLE 42 | [assembly: InternalsVisibleTo("Postal.Tests")] 43 | #endif -------------------------------------------------------------------------------- /src/Postal/Email.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Dynamic; 4 | using System.Net.Mail; 5 | using System.Threading.Tasks; 6 | using System.Web.Mvc; 7 | 8 | namespace Postal 9 | { 10 | /// 11 | /// An Email object has the name of the MVC view to render and a view data dictionary 12 | /// to store the data to render. It is best used as a dynamic object, just like the 13 | /// ViewBag property of a Controller. Any dynamic property access is mapped to the 14 | /// view data dictionary. 15 | /// 16 | public class Email : DynamicObject, IViewDataContainer 17 | { 18 | /// 19 | /// Creates a new Email, that will render the given view. 20 | /// 21 | /// The name of the view to render 22 | public Email(string viewName) 23 | { 24 | if (viewName == null) throw new ArgumentNullException("viewName"); 25 | if (string.IsNullOrWhiteSpace(viewName)) throw new ArgumentException("View name cannot be empty.", "viewName"); 26 | 27 | Attachments = new List(); 28 | ViewName = viewName; 29 | ViewData = new ViewDataDictionary(this); 30 | ImageEmbedder = new ImageEmbedder(); 31 | } 32 | 33 | /// Create an Email where the ViewName is derived from the name of the class. 34 | /// Used when defining strongly typed Email classes. 35 | protected Email() 36 | { 37 | Attachments = new List(); 38 | ViewName = DeriveViewNameFromClassName(); 39 | ViewData = new ViewDataDictionary(this); 40 | ImageEmbedder = new ImageEmbedder(); 41 | } 42 | 43 | /// 44 | /// The name of the view containing the email template. 45 | /// 46 | public string ViewName { get; set; } 47 | 48 | /// 49 | /// The view data to pass to the view. 50 | /// 51 | public ViewDataDictionary ViewData { get; set; } 52 | 53 | /// 54 | /// The attachments to send with the email. 55 | /// 56 | public List Attachments { get; set; } 57 | 58 | internal ImageEmbedder ImageEmbedder { get; private set; } 59 | 60 | /// 61 | /// Adds an attachment to the email. 62 | /// 63 | /// The attachment to add. 64 | public void Attach(Attachment attachment) 65 | { 66 | Attachments.Add(attachment); 67 | } 68 | 69 | /// 70 | /// Convenience method that sends this email via a default EmailService. 71 | /// 72 | public void Send() 73 | { 74 | CreateEmailService().Send(this); 75 | } 76 | 77 | /// 78 | /// Convenience method that sends this email asynchronously via a default EmailService. 79 | /// 80 | public Task SendAsync() 81 | { 82 | return CreateEmailService().SendAsync(this); 83 | } 84 | 85 | /// 86 | /// A function that returns an instance of . 87 | /// 88 | public static Func CreateEmailService = () => new EmailService(); 89 | 90 | // Any dynamic property access is delegated to view data dictionary. 91 | // This makes for sweet looking syntax - thank you C#4! 92 | 93 | /// 94 | /// Stores the given value into the . 95 | /// 96 | /// Provides the name of the view data property. 97 | /// The value to store. 98 | /// Always returns true. 99 | public override bool TrySetMember(SetMemberBinder binder, object value) 100 | { 101 | ViewData[binder.Name] = value; 102 | return true; 103 | } 104 | 105 | /// 106 | /// Tries to get a stored value from . 107 | /// 108 | /// Provides the name of the view data property. 109 | /// If found, this is the view data property value. 110 | /// True if the property was found, otherwise false. 111 | public override bool TryGetMember(GetMemberBinder binder, out object result) 112 | { 113 | return ViewData.TryGetValue(binder.Name, out result); 114 | } 115 | 116 | string DeriveViewNameFromClassName() 117 | { 118 | var viewName = GetType().Name; 119 | if (viewName.EndsWith("Email")) viewName = viewName.Substring(0, viewName.Length - "Email".Length); 120 | return viewName; 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /src/Postal/EmailParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Mail; 6 | using System.Net.Mime; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | 10 | namespace Postal 11 | { 12 | /// 13 | /// Converts the raw string output of a view into a . 14 | /// 15 | public class EmailParser : IEmailParser 16 | { 17 | /// 18 | /// Creates a new . 19 | /// 20 | public EmailParser(IEmailViewRenderer alternativeViewRenderer) 21 | { 22 | this.alternativeViewRenderer = alternativeViewRenderer; 23 | } 24 | 25 | readonly IEmailViewRenderer alternativeViewRenderer; 26 | 27 | /// 28 | /// Parses the email view output into a . 29 | /// 30 | /// The email view output. 31 | /// The used to generate the output. 32 | /// A containing the email headers and content. 33 | public MailMessage Parse(string emailViewOutput, Email email) 34 | { 35 | var message = new MailMessage(); 36 | InitializeMailMessage(message, emailViewOutput, email); 37 | return message; 38 | } 39 | 40 | void InitializeMailMessage(MailMessage message, string emailViewOutput, Email email) 41 | { 42 | using (var reader = new StringReader(emailViewOutput)) 43 | { 44 | ParserUtils.ParseHeaders(reader, (key, value) => ProcessHeader(key, value, message, email)); 45 | AssignCommonHeaders(message, email); 46 | if (message.AlternateViews.Count == 0) 47 | { 48 | var messageBody = reader.ReadToEnd().Trim(); 49 | if (email.ImageEmbedder.HasImages) 50 | { 51 | var view = AlternateView.CreateAlternateViewFromString(messageBody, new ContentType("text/html")); 52 | email.ImageEmbedder.AddImagesToView(view); 53 | message.AlternateViews.Add(view); 54 | message.Body = "Plain text not available."; 55 | message.IsBodyHtml = false; 56 | } 57 | else 58 | { 59 | message.Body = messageBody; 60 | if (message.Body.StartsWith("<")) message.IsBodyHtml = true; 61 | } 62 | } 63 | 64 | AddAttachments(message, email); 65 | } 66 | } 67 | 68 | void AssignCommonHeaders(MailMessage message, Email email) 69 | { 70 | if (message.To.Count == 0) 71 | { 72 | AssignCommonHeader(email, "to", to => message.To.Add(to)); 73 | AssignCommonHeader(email, "to", to => message.To.Add(to)); 74 | } 75 | if (message.From == null) 76 | { 77 | AssignCommonHeader(email, "from", from => message.From = new MailAddress(from)); 78 | AssignCommonHeader(email, "from", from => message.From = from); 79 | } 80 | if (message.CC.Count == 0) 81 | { 82 | AssignCommonHeader(email, "cc", cc => message.CC.Add(cc)); 83 | AssignCommonHeader(email, "cc", cc => message.CC.Add(cc)); 84 | } 85 | if (message.Bcc.Count == 0) 86 | { 87 | AssignCommonHeader(email, "bcc", bcc => message.Bcc.Add(bcc)); 88 | AssignCommonHeader(email, "bcc", bcc => message.Bcc.Add(bcc)); 89 | } 90 | if (message.ReplyToList.Count == 0) 91 | { 92 | AssignCommonHeader(email, "replyto", replyTo => message.ReplyToList.Add(replyTo)); 93 | AssignCommonHeader(email, "replyto", replyTo => message.ReplyToList.Add(replyTo)); 94 | } 95 | if (message.Sender == null) 96 | { 97 | AssignCommonHeader(email, "sender", sender => message.Sender = new MailAddress(sender)); 98 | AssignCommonHeader(email, "sender", sender => message.Sender = sender); 99 | } 100 | if (string.IsNullOrEmpty(message.Subject)) 101 | { 102 | AssignCommonHeader(email, "subject", subject => message.Subject = subject); 103 | } 104 | } 105 | 106 | void AssignCommonHeader(Email email, string header, Action assign) 107 | where T : class 108 | { 109 | object value; 110 | if (email.ViewData.TryGetValue(header, out value)) 111 | { 112 | var typedValue = value as T; 113 | if (typedValue != null) assign(typedValue); 114 | } 115 | } 116 | 117 | void ProcessHeader(string key, string value, MailMessage message, Email email) 118 | { 119 | if (IsAlternativeViewsHeader(key)) 120 | { 121 | foreach (var view in CreateAlternativeViews(value, email)) 122 | { 123 | message.AlternateViews.Add(view); 124 | } 125 | } 126 | else 127 | { 128 | AssignEmailHeaderToMailMessage(key, value, message); 129 | } 130 | } 131 | 132 | IEnumerable CreateAlternativeViews(string deliminatedViewNames, Email email) 133 | { 134 | var viewNames = deliminatedViewNames.Split(new[] { ',', ' ', ';' }, StringSplitOptions.RemoveEmptyEntries); 135 | return from viewName in viewNames 136 | select CreateAlternativeView(email, viewName); 137 | } 138 | 139 | AlternateView CreateAlternativeView(Email email, string alternativeViewName) 140 | { 141 | var fullViewName = GetAlternativeViewName(email, alternativeViewName); 142 | var output = alternativeViewRenderer.Render(email, fullViewName); 143 | 144 | string contentType; 145 | string body; 146 | using (var reader = new StringReader(output)) 147 | { 148 | contentType = ParseHeadersForContentType(reader); 149 | body = reader.ReadToEnd(); 150 | } 151 | 152 | if (string.IsNullOrWhiteSpace(contentType)) 153 | { 154 | if (alternativeViewName.Equals("text", StringComparison.OrdinalIgnoreCase)) 155 | { 156 | contentType = "text/plain"; 157 | } 158 | else if (alternativeViewName.Equals("html", StringComparison.OrdinalIgnoreCase)) 159 | { 160 | contentType = "text/html"; 161 | } 162 | else 163 | { 164 | throw new Exception("The 'Content-Type' header is missing from the alternative view '" + fullViewName + "'."); 165 | } 166 | } 167 | 168 | var stream = CreateStreamOfBody(body); 169 | var alternativeView = new AlternateView(stream, contentType); 170 | if (alternativeView.ContentType.CharSet == null) 171 | { 172 | // Must set a charset otherwise mail readers seem to guess the wrong one! 173 | // Strings are unicode by default in .net. 174 | alternativeView.ContentType.CharSet = Encoding.Unicode.WebName; 175 | // A different charset can be specified in the Content-Type header. 176 | // e.g. Content-Type: text/html; charset=utf-8 177 | } 178 | email.ImageEmbedder.AddImagesToView(alternativeView); 179 | return alternativeView; 180 | } 181 | 182 | static string GetAlternativeViewName(Email email, string alternativeViewName) 183 | { 184 | if (email.ViewName.StartsWith("~")) 185 | { 186 | var index = email.ViewName.LastIndexOf('.'); 187 | return email.ViewName.Insert(index + 1, alternativeViewName + "."); 188 | } 189 | else 190 | { 191 | return email.ViewName + "." + alternativeViewName; 192 | } 193 | } 194 | 195 | MemoryStream CreateStreamOfBody(string body) 196 | { 197 | var stream = new MemoryStream(); 198 | var writer = new StreamWriter(stream); 199 | writer.Write(body); 200 | writer.Flush(); 201 | stream.Position = 0; 202 | return stream; 203 | } 204 | 205 | string ParseHeadersForContentType(StringReader reader) 206 | { 207 | string contentType = null; 208 | ParserUtils.ParseHeaders(reader, (key, value) => 209 | { 210 | if (key.Equals("content-type", StringComparison.OrdinalIgnoreCase)) 211 | { 212 | contentType = value; 213 | } 214 | }); 215 | return contentType; 216 | } 217 | 218 | bool IsAlternativeViewsHeader(string headerName) 219 | { 220 | return headerName.Equals("views", StringComparison.OrdinalIgnoreCase); 221 | } 222 | 223 | void AssignEmailHeaderToMailMessage(string key, string value, MailMessage message) 224 | { 225 | switch (key) 226 | { 227 | case "to": 228 | message.To.Add(value); 229 | break; 230 | case "from": 231 | message.From = new MailAddress(value); 232 | break; 233 | case "subject": 234 | message.Subject = value; 235 | break; 236 | case "cc": 237 | message.CC.Add(value); 238 | break; 239 | case "bcc": 240 | message.Bcc.Add(value); 241 | break; 242 | case "reply-to": 243 | message.ReplyToList.Add(value); 244 | break; 245 | case "sender": 246 | message.Sender = new MailAddress(value); 247 | break; 248 | case "priority": 249 | MailPriority priority; 250 | if (Enum.TryParse(value, true, out priority)) 251 | { 252 | message.Priority = priority; 253 | } 254 | else 255 | { 256 | throw new ArgumentException(string.Format("Invalid email priority: {0}. It must be High, Medium or Low.", value)); 257 | } 258 | break; 259 | case "content-type": 260 | var charsetMatch = Regex.Match(value, @"\bcharset\s*=\s*(.*)$"); 261 | if (charsetMatch.Success) 262 | { 263 | message.BodyEncoding = Encoding.GetEncoding(charsetMatch.Groups[1].Value); 264 | } 265 | break; 266 | default: 267 | message.Headers[key] = value; 268 | break; 269 | } 270 | } 271 | 272 | void AddAttachments(MailMessage message, Email email) 273 | { 274 | foreach (var attachment in email.Attachments) 275 | { 276 | message.Attachments.Add(attachment); 277 | } 278 | } 279 | } 280 | } -------------------------------------------------------------------------------- /src/Postal/EmailService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Mail; 3 | using System.Threading.Tasks; 4 | using System.Web.Mvc; 5 | 6 | namespace Postal 7 | { 8 | /// 9 | /// Sends email using the default . 10 | /// 11 | public class EmailService : IEmailService 12 | { 13 | /// 14 | /// Creates a new cref="EmailService"/, using the default view engines. 15 | /// 16 | public EmailService() : this(ViewEngines.Engines) 17 | { 18 | } 19 | 20 | /// Creates a new , using the given view engines. 21 | /// The view engines to use when creating email views. 22 | /// A function that creates a . If null, a default creation function is used. 23 | public EmailService(ViewEngineCollection viewEngines, Func createSmtpClient = null) 24 | { 25 | emailViewRenderer = new EmailViewRenderer(viewEngines); 26 | emailParser = new EmailParser(emailViewRenderer); 27 | this.createSmtpClient = createSmtpClient ?? (() => new SmtpClient()); 28 | } 29 | 30 | /// 31 | /// Creates a new . 32 | /// 33 | public EmailService(IEmailViewRenderer emailViewRenderer, IEmailParser emailParser, Func createSmtpClient) 34 | { 35 | this.emailViewRenderer = emailViewRenderer; 36 | this.emailParser = emailParser; 37 | this.createSmtpClient = createSmtpClient; 38 | } 39 | 40 | readonly IEmailViewRenderer emailViewRenderer; 41 | readonly IEmailParser emailParser; 42 | readonly Func createSmtpClient; 43 | 44 | /// 45 | /// Sends an email using an . 46 | /// 47 | /// The email to send. 48 | public void Send(Email email) 49 | { 50 | using (var mailMessage = CreateMailMessage(email)) 51 | using (var smtp = createSmtpClient()) 52 | { 53 | smtp.Send(mailMessage); 54 | } 55 | } 56 | 57 | /// 58 | /// Send an email asynchronously, using an . 59 | /// 60 | /// The email to send. 61 | /// A that completes once the email has been sent. 62 | public Task SendAsync(Email email) 63 | { 64 | // Wrap the SmtpClient's awkward async API in the much nicer Task pattern. 65 | // However, we must be careful to dispose of the resources we create correctly. 66 | var mailMessage = CreateMailMessage(email); 67 | try 68 | { 69 | var smtp = createSmtpClient(); 70 | try 71 | { 72 | var taskCompletionSource = new TaskCompletionSource(); 73 | 74 | smtp.SendCompleted += (o, e) => 75 | { 76 | smtp.Dispose(); 77 | mailMessage.Dispose(); 78 | 79 | if (e.Error != null) 80 | { 81 | taskCompletionSource.TrySetException(e.Error); 82 | } 83 | else if (e.Cancelled) 84 | { 85 | taskCompletionSource.TrySetCanceled(); 86 | } 87 | else // Success 88 | { 89 | taskCompletionSource.TrySetResult(null); 90 | } 91 | }; 92 | 93 | smtp.SendAsync(mailMessage, null); 94 | return taskCompletionSource.Task; 95 | } 96 | catch 97 | { 98 | smtp.Dispose(); 99 | throw; 100 | } 101 | } 102 | catch 103 | { 104 | mailMessage.Dispose(); 105 | throw; 106 | } 107 | } 108 | 109 | /// 110 | /// Renders the email view and builds a . Does not send the email. 111 | /// 112 | /// The email to render. 113 | /// A containing the rendered email. 114 | public MailMessage CreateMailMessage(Email email) 115 | { 116 | var rawEmailString = emailViewRenderer.Render(email); 117 | var mailMessage = emailParser.Parse(rawEmailString, email); 118 | return mailMessage; 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /src/Postal/EmailViewRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Web; 4 | using System.Web.Mvc; 5 | using System.Web.Routing; 6 | 7 | namespace Postal 8 | { 9 | /// 10 | /// Renders view's into raw strings using the MVC ViewEngine infrastructure. 11 | /// 12 | public class EmailViewRenderer : IEmailViewRenderer 13 | { 14 | /// 15 | /// Creates a new that uses the given view engines. 16 | /// 17 | /// The view engines to use when rendering email views. 18 | public EmailViewRenderer(ViewEngineCollection viewEngines) 19 | { 20 | this.viewEngines = viewEngines; 21 | EmailViewDirectoryName = "Emails"; 22 | } 23 | 24 | readonly ViewEngineCollection viewEngines; 25 | 26 | /// 27 | /// The name of the directory in "Views" that contains the email views. 28 | /// By default, this is "Emails". 29 | /// 30 | public string EmailViewDirectoryName { get; set; } 31 | 32 | /// 33 | /// Renders an email view. 34 | /// 35 | /// The email to render. 36 | /// Optional email view name override. If null then the email's ViewName property is used instead. 37 | /// The rendered email view output. 38 | public string Render(Email email, string viewName = null) 39 | { 40 | viewName = viewName ?? email.ViewName; 41 | var controllerContext = CreateControllerContext(); 42 | var view = CreateView(viewName, controllerContext); 43 | var viewOutput = RenderView(view, email.ViewData, controllerContext, email.ImageEmbedder); 44 | return viewOutput; 45 | } 46 | 47 | ControllerContext CreateControllerContext() 48 | { 49 | // A dummy HttpContextBase that is enough to allow the view to be rendered. 50 | var httpContext = new HttpContextWrapper( 51 | new HttpContext( 52 | new HttpRequest("", UrlRoot(), ""), 53 | new HttpResponse(TextWriter.Null) 54 | ) 55 | ); 56 | var routeData = new RouteData(); 57 | routeData.Values["controller"] = EmailViewDirectoryName; 58 | var requestContext = new RequestContext(httpContext, routeData); 59 | var stubController = new StubController(); 60 | var controllerContext = new ControllerContext(requestContext, stubController); 61 | stubController.ControllerContext = controllerContext; 62 | return controllerContext; 63 | } 64 | 65 | string UrlRoot() 66 | { 67 | var httpContext = HttpContext.Current; 68 | if (httpContext == null) 69 | { 70 | return "http://localhost"; 71 | } 72 | 73 | return httpContext.Request.Url.GetLeftPart(UriPartial.Authority) + 74 | httpContext.Request.ApplicationPath; 75 | } 76 | 77 | IView CreateView(string viewName, ControllerContext controllerContext) 78 | { 79 | var result = viewEngines.FindView(controllerContext, viewName, null); 80 | if (result.View != null) 81 | return result.View; 82 | 83 | throw new Exception( 84 | "Email view not found for " + viewName + 85 | ". Locations searched:" + Environment.NewLine + 86 | string.Join(Environment.NewLine, result.SearchedLocations) 87 | ); 88 | } 89 | 90 | string RenderView(IView view, ViewDataDictionary viewData, ControllerContext controllerContext, ImageEmbedder imageEmbedder) 91 | { 92 | using (var writer = new StringWriter()) 93 | { 94 | var viewContext = new ViewContext(controllerContext, view, viewData, new TempDataDictionary(), writer); 95 | viewData[ImageEmbedder.ViewDataKey] = imageEmbedder; 96 | view.Render(viewContext, writer); 97 | viewData.Remove(ImageEmbedder.ViewDataKey); 98 | return writer.GetStringBuilder().ToString(); 99 | } 100 | } 101 | 102 | // StubController so we can create a ControllerContext. 103 | class StubController : Controller { } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Postal/EmailViewResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Net.Mail; 5 | using System.Text; 6 | using System.Web.Mvc; 7 | 8 | namespace Postal 9 | { 10 | /// 11 | /// Renders a preview of an email to display in the browser. 12 | /// 13 | public class EmailViewResult : ViewResult 14 | { 15 | const string TextContentType = "text/plain"; 16 | const string HtmlContentType = "text/html"; 17 | 18 | IEmailViewRenderer Renderer { get; set; } 19 | IEmailParser Parser { get; set; } 20 | Email Email { get; set; } 21 | 22 | /// 23 | /// Creates a new . 24 | /// 25 | public EmailViewResult(Email email, IEmailViewRenderer renderer, IEmailParser parser) 26 | { 27 | Email = email; 28 | Renderer = renderer ?? new EmailViewRenderer(ViewEngineCollection); 29 | Parser = parser ?? new EmailParser(Renderer); 30 | } 31 | 32 | /// 33 | /// Creates a new . 34 | /// 35 | public EmailViewResult(Email email) 36 | : this(email, null, null) 37 | { 38 | } 39 | 40 | /// 41 | /// When called by the action invoker, renders the view to the response. 42 | /// 43 | public override void ExecuteResult(ControllerContext context) 44 | { 45 | var httpContext = context.RequestContext.HttpContext; 46 | var query = httpContext.Request.QueryString; 47 | var format = query["format"]; 48 | var contentType = ExecuteResult(context.HttpContext.Response.Output, format); 49 | httpContext.Response.ContentType = contentType; 50 | } 51 | 52 | /// 53 | /// Writes the email preview in the given format. 54 | /// 55 | /// The content type for the HTTP response. 56 | public string ExecuteResult(TextWriter writer, string format = null) 57 | { 58 | var result = Renderer.Render(Email); 59 | var mailMessage = Parser.Parse(result, Email); 60 | 61 | // no special requests; render what's in the template 62 | if (string.IsNullOrEmpty(format)) 63 | { 64 | if (!mailMessage.IsBodyHtml) 65 | { 66 | writer.Write(result); 67 | return TextContentType; 68 | } 69 | 70 | var template = Extract(result); 71 | template.Write(writer); 72 | return HtmlContentType; 73 | } 74 | 75 | // Check if alternative 76 | var alternativeContentType = CheckAlternativeViews(writer, mailMessage, format); 77 | 78 | if (!string.IsNullOrEmpty(alternativeContentType)) 79 | return alternativeContentType; 80 | 81 | if (format == "text") 82 | { 83 | if(mailMessage.IsBodyHtml) 84 | throw new NotSupportedException("No text view available for this email"); 85 | 86 | writer.Write(result); 87 | return TextContentType; 88 | } 89 | 90 | if (format == "html") 91 | { 92 | if (!mailMessage.IsBodyHtml) 93 | throw new NotSupportedException("No html view available for this email"); 94 | 95 | var template = Extract(result); 96 | template.Write(writer); 97 | return HtmlContentType; 98 | } 99 | 100 | throw new NotSupportedException(string.Format("Unsupported format {0}", format)); 101 | } 102 | 103 | static string CheckAlternativeViews(TextWriter writer, MailMessage mailMessage, string format) 104 | { 105 | var contentType = format == "html" 106 | ? HtmlContentType 107 | : TextContentType; 108 | 109 | // check for alternative view 110 | var view = mailMessage.AlternateViews.FirstOrDefault(v => v.ContentType.MediaType == contentType); 111 | 112 | if (view == null) 113 | return null; 114 | 115 | string content; 116 | using (var reader = new StreamReader(view.ContentStream)) 117 | content = reader.ReadToEnd(); 118 | 119 | content = ReplaceLinkedImagesWithEmbeddedImages(view, content); 120 | 121 | writer.Write(content); 122 | return contentType; 123 | } 124 | 125 | class TemplateParts 126 | { 127 | readonly string header; 128 | readonly string body; 129 | 130 | public TemplateParts(string header, string body) 131 | { 132 | this.header = header; 133 | this.body = body; 134 | } 135 | 136 | public void Write(TextWriter writer) 137 | { 138 | writer.WriteLine(""); 141 | writer.WriteLine(body); 142 | } 143 | } 144 | 145 | static TemplateParts Extract(string template) 146 | { 147 | var headerBuilder = new StringBuilder(); 148 | 149 | using (var reader = new StringReader(template)) 150 | { 151 | // try to read until we passed headers 152 | var line = reader.ReadLine(); 153 | 154 | while (line != null) 155 | { 156 | if (string.IsNullOrEmpty(line)) 157 | { 158 | return new TemplateParts(headerBuilder.ToString(), reader.ReadToEnd()); 159 | } 160 | 161 | headerBuilder.AppendLine(line); 162 | line = reader.ReadLine(); 163 | } 164 | } 165 | 166 | return null; 167 | } 168 | 169 | internal static string ReplaceLinkedImagesWithEmbeddedImages(AlternateView view, string content) 170 | { 171 | var resources = view.LinkedResources; 172 | 173 | if (!resources.Any()) 174 | return content; 175 | 176 | foreach (var resource in resources) 177 | { 178 | var find = "src=\"cid:" + resource.ContentId + "\""; 179 | var imageData = ComposeImageData(resource); 180 | content = content.Replace(find, "src=\"" + imageData + "\""); 181 | } 182 | 183 | return content; 184 | } 185 | 186 | static string ComposeImageData(LinkedResource resource) 187 | { 188 | var contentType = resource.ContentType.MediaType; 189 | var bytes = ReadFully(resource.ContentStream); 190 | return string.Format("data:{0};base64,{1}", 191 | contentType, 192 | Convert.ToBase64String(bytes)); 193 | } 194 | 195 | static byte[] ReadFully(Stream input) 196 | { 197 | using (var ms = new MemoryStream()) 198 | { 199 | input.CopyTo(ms); 200 | return ms.ToArray(); 201 | } 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/Postal/FileSystemRazorView.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Web.Mvc; 3 | using RazorEngine; 4 | 5 | namespace Postal 6 | { 7 | /// 8 | /// A view that uses the Razor engine to render a templates loaded directly from the 9 | /// file system. This means it will work outside of ASP.NET. 10 | /// 11 | public class FileSystemRazorView : IView 12 | { 13 | readonly string template; 14 | readonly string cacheName; 15 | 16 | /// 17 | /// Creates a new using the given view filename. 18 | /// 19 | /// The filename of the view. 20 | public FileSystemRazorView(string filename) 21 | { 22 | template = File.ReadAllText(filename); 23 | cacheName = filename; 24 | } 25 | 26 | /// 27 | /// Renders the view into the given . 28 | /// 29 | /// The that contains the view data model. 30 | /// The used to write the rendered output. 31 | public void Render(ViewContext viewContext, TextWriter writer) 32 | { 33 | var content = Razor.Parse(template, viewContext.ViewData.Model, cacheName); 34 | 35 | writer.Write(content); 36 | writer.Flush(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Postal/FileSystemRazorViewEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Web.Mvc; 6 | 7 | namespace Postal 8 | { 9 | /// 10 | /// A view engine that uses the Razor engine to render a templates loaded directly from the 11 | /// file system. This means it will work outside of ASP.NET. 12 | /// 13 | public class FileSystemRazorViewEngine : IViewEngine 14 | { 15 | readonly string viewPathRoot; 16 | 17 | /// 18 | /// Creates a new that finds views within the given path. 19 | /// 20 | /// The root directory that contains views. 21 | public FileSystemRazorViewEngine(string viewPathRoot) 22 | { 23 | this.viewPathRoot = viewPathRoot; 24 | } 25 | 26 | string GetViewFullPath(string path) 27 | { 28 | return Path.Combine(viewPathRoot, path); 29 | } 30 | 31 | /// 32 | /// Tries to find a razor view (.cshtml or .vbhtml files). 33 | /// 34 | public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) 35 | { 36 | var possibleFilenames = new List(); 37 | 38 | if (!partialViewName.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase) 39 | && !partialViewName.EndsWith(".vbhtml", StringComparison.OrdinalIgnoreCase)) 40 | { 41 | possibleFilenames.Add(partialViewName + ".cshtml"); 42 | possibleFilenames.Add(partialViewName + ".vbhtml"); 43 | } 44 | else 45 | { 46 | possibleFilenames.Add(partialViewName); 47 | } 48 | 49 | var possibleFullPaths = possibleFilenames.Select(GetViewFullPath).ToArray(); 50 | 51 | var existingPath = possibleFullPaths.FirstOrDefault(File.Exists); 52 | 53 | if (existingPath != null) 54 | { 55 | return new ViewEngineResult(new FileSystemRazorView(existingPath), this); 56 | } 57 | else 58 | { 59 | return new ViewEngineResult(possibleFullPaths); 60 | } 61 | } 62 | 63 | /// 64 | /// Tries to find a razor view (.cshtml or .vbhtml files). 65 | /// 66 | public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) 67 | { 68 | return FindPartialView(controllerContext, viewName, useCache); 69 | } 70 | 71 | /// 72 | /// Does nothing. 73 | /// 74 | public void ReleaseView(ControllerContext controllerContext, IView view) 75 | { 76 | // Nothing to do here - FileSystemRazorView does not need disposing. 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Postal/HtmlExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Mvc; 3 | using System; 4 | 5 | namespace Postal 6 | { 7 | /// 8 | /// Helper methods that extend . 9 | /// 10 | public static class HtmlExtensions 11 | { 12 | /// 13 | /// Embeds the given image into the email and returns an HTML <img> tag referencing the image. 14 | /// 15 | /// The . 16 | /// An image file path or URL. A file path can be relative to the web application root directory. 17 | /// The content for the <img alt> attribute. 18 | /// An HTML <img> tag. 19 | public static IHtmlString EmbedImage(this HtmlHelper html, string imagePathOrUrl, string alt = "") 20 | { 21 | if (string.IsNullOrWhiteSpace(imagePathOrUrl)) throw new ArgumentException("Path or URL required", "imagePathOrUrl"); 22 | 23 | if (IsFileName(imagePathOrUrl)) 24 | { 25 | imagePathOrUrl = html.ViewContext.HttpContext.Server.MapPath(imagePathOrUrl); 26 | } 27 | var imageEmbedder = (ImageEmbedder)html.ViewData[ImageEmbedder.ViewDataKey]; 28 | var resource = imageEmbedder.ReferenceImage(imagePathOrUrl); 29 | return new HtmlString(string.Format("\"{1}\"/", resource.ContentId, html.AttributeEncode(alt))); 30 | } 31 | 32 | static bool IsFileName(string pathOrUrl) 33 | { 34 | return !(pathOrUrl.StartsWith("http:", StringComparison.OrdinalIgnoreCase) 35 | || pathOrUrl.StartsWith("https:", StringComparison.OrdinalIgnoreCase)); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Postal/IEmailParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Mail; 3 | 4 | namespace Postal 5 | { 6 | /// 7 | /// Parses raw string output of email views into . 8 | /// 9 | public interface IEmailParser 10 | { 11 | /// 12 | /// Creates a from the string output of an email view. 13 | /// 14 | /// The string output of the email view. 15 | /// The email data used to render the view. 16 | /// The created 17 | MailMessage Parse(string emailViewOutput, Email email); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Postal/IEmailService.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Mail; 2 | using System.Threading.Tasks; 3 | 4 | namespace Postal 5 | { 6 | /// 7 | /// Creates and send email. 8 | /// 9 | public interface IEmailService 10 | { 11 | /// 12 | /// Creates and sends a using . 13 | /// This uses the default configuration for mail defined in web.config. 14 | /// 15 | /// The email to send. 16 | void Send(Email email); 17 | 18 | /// 19 | /// Creates and sends a asynchronously using . 20 | /// This uses the default configuration for mail defined in web.config. 21 | /// 22 | /// The email to send. 23 | /// A that can be used to await completion of sending the email. 24 | Task SendAsync(Email email); 25 | 26 | /// 27 | /// Creates a new for the given email. You can 28 | /// modify the message, for example adding attachments, and then send this yourself. 29 | /// 30 | /// The email to generate. 31 | /// A new . 32 | MailMessage CreateMailMessage(Email email); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Postal/IEmailViewRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Postal 4 | { 5 | /// 6 | /// Renders an email view. 7 | /// 8 | public interface IEmailViewRenderer 9 | { 10 | /// 11 | /// Renders an email view based on the provided view name. 12 | /// 13 | /// The email data to pass to the view. 14 | /// Optional, the name of the view. If null, the ViewName of the email will be used. 15 | /// The string result of rendering the email. 16 | string Render(Email email, string viewName = null); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Postal/ImageEmbedder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Mail; 4 | using System.IO; 5 | using System.Net.Mime; 6 | using System.Net; 7 | 8 | namespace Postal 9 | { 10 | /// 11 | /// Used by the helper method. 12 | /// It generates the objects need to embed images into an email. 13 | /// 14 | public class ImageEmbedder 15 | { 16 | internal static string ViewDataKey = "Postal.ImageEmbedder"; 17 | 18 | /// 19 | /// Creates a new . 20 | /// 21 | public ImageEmbedder() 22 | { 23 | createLinkedResource = CreateLinkedResource; 24 | } 25 | 26 | /// 27 | /// Creates a new . 28 | /// 29 | /// A delegate that creates a from an image path or URL. 30 | public ImageEmbedder(Func createLinkedResource) 31 | { 32 | this.createLinkedResource = createLinkedResource; 33 | } 34 | 35 | readonly Func createLinkedResource; 36 | readonly Dictionary images = new Dictionary(); 37 | 38 | /// 39 | /// Gets if any images have been referenced. 40 | /// 41 | public bool HasImages 42 | { 43 | get { return images.Count > 0; } 44 | } 45 | 46 | /// 47 | /// Creates a from an image path or URL. 48 | /// 49 | /// The image path or URL. 50 | /// A new 51 | public static LinkedResource CreateLinkedResource(string imagePathOrUrl) 52 | { 53 | if (Uri.IsWellFormedUriString(imagePathOrUrl, UriKind.Absolute)) 54 | { 55 | var client = new WebClient(); 56 | var bytes = client.DownloadData(imagePathOrUrl); 57 | return new LinkedResource(new MemoryStream(bytes)); 58 | } 59 | else 60 | { 61 | return new LinkedResource(File.OpenRead(imagePathOrUrl)); 62 | } 63 | } 64 | 65 | /// 66 | /// Records a reference to the given image. 67 | /// 68 | /// The image path or URL. 69 | /// The content type of the image e.g. "image/png". If null, then content type is determined from the file name extension. 70 | /// A representing the embedded image. 71 | public LinkedResource ReferenceImage(string imagePathOrUrl, string contentType = null) 72 | { 73 | LinkedResource resource; 74 | if (images.TryGetValue(imagePathOrUrl, out resource)) return resource; 75 | 76 | resource = createLinkedResource(imagePathOrUrl); 77 | 78 | contentType = contentType ?? DetermineContentType(imagePathOrUrl); 79 | if (contentType != null) 80 | { 81 | resource.ContentType = new ContentType(contentType); 82 | } 83 | 84 | images[imagePathOrUrl] = resource; 85 | return resource; 86 | } 87 | 88 | string DetermineContentType(string pathOrUrl) 89 | { 90 | if (pathOrUrl == null) throw new ArgumentNullException("pathOrUrl"); 91 | 92 | var extension = Path.GetExtension(pathOrUrl).ToLowerInvariant(); 93 | switch (extension) 94 | { 95 | case ".png": 96 | return "image/png"; 97 | case ".jpeg": 98 | case ".jpg": 99 | return "image/jpeg"; 100 | case ".gif": 101 | return "image/gif"; 102 | default: 103 | return null; 104 | } 105 | } 106 | 107 | /// 108 | /// Adds recorded image references to the given . 109 | /// 110 | public void AddImagesToView(AlternateView view) 111 | { 112 | foreach (var image in images) 113 | { 114 | view.LinkedResources.Add(image.Value); 115 | } 116 | } 117 | 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Postal/ParserUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Postal 6 | { 7 | /// 8 | /// Helper methods for parsing email. 9 | /// 10 | public static class ParserUtils 11 | { 12 | /// 13 | /// Headers are of the form "(key): (value)" e.g. "Subject: Hello, world". 14 | /// The headers block is terminated by an empty line. 15 | /// 16 | public static void ParseHeaders(TextReader reader, Action useKeyAndValue) 17 | { 18 | string line; 19 | while (string.IsNullOrWhiteSpace(line = reader.ReadLine())) 20 | { 21 | // Skip over any empty lines before the headers. 22 | } 23 | 24 | var headerStart = new Regex(@"^\s*([A-Za-z\-]+)\s*:\s*(.*)"); 25 | do 26 | { 27 | var match = headerStart.Match(line); 28 | if (!match.Success) break; 29 | 30 | var key = match.Groups[1].Value.ToLowerInvariant(); 31 | var value = match.Groups[2].Value.TrimEnd(); 32 | useKeyAndValue(key, value); 33 | } while (!string.IsNullOrWhiteSpace(line = reader.ReadLine())); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Postal/Postal.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {779257EF-F8DD-495B-8B54-1F2E6AC5FA9B} 8 | Library 9 | Properties 10 | Postal 11 | Postal 12 | v4.5 13 | 512 14 | ..\..\ 15 | true 16 | 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | TRACE;DEBUG;NET45;TESTABLE 24 | prompt 25 | 4 26 | bin\Debug\Postal.xml 27 | false 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE;NET45;TESTABLE 34 | prompt 35 | 4 36 | bin\Release\Postal.xml 37 | false 38 | 39 | 40 | $(DefineConstants);NET45 41 | 42 | 43 | false 44 | 45 | 46 | ..\Postal.snk 47 | 48 | 49 | 50 | False 51 | True 52 | 53 | 54 | False 55 | ..\..\packages\RazorEngine.3.4.1\lib\net45\RazorEngine.dll 56 | 57 | 58 | 59 | 60 | 61 | 62 | False 63 | ..\..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.Helpers.dll 64 | 65 | 66 | False 67 | ..\..\packages\Microsoft.AspNet.Mvc.5.1.2\lib\net45\System.Web.Mvc.dll 68 | 69 | 70 | False 71 | ..\..\packages\Microsoft.AspNet.Razor.3.1.2\lib\net45\System.Web.Razor.dll 72 | 73 | 74 | False 75 | ..\..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.dll 76 | 77 | 78 | False 79 | ..\..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.Deployment.dll 80 | 81 | 82 | False 83 | ..\..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.Razor.dll 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | Designer 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 122 | -------------------------------------------------------------------------------- /src/Postal/ResourceRazorView.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | using System.Web.Mvc; 4 | using RazorEngine; 5 | 6 | namespace Postal 7 | { 8 | /// 9 | /// An that reads its content from an assembly resource. 10 | /// 11 | public class ResourceRazorView : IView 12 | { 13 | private readonly string resourcePath; 14 | private readonly string template; 15 | 16 | /// 17 | /// Creates a new for a given assembly and resource. 18 | /// 19 | /// The assembly containing the resource. 20 | /// The resource path. 21 | public ResourceRazorView(Assembly sourceAssembly, string resourcePath) 22 | { 23 | this.resourcePath = resourcePath; 24 | // We've already ensured that the resource exists in ResourceRazorViewEngine 25 | // ReSharper disable AssignNullToNotNullAttribute 26 | using (var stream = sourceAssembly.GetManifestResourceStream(resourcePath)) 27 | using (var reader = new StreamReader(stream)) 28 | template = reader.ReadToEnd(); 29 | // ReSharper restore AssignNullToNotNullAttribute 30 | } 31 | 32 | /// 33 | /// Renders the view into the given . 34 | /// 35 | /// Contains the view data model. 36 | /// The used to write the rendered output. 37 | public void Render(ViewContext viewContext, TextWriter writer) 38 | { 39 | var content = Razor.Parse(template, viewContext.ViewData.Model, resourcePath); 40 | 41 | writer.Write(content); 42 | writer.Flush(); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/Postal/ResourceRazorViewEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Web.Mvc; 6 | 7 | namespace Postal 8 | { 9 | /// 10 | /// A view engine that uses the Razor engine to render a templates loaded from assembly resource. 11 | /// This means it will work outside of ASP.NET. 12 | /// 13 | public class ResourceRazorViewEngine : IViewEngine 14 | { 15 | private readonly Assembly viewSourceAssembly; 16 | private readonly string viewPathRoot; 17 | 18 | /// 19 | /// Creates a new that finds views in the given assembly. 20 | /// 21 | /// The assembly containing view resources. 22 | /// A common resource path prefix. 23 | public ResourceRazorViewEngine(Assembly viewSourceAssembly, string viewPathRoot) 24 | { 25 | this.viewSourceAssembly = viewSourceAssembly; 26 | this.viewPathRoot = viewPathRoot; 27 | } 28 | 29 | /// 30 | /// Tries to find a razor view (.cshtml or .vbhtml files). 31 | /// 32 | public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) 33 | { 34 | var possibleFilenames = new List(); 35 | 36 | if (!partialViewName.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase) 37 | && !partialViewName.EndsWith(".vbhtml", StringComparison.OrdinalIgnoreCase)) 38 | { 39 | possibleFilenames.Add(partialViewName + ".cshtml"); 40 | possibleFilenames.Add(partialViewName + ".vbhtml"); 41 | } 42 | else 43 | { 44 | possibleFilenames.Add(partialViewName); 45 | } 46 | 47 | var possibleFullPaths = possibleFilenames.Select(GetViewFullPath).ToArray(); 48 | 49 | var existingPath = possibleFullPaths.FirstOrDefault(ResourceExists); 50 | 51 | if (existingPath != null) 52 | { 53 | return new ViewEngineResult(new ResourceRazorView(viewSourceAssembly, existingPath), this); 54 | } 55 | 56 | return new ViewEngineResult(possibleFullPaths); 57 | } 58 | 59 | /// 60 | /// Tries to find a razor view (.cshtml or .vbhtml files). 61 | /// 62 | public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) 63 | { 64 | return FindPartialView(controllerContext, viewName, useCache); 65 | } 66 | 67 | /// 68 | /// Does nothing. 69 | /// 70 | public void ReleaseView(ControllerContext controllerContext, IView view) 71 | { 72 | // Nothing to do here - ResourceRazorView does not need disposing. 73 | } 74 | 75 | string GetViewFullPath(string path) 76 | { 77 | return String.Format("{0}.{1}", viewPathRoot, path); 78 | } 79 | 80 | bool ResourceExists(string name) 81 | { 82 | return viewSourceAssembly.GetManifestResourceNames().Contains(name); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/Postal/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Postal/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Samples/ConsoleSample/ConsoleSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2C24E942-E0E1-4116-838B-ACD25653D5B2} 8 | Exe 9 | Properties 10 | ConsoleSample 11 | ConsoleSample 12 | v4.5 13 | 512 14 | ..\..\..\ 15 | true 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | True 39 | ..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 40 | 41 | 42 | 43 | 44 | False 45 | ..\..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.Helpers.dll 46 | 47 | 48 | 49 | False 50 | ..\..\..\packages\Microsoft.AspNet.Mvc.5.1.1\lib\net45\System.Web.Mvc.dll 51 | 52 | 53 | False 54 | ..\..\..\packages\Microsoft.AspNet.Razor.3.1.1\lib\net45\System.Web.Razor.dll 55 | 56 | 57 | False 58 | ..\..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.WebPages.dll 59 | 60 | 61 | False 62 | ..\..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.WebPages.Deployment.dll 63 | 64 | 65 | False 66 | ..\..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.WebPages.Razor.dll 67 | 68 | 69 | 70 | 71 | {779257ef-f8dd-495b-8b54-1f2e6ac5fa9b} 72 | Postal 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 93 | -------------------------------------------------------------------------------- /src/Samples/ConsoleSample/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Web.Mvc; 3 | using Postal; 4 | 5 | namespace ConsoleSample 6 | { 7 | /* 8 | Before running this sample, please start the SMTP development server, 9 | found in the Postal code directory: tools\smtp4dev.exe 10 | 11 | Use the SMTP development server to inspect the contents of generated email (headers, content, etc). 12 | No email is really sent, so it's perfect for debugging. 13 | */ 14 | 15 | class Program // That's right, no asp.net runtime required! 16 | { 17 | static void Main(string[] args) 18 | { 19 | // Get the path to the directory containing views 20 | var viewsPath = Path.GetFullPath(@"..\..\Views"); 21 | 22 | var engines = new ViewEngineCollection(); 23 | engines.Add(new FileSystemRazorViewEngine(viewsPath)); 24 | 25 | var service = new EmailService(engines); 26 | 27 | dynamic email = new Email("Test"); 28 | email.Message = "Hello, non-asp.net world!"; 29 | service.Send(email); 30 | 31 | // Alternatively, set the service factory like this: 32 | /* 33 | Email.CreateEmailService = () => new EmailService(engines); 34 | 35 | dynamic email = new Email("Test"); 36 | email.Message = "Hello, non-asp.net world!"; 37 | email.Send(); 38 | */ 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/Samples/ConsoleSample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("ConsoleSample")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Microsoft")] 11 | [assembly: AssemblyProduct("ConsoleSample")] 12 | [assembly: AssemblyCopyright("Copyright © Microsoft 2011")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("5b6e1475-dce8-474f-9654-a081b49263a7")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/Samples/ConsoleSample/Views/Test.cshtml: -------------------------------------------------------------------------------- 1 | To: test@test.com 2 | From: test@test.com 3 | Subject: Subject here 4 | 5 | Example email. 6 | @Model.Message 7 | 8 | @* You HAVE to use Model when using file system razor views. ViewBag will not work. *@ 9 | @* See http://razorengine.codeplex.com/ for more info *@ -------------------------------------------------------------------------------- /src/Samples/ConsoleSample/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Samples/ConsoleSample/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Samples/ResourceSample/Program.cs: -------------------------------------------------------------------------------- 1 |  2 | using Postal; 3 | using System.Web.Mvc; 4 | 5 | namespace ResourceSample 6 | { 7 | /* 8 | Before running this sample, please start the SMTP development server, 9 | found in the Postal code directory: tools\smtp4dev.exe 10 | 11 | Use the SMTP development server to inspect the contents of generated email (headers, content, etc). 12 | No email is really sent, so it's perfect for debugging. 13 | */ 14 | 15 | class Program 16 | { 17 | static void Main() 18 | { 19 | var engines = new ViewEngineCollection 20 | { 21 | new ResourceRazorViewEngine(typeof(Program).Assembly, @"ResourceSample.Resources.Views") 22 | }; 23 | 24 | var service = new EmailService(engines); 25 | 26 | dynamic email = new Email("Test"); 27 | email.Message = "Hello, non-asp.net world!"; 28 | service.Send(email); 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/Samples/ResourceSample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("ResourceSample")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("ResourceSample")] 12 | [assembly: AssemblyCopyright("Copyright © Andrew Davey 2013")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("fddbbd60-cdda-4e67-9ad9-f7861128d7b3")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/Samples/ResourceSample/ResourceSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AB26BB59-F976-4361-A041-21C7EF1F2AE5} 8 | Exe 9 | Properties 10 | ResourceSample 11 | ResourceSample 12 | v4.5 13 | 512 14 | ..\..\..\ 15 | true 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | false 28 | 29 | 30 | AnyCPU 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | false 38 | 39 | 40 | 41 | True 42 | ..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 43 | 44 | 45 | 46 | 47 | False 48 | ..\..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.Helpers.dll 49 | 50 | 51 | False 52 | ..\..\..\packages\Microsoft.AspNet.Mvc.5.1.1\lib\net45\System.Web.Mvc.dll 53 | 54 | 55 | False 56 | ..\..\..\packages\Microsoft.AspNet.Razor.3.1.1\lib\net45\System.Web.Razor.dll 57 | 58 | 59 | False 60 | ..\..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.WebPages.dll 61 | 62 | 63 | False 64 | ..\..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.WebPages.Deployment.dll 65 | 66 | 67 | False 68 | ..\..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.WebPages.Razor.dll 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | {779257ef-f8dd-495b-8b54-1f2e6ac5fa9b} 79 | Postal 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 100 | -------------------------------------------------------------------------------- /src/Samples/ResourceSample/Resources/Views/Test.cshtml: -------------------------------------------------------------------------------- 1 | To: test@test.com 2 | From: test@test.com 3 | Subject: Subject here 4 | 5 | Example email. 6 | @Model.Message 7 | 8 | @* You HAVE to use Model when using file system razor views. ViewBag will not work. *@ 9 | @* See http://razorengine.codeplex.com/ for more info *@ -------------------------------------------------------------------------------- /src/Samples/ResourceSample/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/Samples/ResourceSample/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Samples/WebSample/App_Start/FilterConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | 3 | namespace WebSample 4 | { 5 | public class FilterConfig 6 | { 7 | public static void RegisterGlobalFilters(GlobalFilterCollection filters) 8 | { 9 | filters.Add(new HandleErrorAttribute()); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Samples/WebSample/App_Start/RouteConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using System.Web.Routing; 7 | 8 | namespace WebSample 9 | { 10 | public class RouteConfig 11 | { 12 | public static void RegisterRoutes(RouteCollection routes) 13 | { 14 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 15 | 16 | routes.MapRoute( 17 | name: "Default", 18 | url: "{controller}/{action}/{id}", 19 | defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 20 | ); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Samples/WebSample/Content/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.1.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-color:#357ebd}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /src/Samples/WebSample/Content/postal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewdavey/postal/9b0e426aa79af93ce67b8b1a6f08d561f0f07248/src/Samples/WebSample/Content/postal.png -------------------------------------------------------------------------------- /src/Samples/WebSample/Controllers/EmailController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web.Mvc; 3 | using Postal; 4 | 5 | namespace WebSample.Controllers 6 | { 7 | public class EmailController : Controller 8 | { 9 | [HttpPost] 10 | public ActionResult SendSimple() 11 | { 12 | dynamic email = new Email("Simple"); 13 | email.Date = DateTime.UtcNow.ToString(); 14 | email.Send(); 15 | 16 | return RedirectToAction("Sent", "Home"); 17 | } 18 | 19 | [HttpPost] 20 | public ActionResult SendMultiPart() 21 | { 22 | dynamic email = new Email("MultiPart"); 23 | email.Date = DateTime.UtcNow.ToString(); 24 | email.Send(); 25 | 26 | return RedirectToAction("Sent", "Home"); 27 | } 28 | 29 | [HttpPost] 30 | public ActionResult SendTypedEmail() 31 | { 32 | var email = new TypedEmail(); 33 | email.Date = DateTime.UtcNow.ToString(); 34 | email.Send(); 35 | 36 | return RedirectToAction("Sent", "Home"); 37 | } 38 | } 39 | 40 | public class TypedEmail : Email 41 | { 42 | public string Date { get; set; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Samples/WebSample/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | 3 | namespace WebSample.Controllers 4 | { 5 | public class HomeController : Controller 6 | { 7 | public ActionResult Index() 8 | { 9 | return View(); 10 | } 11 | 12 | public ActionResult Samples() 13 | { 14 | return View(); 15 | } 16 | 17 | public ActionResult Sent() 18 | { 19 | return View(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Samples/WebSample/Controllers/PreviewController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using Postal; 7 | 8 | namespace WebSample.Controllers 9 | { 10 | public class PreviewController : Controller 11 | { 12 | public ActionResult Simple() 13 | { 14 | dynamic email = new Email("Simple"); 15 | email.Date = DateTime.UtcNow.ToString(); 16 | 17 | return new EmailViewResult(email); 18 | } 19 | 20 | public ActionResult SimpleHtml() 21 | { 22 | dynamic email = new Email("SimpleHtml"); 23 | email.Date = DateTime.UtcNow.ToString(); 24 | 25 | return new EmailViewResult(email); 26 | } 27 | 28 | public ActionResult MultiPart() 29 | { 30 | dynamic email = new Email("MultiPart"); 31 | email.Date = DateTime.UtcNow.ToString(); 32 | 33 | return new EmailViewResult(email); 34 | } 35 | 36 | public ActionResult Typed() 37 | { 38 | var email = new TypedEmail(); 39 | email.Date = DateTime.UtcNow.ToString(); 40 | 41 | return new EmailViewResult(email); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Samples/WebSample/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="WebSample.MvcApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /src/Samples/WebSample/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Mvc; 3 | using System.Web.Routing; 4 | 5 | namespace WebSample 6 | { 7 | public class MvcApplication : HttpApplication 8 | { 9 | protected void Application_Start() 10 | { 11 | FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 12 | RouteConfig.RegisterRoutes(RouteTable.Routes); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Samples/WebSample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("WebSample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WebSample")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("0dee574a-3e2c-425f-8f0e-6f72f9926a1b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/Samples/WebSample/Views/Emails/MultiPart.Html.cshtml: -------------------------------------------------------------------------------- 1 | Content-Type: text/html; charset=utf8 2 | 3 | 4 | 5 |

This is an HTML message

6 |

Generated by Postal on @ViewBag.Date

7 |

8 | @Html.EmbedImage("~/Content/postal.png") 9 |

10 | 11 | -------------------------------------------------------------------------------- /src/Samples/WebSample/Views/Emails/MultiPart.Text.cshtml: -------------------------------------------------------------------------------- 1 | Content-Type: text/plain; charset=utf8 2 | 3 | This is a plain text message 4 | 5 | Generated by Postal on @ViewBag.Date -------------------------------------------------------------------------------- /src/Samples/WebSample/Views/Emails/MultiPart.cshtml: -------------------------------------------------------------------------------- 1 | To: test@example.org 2 | From: test@example.org 3 | Subject: Multi-part email example 4 | Views: Text,Html -------------------------------------------------------------------------------- /src/Samples/WebSample/Views/Emails/Simple.cshtml: -------------------------------------------------------------------------------- 1 | To: test@example.org 2 | From: test@example.org 3 | Subject: Simple email example 4 | 5 | Hello, world! 6 | 7 | The date is: @ViewBag.Date -------------------------------------------------------------------------------- /src/Samples/WebSample/Views/Emails/SimpleHtml.cshtml: -------------------------------------------------------------------------------- 1 | To: test@example.org 2 | From: test@example.org 3 | Subject: Simple email example 4 | 5 | 6 | 7 | 8 |

Html

9 |

The date is: @ViewBag.Date

10 |

The To; From and Subject information is rendered as HTML comment.

11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Samples/WebSample/Views/Emails/Typed.cshtml: -------------------------------------------------------------------------------- 1 | @model WebSample.Controllers.TypedEmail 2 | To: test@example.org 3 | From: test@example.org 4 | Subject: Simple email example 5 | 6 | Hello, world! 7 | 8 | The date is: @Model.Date -------------------------------------------------------------------------------- /src/Samples/WebSample/Views/Emails/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = null; 3 | } -------------------------------------------------------------------------------- /src/Samples/WebSample/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | 

Postal Samples

2 |

3 | Before running these samples, please start the SMTP development server, 4 | found in the Postal code directory: tools\smtp4dev.exe 5 |

6 |

7 | Use the SMTP development server to inspect the contents of generated email (headers, content, etc). 8 | No email is really sent, so it's perfect for debugging. 9 |

10 |

11 | Continue » 12 |

13 | -------------------------------------------------------------------------------- /src/Samples/WebSample/Views/Home/Samples.cshtml: -------------------------------------------------------------------------------- 1 | 

Postal Samples

2 | 3 |

4 | The send buttons on this page post to Controllers\EmailController.cs
5 | The preview buttons on this page go to Controllers\PreviewController.cs 6 |

7 | 8 |

Sending a basic email

9 |

Email will be generated from Views\Email\Simple.cshtml

10 |
11 | 12 | Preview 13 |
14 | 15 |

Strongly-typed email class

16 |

Email will be generated from Views\Email\Typed.cshtml

17 |
18 | 19 | Preview 20 |
21 | 22 |

Multipart emails

23 |

24 | Use separate views to define HTML and plain-text content for a single email. 25 |

26 |
Views\Email\Simple.cshtml
27 | Views\Email\Simple.Html.cshtml
28 | Views\Email\Simple.Text.cshtml
29 |

30 | Images can be embedded into the email using @@Html.EmbedImage("~/image.png"). 31 |

32 |
33 | 34 | Preview 35 | Preview Text 36 | Preview Html 37 |
38 | -------------------------------------------------------------------------------- /src/Samples/WebSample/Views/Home/Sent.cshtml: -------------------------------------------------------------------------------- 1 | 

Email Sent

2 |

3 | Continue » 4 |

-------------------------------------------------------------------------------- /src/Samples/WebSample/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Postal Samples 5 | 6 | 7 | 8 |
9 | @RenderBody() 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Samples/WebSample/Views/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 40 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/Samples/WebSample/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } -------------------------------------------------------------------------------- /src/Samples/WebSample/Web.Debug.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /src/Samples/WebSample/Web.Release.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /src/Samples/WebSample/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/Samples/WebSample/WebSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | 2.0 10 | {20E5601B-354D-4393-951C-C3BCC970EAAC} 11 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 12 | Library 13 | Properties 14 | WebSample 15 | WebSample 16 | v4.5 17 | false 18 | true 19 | 20 | 21 | 22 | 23 | ..\..\..\ 24 | true 25 | 26 | 27 | 28 | true 29 | full 30 | false 31 | bin\ 32 | DEBUG;TRACE 33 | prompt 34 | 4 35 | false 36 | 37 | 38 | pdbonly 39 | true 40 | bin\ 41 | TRACE 42 | prompt 43 | 4 44 | false 45 | 46 | 47 | 48 | 49 | True 50 | ..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 51 | 52 | 53 | ..\..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | False 62 | ..\..\..\packages\Microsoft.AspNet.WebApi.Client.5.1.1\lib\net45\System.Net.Http.Formatting.dll 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | False 71 | ..\..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.Helpers.dll 72 | 73 | 74 | False 75 | ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.1.1\lib\net45\System.Web.Http.dll 76 | 77 | 78 | False 79 | ..\..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.1.1\lib\net45\System.Web.Http.WebHost.dll 80 | 81 | 82 | 83 | 84 | False 85 | ..\..\..\packages\Microsoft.AspNet.Mvc.5.1.1\lib\net45\System.Web.Mvc.dll 86 | 87 | 88 | False 89 | ..\..\..\packages\Microsoft.AspNet.Razor.3.1.1\lib\net45\System.Web.Razor.dll 90 | 91 | 92 | 93 | False 94 | ..\..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.WebPages.dll 95 | 96 | 97 | False 98 | ..\..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.WebPages.Deployment.dll 99 | 100 | 101 | False 102 | ..\..\..\packages\Microsoft.AspNet.WebPages.3.1.1\lib\net45\System.Web.WebPages.Razor.dll 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | Global.asax 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | Web.config 143 | 144 | 145 | Web.config 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | {779257ef-f8dd-495b-8b54-1f2e6ac5fa9b} 168 | Postal 169 | 170 | 171 | 172 | 173 | 174 | 175 | 10.0 176 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | True 189 | True 190 | 0 191 | / 192 | http://localhost:16807/ 193 | False 194 | False 195 | 196 | 197 | False 198 | 199 | 200 | 201 | 202 | 203 | 209 | -------------------------------------------------------------------------------- /src/Samples/WebSample/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewdavey/postal/9b0e426aa79af93ce67b8b1a6f08d561f0f07248/src/Samples/WebSample/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/Samples/WebSample/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewdavey/postal/9b0e426aa79af93ce67b8b1a6f08d561f0f07248/src/Samples/WebSample/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/Samples/WebSample/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewdavey/postal/9b0e426aa79af93ce67b8b1a6f08d561f0f07248/src/Samples/WebSample/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/Samples/WebSample/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tools/smtp4dev-license.txt: -------------------------------------------------------------------------------- 1 | New BSD License (BSD) 2 | Copyright (c) 2009-2011, Robert Wood 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | 11 | * Neither the name of smtp4dev nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /tools/smtp4dev.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewdavey/postal/9b0e426aa79af93ce67b8b1a6f08d561f0f07248/tools/smtp4dev.exe --------------------------------------------------------------------------------