Tharga.Team.Email
4.2.0
dotnet add package Tharga.Team.Email --version 4.2.0
NuGet\Install-Package Tharga.Team.Email -Version 4.2.0
<PackageReference Include="Tharga.Team.Email" Version="4.2.0" />
<PackageVersion Include="Tharga.Team.Email" Version="4.2.0" />
<PackageReference Include="Tharga.Team.Email" />
paket add Tharga.Team.Email --version 4.2.0
#r "nuget: Tharga.Team.Email, 4.2.0"
#:package Tharga.Team.Email@4.2.0
#addin nuget:?package=Tharga.Team.Email&version=4.2.0
#tool nuget:?package=Tharga.Team.Email&version=4.2.0
Tharga.Team.Email
E-mail for Tharga.Team: reading a mailbox over IMAP, sending over SMTP, and the MIME handling both need.
It is the mail layer shared by Tharga.Team.Support (support cases by e-mail), team mailboxes (an address per team that receives real mail), invitation email and mail to users (categories, unsubscribe links, a persisted queue, staff messages and automatic notices). The package exists so that MailKit reaches only consumers who use mail.
The guides are Email, team mailboxes & the postmaster, Mail to users, Messages to users, Messages to contacts and Automatic notices.
What is in it
IMailClient— send a message and fetch new mail from a folder, never throwing: every outcome is a result. The mailbox is only ever read; nothing is marked, moved or deleted.MailOptions/MailServerOptions— IMAP and SMTP servers, the sending address, the folder, poll interval and time-outs,FirstReadSincefor a mailbox that already holds mail, and the file limitsMaxAttachmentBytes(25 MB),MaxAttachmentTotalBytes(50 MB) andMaxAttachmentCount(20).InboundMail,OutboundMail,MailAttachment,InboundAttachment,MailSendResult,MailFetchPosition,MailFetchResult— the mail contracts. An inbound message carries its envelope recipients (Delivered-To,X-Original-To,Envelope-To, most trustworthy first), so a host can route it by the address it was actually delivered to.- Files both ways.
InboundMail.Attachmentsholds a mail's attachments, the images its body shows (with their Content-ID) and forwarded mail as.eml, with file names sanitized.OutboundMail.Attachmentssends files; aMailAttachmentwith aContentIdis sent beside the HTML that shows it. - HTML both ways.
InboundMail.BodyHtmlis the HTML exactly as the sender wrote it — not sanitized; sanitize it before showing it.OutboundMail.BodyHtmlis sent asmultipart/alternativebeside the text, and must be sanitized by the caller. - Bounces. A delivery status notification fills
InboundMail.BouncedMessageIdandBounceReason. - Unsubscribing.
OutboundMail.UnsubscribeUrlis sent asList-Unsubscribe, withList-Unsubscribe-Postfor one-click unsubscribing (RFC 8058) when it is https. - A mailbox that could not be read says why in
MailFetchResult.Error, never with the password.
- Files both ways.
- DKIM sender check. Every mail read is verified over DNS:
InboundMail.SenderVerifiedis true when a signature verifies, signsFromand the whole body, and its domain is theFromdomain or a parent of it, whichSenderDomainnames. Keys are cached ten minutes (a miss one minute) and a lookup is cut off after five seconds; anything that fails means not verified. It vouches for the domain's mail system, not for a person: a large provider signs only its own account holder's mail, a company domain whatever its servers relay. HtmlText— the plain-text rendering of HTML the toolkit uses: paragraphs kept, list items as-lines, quotes as>lines, links astext (address).IMailBounceHandler/MailBounce— receives a bounce the support mail poller read that names no support answer; register your own to mark mail you sent as undelivered. Make it idempotent, and do not log the reason, which often quotes an address.RecipientFilter— accept mail only for chosen domains or addresses.AddThargaMailTemplate(o => o.Html = …)/MailTemplateOptions— the host's HTML frame around every HTML mail the toolkit sends (support answers, mail to users, invitations) and the previews of them, withSupportHtmlan optional frame of its own for support answers, with{{content}}(required),{{subject}},{{footer}}and{{preheader}}. The template is trusted markup; what goes into it is sanitized. With a template, a mail with text alone also gets an HTML part. See the mail template.
The transport knows nothing about teams, users or audit entries; a test guards that.
Team mailboxes
builder.Services.AddThargaTeamMailbox(o =>
{
o.Domain = "example.app";
o.Suffix = "-case"; // {key}-case@example.app, {key}-case+{item}@example.app
o.Mail.Imap.Host = "imap.example.app"; // the catch-all mailbox; leave empty to read nothing
});
builder.Services.AddScoped<IInboundMailContentStore, MyBlobContentStore>();
- An opaque, rotatable key per team — a primary, and a secondary still accepted until it retires.
- One catch-all mailbox, each mail routed by the address it was delivered to; what no team owns goes to the postmaster (
IPostmasterService, system scopes), never dropped — routed to a team, or archived without routing and restored, and read with its files throughGetBodyAsyncandOpenAttachmentAsync. - Each delivery kept once, durably, however many instances read; a mail that cannot be kept is read again rather than skipped.
- The raw message goes to the host's
IInboundMailContentStore; the toolkit keeps the envelope. Storage for keys and mail comes fromTharga.Team.MongoDB.
Mail to users
builder.Services.AddThargaUserMail(o =>
{
o.Mail.Smtp.Host = "smtp.example.com";
o.Mail.FromAddress = "hello@example.com"; // an address a reply reaches
o.BaseUrl = "https://app.example.com"; // unsubscribe links are built on it
o.AddCategory(new UserMailCategory { Key = "news", Name = "Product news" });
});
builder.Services.AddThargaUserMessages(); // staff write to users: the messages:send system scope
builder.Services.AddThargaUserNotices(o => o.Add(/* your catalogue */));
app.MapThargaMailUnsubscribe(); // anonymous; allow it in a deny-by-default host
- Categories each user turns on or off on their profile — two built-ins,
account(always required) andservice(required unlessServiceRequiredis false), plus your own — with an unsubscribe link and the RFC 8058 headers on every mail they could have turned off, how often each is sent, and a required one linking to your terms (TermsUrl). - An unsubscribe page that names the category and the product, says it turns off only that, and shows the signed-in user the link is for every category and its state (
PreferencesPath), or anyone else a way to sign in (SignInPath);PageCssstyles it. - A persisted queue any instance sends from, reading the user's choice and address when it sends, retrying only what may pass, under the outbound mail policy.
- Messages (
IUserMessageService) staff write to chosen users of chosen teams, and notices (IUserNoticeRaiser) raised by events and delivered at once or in a daily summary. - A mail history per user (
IUserMailHistoryService), and mail marked bounced when the receiving server returns it — seen when it was sent from a support mailbox's address. - Invitations go through it once it is registered: recorded, one immediate attempt, from its address. A sender of your own is kept.
- Messages to contacts (
AddThargaContactMessages,IContactMessageService, thecontacts:sendsystem scope) — staff write to people who are not users, listed by the host with its own references: each address once, in contact categories of their own, fromContactMessageOptions.FromAddressor the address of mail to users, with a footer naming the sender (SenderLine), why it came and an unsubscribe link that opts the address out of the category or of all contact mail, for good. The same unsubscribe route answers it.
Storage comes from Tharga.Team.MongoDB, views from Tharga.Team.Blazor. Every instance must share one persisted Data Protection key ring, or unsubscribe links made on one are refused by another.
Invitation email
builder.Services.AddThargaEmailInvitations(); // settings from o.Email, or pass them here
The SMTP invitation sender, through the same transport and the outbound mail policy. New in 3.29 — it replaces SmtpTeamEmailSender, which lived in Tharga.Team.Service; o.Email alone no longer registers a sender. With AddThargaUserMail registered it is replaced by mail to users, and becomes unnecessary.
Dependencies
- MailKit — IMAP, SMTP and MIME.
- DnsClient — the DNS lookups for DKIM keys.
- AngleSharp — parsing mail HTML for its text, its quoted thread and its images.
- The ASP.NET Core shared framework, privately — Data Protection and the unsubscribe route.
- Tharga.Team.Service — the outbound mail policy, audit logging and the HTML sanitizer.
Moving to 4.2
OutboundMail,InboundMailandMailFetchResultgained trailing positional parameters (OutboundMail:Attachments,BodyHtml,UnsubscribeUrl;InboundMail:BouncedMessageId,BounceReason,SenderVerified,SenderDomain,Attachments,BodyHtml;MailFetchResult:Error). Recompile; code that deconstructs them adds the new elements.InboundMail.BodyHtmlis raw; sanitize it before showing it.HadAttachmentsis now true whenever files were present, carried or not.- Every mail read is DKIM-checked, so a host reading mail needs outbound DNS; without it nothing is verified and nothing breaks.
- Dependencies changed:
DnsClientandAngleSharpadded, the ASP.NET Core framework referenced privately, andMicrosoft.Extensions.Logging.AbstractionsandMicrosoft.Extensions.Optionsno longer listed, since the framework provides them. - With
AddThargaUserMail, invitations go through mail to users, fromUserMailOptions.Mailrather thanEmailOptions; a withheld or failed invitation says "could not send". UserMailOptionsgainedServiceRequired,TermsUrl,ProductName,PreferencesPath,SignInPathandPageCss, andMailTemplateOptionsSupportHtml. Their defaults change nothing but the unsubscribe page's wording; see Moving to 4.2 for the text keys added and removed.- Messages to contacts are new (
AddThargaContactMessages); nothing changes until it is called. They share the user mail queue, so a host with its own outbox reads Moving to 4.2:UserMailKind.Contact,UserMailBatch.RecipientAddressesandIUserMailOutbox.CountSentToContactsByCategoryAsync.
Moving from Tharga.Team.Support
These types used to live in Tharga.Team.Support.Email. They are now in the namespace Tharga.Team.Email, and two were renamed:
| Before | Now |
|---|---|
Tharga.Team.Support.Email.ISupportMailClient |
Tharga.Team.Email.IMailClient |
Tharga.Team.Support.Email.SupportMailClient |
Tharga.Team.Email.MailClient (internal) |
Tharga.Team.Support.Email.* |
Tharga.Team.Email.* |
Tharga.Team.Support references this package, so support configuration (SupportCaseOptions.Email) is unchanged apart from the namespace of MailOptions.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- AngleSharp (>= 1.8.5)
- DnsClient (>= 1.8.0)
- MailKit (>= 4.18.1)
- System.Security.Cryptography.Pkcs (>= 10.0.13)
- Tharga.Team.Service (>= 4.2.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Tharga.Team.Email:
| Package | Downloads |
|---|---|
|
Tharga.Team.Support
Support and notifications for Tharga Team — route audited events to Slack channels, with the message shaped per event. |
GitHub repositories
This package is not used by any popular GitHub repositories.