Skip to content

taskscape/MailSenderService

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

MailService

What the mail sending service is

This repository contains MailService.Api, a small ASP.NET Core app that exposes a single HTTP endpoint for sending e-mail. Incoming requests never contain SMTP passwords: the server looks up SMTP settings by the sender address you supply.

Flow:

  1. The client sends a POST with JSON (Content-Type: application/json) to /api/mail/send.
  2. The endpoint checks the apiKey against MAIL_API_KEY from the .env file.
  3. It resolves fromEmail to an SMTP profile in SMTP_CONFIG_JSON (each allowed sender is a key in that JSON object).
  4. It validates recipients, subject, and that at least one of plainTextBody or htmlBody is present.
  5. EmailSenderService builds a MIME message and sends it with MailKit using the matched host, port, SSL, and credentials.

Typical HTTP results:

Status Meaning
200 Success; JSON body includes a confirmation message.
401 API key missing or wrong.
400 Validation error (e.g. unknown fromEmail, empty toEmails, missing body).
500 SMTP or internal failure while sending (details in server logs).

Property names in JSON are camelCase (for example fromEmail, toEmails, plainTextBody).

How the REST service works

The API exposes one endpoint:

  • POST /api/mail/send

Each request must include:

  • apiKey: API key that must match MAIL_API_KEY in .env
  • fromEmail: sender e-mail address that must exist in SMTP mappings
  • toEmails: list of recipient addresses
  • subject: mail subject
  • plainTextBody or htmlBody: at least one must be present (see below if you send both)

The service validates:

  1. API key
  2. Sender (fromEmail) exists in SMTP_CONFIG_JSON
  3. Request payload contains required fields

If valid, it selects SMTP configuration for the sender and sends the message through that SMTP server using MailKit.

When both plainTextBody and htmlBody are provided

The service does not pick only one of them or concatenate them into a single blob. It uses MimeKit’s BodyBuilder with both values set, which produces a standard multipart/alternative MIME message: both a plain text part and an HTML part are included in the same outgoing e-mail.

Recipients’ mail programs then choose what to display: clients that support HTML usually show the HTML part, and plain-text-only clients (or “view as plain text”) typically show the plain text part. You should treat the two fields as the same content in two formats (for example HTML newsletter plus an accessible text fallback), not as two unrelated bodies.

If you supply only plainTextBody or only htmlBody, the message is sent with that single part only.

Configuration (.env)

Place a .env file next to the project (same folder as MailService.Api.csproj):

  • MailService.Api/.env

Required variables:

  • MAIL_API_KEY
  • SMTP_CONFIG_JSON (JSON object keyed by sender e-mail)

Example:

MAIL_API_KEY=replace_with_strong_secret_key
SMTP_CONFIG_JSON={"noreply@example.com":{"host":"smtp.example.com","port":587,"enableSsl":true,"username":"smtp-user","password":"smtp-password","fromName":"Mail Service","fromEmail":"noreply@example.com"}}

Run

cd MailService.Api
dotnet run

By default (see Properties/launchSettings.json) the app listens on http://localhost:5127 and https://localhost:7012. Use the same base URL in your client examples.

Host on IIS (Windows)

These steps assume a Windows machine with IIS acting as the reverse proxy; the ASP.NET Core Module (ANCM) starts your app in a worker process.

1. Install IIS and the .NET Hosting Bundle

  1. Turn on Internet Information Services and the features you need (at minimum Web Management Tools and World Wide Web Services). On Windows Server this is done in Server Manager → Add Roles and Features; on Windows client editions, Turn Windows features on or off.
  2. Install the ASP.NET Core Hosting Bundle for the same major .NET version as the app. This repository targets net10.0 (MailService.Api.csproj), so install the bundle that includes that runtime. The hosting bundle installs the .NET runtime and the IIS AspNetCoreModuleV2 shim.

Restart IIS after installing the bundle (for example iisreset from an elevated command prompt, or restart the server).

2. Publish the application

On a machine with the SDK, from the repository root:

dotnet publish .\MailService.Api\MailService.Api.csproj -c Release -o C:\Publish\MailService.Api

Use any output folder you prefer; that folder becomes the IIS site’s physical path.

The publish output includes web.config, which tells IIS to forward requests to your app via ANCM.

3. Provide configuration (.env)

The app loads .env from its content root—on IIS, that is the site’s physical path (the publish folder), not the project folder on your dev machine.

Copy a valid .env file into the publish directory next to MailService.Api.dll (same place as web.config), with MAIL_API_KEY and SMTP_CONFIG_JSON as described in Configuration (.env).

Ensure the IIS app pool identity can read that file (and the folder). Avoid granting broader permissions than needed.

4. Create the app pool and site in IIS Manager

  1. Open IIS ManagerApplication PoolsAdd Application Pool.
    • Name: e.g. MailService.Api.
    • .NET CLR version: No Managed Code (ASP.NET Core runs out-of-process; the CLR setting does not load classic .NET Framework).
    • Managed pipeline mode: Integrated (default).
  2. Advanced Settings (optional but common): set Identity to a dedicated account if you do not want ApplicationPoolIdentity.
  3. Under SitesAdd Website:
    • Physical path: your publish folder (e.g. C:\Publish\MailService.Api).
    • Binding: host name, port, and (for HTTPS) certificate as required. There is no fixed port; use 80/443 or another port your clients will call.

Start the site (and ensure the app pool is started). Your API base URL is then http://your-server/api/... or https://your-server/api/... according to the binding—for example POST https://your-server/api/mail/send. If the app is installed under an application or virtual directory (for example /MailService), prefix that path: POST https://your-server/MailService/api/mail/send.

5. HTTPS and firewall

  • For HTTPS, create a binding in IIS and assign a server certificate (internal CA, public CA, or development certificate).
  • Open Windows Defender Firewall (or your network firewall) for the HTTP/HTTPS ports you expose.

6. If something fails

  • 502.5 / app won’t start: Usually a missing runtime or bad web.config. Confirm the hosting bundle version matches net10.0 and check Event Viewer → Windows Logs → Application for errors from IIS AspNetCore Module or .NET Runtime.
  • 401/400 from the API when the app does run: configuration or request shape; see the REST sections above.
  • stdout logs: You can enable temporary stdout logging in web.config for the published app per Microsoft’s ASP.NET Core IIS troubleshooting (disable or remove in production once resolved).

As an alternative to .env, you can set environment variables on the app pool or system for the same keys your deployment process supports; this app is written to read secrets from .env at startup, so for IIS the straightforward approach is a .env file in the publish folder unless you change configuration sources.

Example request

{
  "apiKey": "replace_with_strong_secret_key",
  "fromEmail": "noreply@example.com",
  "toEmails": ["recipient@example.com"],
  "subject": "Test message",
  "plainTextBody": "Hello from MailService."
}

Calling from a .NET (C#) application

Use HttpClient and PostAsJsonAsync. The snippet below uses camelCase property names in an anonymous type so the JSON matches what the API expects.

using System.Net.Http.Json;

const string baseUrl = "http://localhost:5127"; // or https://localhost:7012

using var client = new HttpClient { BaseAddress = new Uri(baseUrl) };

var payload = new
{
    apiKey = "replace_with_strong_secret_key",
    fromEmail = "noreply@example.com",
    toEmails = new[] { "recipient@example.com" },
    subject = "Test message",
    plainTextBody = "Hello from MailService.",
    // htmlBody = "<p>Hello from MailService.</p>", // optional; use instead of or with plainTextBody
};

using var response = await client.PostAsJsonAsync("/api/mail/send", payload);

if (!response.IsSuccessStatusCode)
{
    var errorBody = await response.Content.ReadAsStringAsync();
    throw new HttpRequestException($"Mail API returned {(int)response.StatusCode}: {errorBody}");
}

// Success: e.g. { "message": "Mail sent successfully." }
var json = await response.Content.ReadAsStringAsync();

For a shared client library, you can replace the anonymous type with a class whose properties use [JsonPropertyName("apiKey")] (and so on) or configure JsonSerializerOptions with PropertyNamingPolicy = JsonNamingPolicy.CamelCase.

Calling from a PHP application

Send JSON with curl (or any HTTP client). Encode the body with json_encode and set Content-Type: application/json.

<?php

$baseUrl = 'http://localhost:5127'; // or https://localhost:7012
$url = rtrim($baseUrl, '/') . '/api/mail/send';

$payload = [
    'apiKey' => 'replace_with_strong_secret_key',
    'fromEmail' => 'noreply@example.com',
    'toEmails' => ['recipient@example.com'],
    'subject' => 'Test message',
    'plainTextBody' => 'Hello from MailService.',
    // 'htmlBody' => '<p>Hello from MailService.</p>',
];

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
    CURLOPT_RETURNTRANSFER => true,
]);

$responseBody = curl_exec($ch);
if ($responseBody === false) {
    throw new RuntimeException('cURL error: ' . curl_error($ch));
}

$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status < 200 || $status >= 300) {
    throw new RuntimeException("Mail API returned HTTP $status: $responseBody");
}

// Success: e.g. {"message":"Mail sent successfully."}
echo $responseBody;

If you use Guzzle, the same payload can be sent with POST $url, ['json' => $payload].

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages