Codeunit 8905 Email Message Impl.
- App
- System Application
- Namespace
- System.Email
- Versions
- 17-28
Versions171819202122232425262728
Source in 29
src/System Application/App/Email/src/Message/EmailMessageImpl.Codeunit.al1131 lines, Copyright (c) Microsoft Corporation. MIT
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace System.Email;
using System;
using System.Environment;
using System.Telemetry;
using System.Text;
using System.Utilities;
codeunit 8905 "Email Message Impl."
{
Access = Internal;
InherentPermissions = X;
InherentEntitlements = X;
Permissions = tabledata "Sent Email" = r,
tabledata "Email Inbox" = r,
tabledata "Email Outbox" = rim,
tabledata "Email Message" = rimd,
tabledata "Email Error" = rd,
tabledata "Email Recipient" = rid,
tabledata "Email Message Attachment" = rimd,
tabledata "Email Related Record" = rd,
tabledata "Tenant Media" = rm,
tabledata "Email Attachments" = rimd;
procedure Create(EmailMessageImpl: Codeunit "Email Message Impl.")
var
EmailRelatedRecord: Record "Email Related Record";
EmailImpl: Codeunit "Email Impl";
AttachmentInStream: InStream;
begin
Create(EmailMessageImpl.GetRecipientsAsText(Enum::"Email Recipient Type"::"To"),
EmailMessageImpl.GetSubject(), EmailMessageImpl.GetBody(), EmailMessageImpl.IsBodyHTMLFormatted());
SetRecipients(Enum::"Email Recipient Type"::Cc, EmailMessageImpl.GetRecipientsAsText(Enum::"Email Recipient Type"::Cc));
SetRecipients(Enum::"Email Recipient Type"::Bcc, EmailMessageImpl.GetRecipientsAsText(Enum::"Email Recipient Type"::Bcc));
if EmailMessageImpl.Attachments_First() then
repeat
EmailMessageImpl.Attachments_GetContent(AttachmentInStream);
AddAttachment(EmailMessageImpl.Attachments_GetName(), EmailMessageImpl.Attachments_GetContentType(), AttachmentInStream, EmailMessageImpl.Attachments_IsInline(), EmailMessageImpl.Attachments_GetContentId());
until EmailMessageImpl.Attachments_Next() = 0;
EmailRelatedRecord.SetRange("Email Message Id", EmailMessageImpl.GetId());
if EmailRelatedRecord.FindSet() then
repeat
EmailImpl.AddRelation(GetId(), EmailRelatedRecord."Table Id", EmailRelatedRecord."System Id", EmailRelatedRecord."Relation Type", EmailRelatedRecord."Relation Origin");
until EmailRelatedRecord.Next() = 0;
end;
procedure Create(ToRecipients: Text; Subject: Text; Body: Text; HtmlFormatted: Boolean)
begin
Create(ToRecipients, Subject, Body, HtmlFormatted, false);
end;
procedure Create(ToRecipients: Text; Subject: Text; Body: Text; HtmlFormatted: Boolean; Sanitize: Boolean)
var
Recipients: List of [Text];
CCRecipients: List of [Text];
BCCRecipients: List of [Text];
begin
Create(Recipients, Subject, Body, HtmlFormatted, CCRecipients, BCCRecipients, Sanitize);
SetRecipients(Enum::"Email Recipient Type"::"To", ToRecipients);
end;
procedure Create(Recipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; CCRecipients: List of [Text]; BCCRecipients: List of [Text]; Sanitize: Boolean)
begin
InitializeCreation();
UpdateMessage(Recipients, Subject, Body, HtmlFormatted, '', CCRecipients, BCCRecipients, Sanitize);
end;
procedure CreateReply(ToRecipients: Text; Subject: Text; Body: Text; HtmlFormatted: Boolean; ExternalId: Text)
var
Recipients: List of [Text];
CCRecipients: List of [Text];
BCCRecipients: List of [Text];
begin
CreateReply(Recipients, Subject, Body, HtmlFormatted, ExternalId, CCRecipients, BCCRecipients);
SetRecipients(Enum::"Email Recipient Type"::"To", ToRecipients);
end;
procedure CreateReply(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; ExternalId: Text; CCRecipients: List of [Text]; BCCRecipients: List of [Text])
begin
InitializeCreation();
UpdateMessage(ToRecipients, Subject, Body, HtmlFormatted, ExternalId, CCRecipients, BCCRecipients, false);
end;
procedure CreateReplyAll(Subject: Text; Body: Text; HtmlFormatted: Boolean; ExternalId: Text)
var
ToRecipients: List of [Text];
CCRecipients: List of [Text];
BCCRecipients: List of [Text];
begin
CreateReply(ToRecipients, Subject, Body, HtmlFormatted, ExternalId, CCRecipients, BCCRecipients);
end;
local procedure InitializeCreation()
begin
Clear(GlobalEmailMessageAttachment);
Clear(GlobalEmailMessage);
InvalidateHeadersCache();
GlobalEmailMessage.Id := CreateGuid();
GlobalEmailMessage.Insert();
end;
procedure UpdateMessage(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; ExternalId: Text; CCRecipients: List of [Text]; BCCRecipients: List of [Text]; Sanitize: Boolean)
begin
if HtmlFormatted and Sanitize then
Body := SanitizeBody(Body);
SetBodyValue(Body);
SetSubjectValue(Subject);
SetBodyHTMLFormattedValue(HtmlFormatted);
SetExternalId(ExternalId);
Modify();
SetRecipients(Enum::"Email Recipient Type"::"To", ToRecipients);
SetRecipients(Enum::"Email Recipient Type"::Cc, CCRecipients);
SetRecipients(Enum::"Email Recipient Type"::Bcc, BCCRecipients);
end;
local procedure SanitizeBody(Body: Text): Text
var
AppHTMLSanitizer: DotNet AppHtmlSanitizer;
begin
AppHTMLSanitizer := AppHTMLSanitizer.AppHtmlSanitizer();
exit(AppHTMLSanitizer.SanitizeEmail(Body));
end;
procedure Modify()
var
EmailMessage: Record "Email Message";
begin
EmailMessage.SetRange(Id, GlobalEmailMessage.Id);
if not EmailMessage.IsEmpty() then // Don't modify if email message hasn't been inserted
GlobalEmailMessage.Modify();
end;
procedure GetBody() BodyText: Text
var
BodyInStream: InStream;
begin
GlobalEmailMessage.CalcFields(Body);
GlobalEmailMessage.Body.CreateInStream(BodyInStream, TextEncoding::UTF8);
BodyInStream.Read(BodyText);
end;
local procedure SetBodyValue(BodyText: Text)
var
BodyOutStream: OutStream;
begin
Clear(GlobalEmailMessage.Body);
if BodyText = '' then
exit;
ReplaceRgbaColorsWithRgb(BodyText);
GlobalEmailMessage.Body.CreateOutStream(BodyOutStream, TextEncoding::UTF8);
BodyOutStream.Write(BodyText);
end;
procedure SetBody(BodyText: Text)
begin
SetBodyValue(BodyText);
Modify();
end;
procedure AppendToBody(BodyText: Text)
var
ExistingBodyText: Text;
begin
if BodyText = '' then
exit;
ExistingBodyText := GetBody();
SetBody(ExistingBodyText + BodyText);
end;
procedure GetExternalId(): Text[2048]
begin
exit(GlobalEmailMessage."External Id");
end;
local procedure SetExternalId(ExternalId: Text)
begin
GlobalEmailMessage."External Id" := CopyStr(ExternalId, 1, MaxStrLen(GlobalEmailMessage."External Id"));
end;
procedure GetSubject(): Text[2048]
begin
exit(GlobalEmailMessage.Subject);
end;
local procedure SetSubjectValue(Subject: Text)
begin
GlobalEmailMessage.Subject := CopyStr(Subject, 1, MaxStrLen(GlobalEmailMessage.Subject));
end;
procedure SetSubject(Subject: Text)
begin
SetSubjectValue(Subject);
Modify();
end;
procedure IsBodyHTMLFormatted(): Boolean
begin
exit(GlobalEmailMessage."HTML Formatted Body");
end;
local procedure SetBodyHTMLFormattedValue(Value: Boolean)
begin
GlobalEmailMessage."HTML Formatted Body" := Value;
end;
procedure SetBodyHTMLFormatted(Value: Boolean)
begin
SetBodyHTMLFormattedValue(Value);
Modify();
end;
procedure AddHeader(HeaderName: Text; HeaderValue: Text)
begin
StoreHeader(HeaderName, HeaderValue, true);
end;
procedure SetHeader(HeaderName: Text; HeaderValue: Text)
begin
StoreHeader(HeaderName, HeaderValue, false);
end;
procedure GetHeader(HeaderName: Text; var Value: Text): Boolean
var
HeaderToken: JsonToken;
NormalizedName: Text;
begin
Value := '';
NormalizedName := NormalizeHeaderName(HeaderName);
if NormalizedName = '' then
exit(false);
EnsureHeadersLoaded();
if not GlobalHeadersJson.Get(NormalizedName, HeaderToken) then
exit(false);
if not HeaderToken.IsValue() then
exit(false);
Value := HeaderToken.AsValue().AsText();
exit(true);
end;
procedure FlushHeaders()
var
HeadersOutStream: OutStream;
HeadersText: Text;
begin
if not GlobalHeadersDirty then
exit;
Clear(GlobalEmailMessage."Message Headers");
if GlobalHeadersJson.Keys().Count() > 0 then begin
GlobalHeadersJson.WriteTo(HeadersText);
GlobalEmailMessage."Message Headers".CreateOutStream(HeadersOutStream, TextEncoding::UTF8);
HeadersOutStream.WriteText(HeadersText);
end;
Modify();
GlobalHeadersDirty := false;
end;
local procedure StoreHeader(HeaderName: Text; HeaderValue: Text; AppendIfPresent: Boolean)
var
ExistingToken: JsonToken;
NormalizedName: Text;
LineFeed: Text[1];
begin
NormalizedName := NormalizeHeaderName(HeaderName);
if NormalizedName = '' then
exit;
EnsureHeadersLoaded();
if GlobalHeadersJson.Get(NormalizedName, ExistingToken) then
if AppendIfPresent then begin
LineFeed[1] := 10;
GlobalHeadersJson.Replace(NormalizedName, ExistingToken.AsValue().AsText() + LineFeed + HeaderValue);
end else
GlobalHeadersJson.Replace(NormalizedName, HeaderValue)
else
GlobalHeadersJson.Add(NormalizedName, HeaderValue);
GlobalHeadersDirty := true;
end;
local procedure NormalizeHeaderName(HeaderName: Text): Text
begin
exit(LowerCase(DelChr(HeaderName, '<>', ' ')));
end;
local procedure EnsureHeadersLoaded()
var
HeadersInStream: InStream;
HeadersText: Text;
begin
if GlobalHeadersLoaded then
exit;
Clear(GlobalHeadersJson);
GlobalEmailMessage.CalcFields("Message Headers");
if GlobalEmailMessage."Message Headers".HasValue() then begin
GlobalEmailMessage."Message Headers".CreateInStream(HeadersInStream, TextEncoding::UTF8);
HeadersInStream.ReadText(HeadersText);
if HeadersText <> '' then
if not GlobalHeadersJson.ReadFrom(HeadersText) then
Clear(GlobalHeadersJson);
end;
GlobalHeadersLoaded := true;
end;
local procedure InvalidateHeadersCache()
begin
Clear(GlobalHeadersJson);
GlobalHeadersLoaded := false;
GlobalHeadersDirty := false;
end;
procedure IsRead(): Boolean
begin
exit(not GlobalEmailMessage.Editable);
end;
procedure GetContentTypeFromFilename(FileName: Text): Text[250]
begin
if FileName.EndsWith('.graphql') or FileName.EndsWith('.gql') then
exit('application/graphql');
if FileName.EndsWith('.js') then
exit('application/javascript');
if FileName.EndsWith('.json') then
exit('application/json');
if FileName.EndsWith('.doc') then
exit('application/msword(.doc)');
if FileName.EndsWith('.pdf') then
exit('application/pdf');
if FileName.EndsWith('.sql') then
exit('application/sql');
if FileName.EndsWith('.xls') then
exit('application/vnd.ms-excel(.xls)');
if FileName.EndsWith('.ppt') then
exit('application/vnd.ms-powerpoint(.ppt)');
if FileName.EndsWith('.odt') then
exit('application/vnd.oasis.opendocument.text(.odt)');
if FileName.EndsWith('.pptx') then
exit('application/vnd.openxmlformats-officedocument.presentationml.presentation(.pptx)');
if FileName.EndsWith('.xlsx') then
exit('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet(.xlsx)');
if FileName.EndsWith('.docx') then
exit('application/vnd.openxmlformats-officedocument.wordprocessingml.document(.docx)');
if FileName.EndsWith('.xml') then
exit('application/xml');
if FileName.EndsWith('.zip') then
exit('application/zip');
if FileName.EndsWith('.zst') then
exit('application/zstd(.zst)');
if FileName.EndsWith('.mpeg') then
exit('audio/mpeg');
if FileName.EndsWith('.ogg') then
exit('audio/ogg');
if FileName.EndsWith('.gif') then
exit('application/gif');
if FileName.EndsWith('.jpeg') then
exit('application/jpeg');
if FileName.EndsWith('.jpg') then
exit('application/jpg');
if FileName.EndsWith('.png') then
exit('application/png');
if FileName.EndsWith('.css') then
exit('text/css');
if FileName.EndsWith('.csv') then
exit('text/csv');
if FileName.EndsWith('.html') then
exit('text/html');
if FileName.EndsWith('.php') then
exit('text/php');
if FileName.EndsWith('.txt') then
exit('text/plain');
exit('');
end;
procedure AddAttachment(AttachmentName: Text[250]; ContentType: Text[250]; AttachmentBase64: Text)
var
EmailMessageAttachment: Record "Email Message Attachment";
Base64Convert: Codeunit "Base64 Convert";
TempBlob: Codeunit "Temp Blob";
AttachmentOutstream: OutStream;
AttachmentInStream: InStream;
NullGuid: Guid;
begin
AddAttachment(AttachmentName, ContentType, false, NullGuid, EmailMessageAttachment);
TempBlob.CreateOutStream(AttachmentOutstream);
Base64Convert.FromBase64(AttachmentBase64, AttachmentOutstream);
TempBlob.CreateInStream(AttachmentInStream);
InsertAttachment(EmailMessageAttachment, AttachmentInStream, AttachmentName);
end;
procedure AddAttachment(AttachmentName: Text[250]; ContentType: Text[250]; AttachmentInStream: InStream)
begin
AddAttachmentInternal(AttachmentName, ContentType, AttachmentInStream);
end;
procedure AddAttachment(AttachmentName: Text[250]; ContentType: Text[250]; AttachmentInStream: InStream; InLine: Boolean; ContentId: Text[40])
begin
AddAttachmentInternal(AttachmentName, ContentType, AttachmentInStream, InLine, ContentId);
end;
procedure AddAttachmentInternal(AttachmentName: Text[250]; ContentType: Text[250]; AttachmentInStream: InStream) Size: Integer
var
NullGuid: Guid;
begin
exit(AddAttachmentInternal(AttachmentName, ContentType, AttachmentInStream, false, NullGuid));
end;
procedure AddAttachmentInternal(AttachmentName: Text[250]; ContentType: Text[250]; AttachmentInStream: InStream; InLine: Boolean; ContentId: Text[40]) Size: Integer
var
EmailMessageAttachment: Record "Email Message Attachment";
begin
AddAttachment(AttachmentName, ContentType, InLine, ContentId, EmailMessageAttachment);
InsertAttachment(EmailMessageAttachment, AttachmentInStream, '');
exit(EmailMessageAttachment.Length);
end;
local procedure InsertAttachment(var EmailMessageAttachment: Record "Email Message Attachment"; AttachmentInStream: InStream; AttachmentName: Text)
var
MediaID: Guid;
begin
MediaID := EmailMessageAttachment.Data.ImportStream(AttachmentInStream, AttachmentName, EmailMessageAttachment."Content Type");
TenantMedia.Get(MediaID);
TenantMedia.CalcFields(Content);
EmailMessageAttachment.Length := TenantMedia.Content.Length;
EmailMessageAttachment.Insert();
Modify();
end;
procedure AddAttachmentsFromScenario(var EmailAttachments: Record "Email Attachments")
var
EmailMessageAttachment: Record "Email Message Attachment";
NullGuid: Guid;
ContentType: Text[250];
begin
if not EmailAttachments.FindSet() then
exit;
repeat
ContentType := GetContentTypeFromFilename(EmailAttachments."Attachment Name");
AddAttachment(EmailAttachments."Attachment Name", ContentType, false, NullGuid, EmailMessageAttachment);
InsertAttachmentsFromScenario(EmailMessageAttachment, EmailAttachments);
until EmailAttachments.Next() = 0;
end;
local procedure InsertAttachmentsFromScenario(EmailMessageAttachment: Record "Email Message Attachment"; EmailAttachments: Record "Email Attachments")
var
TempBlob: Codeunit "Temp Blob";
MediaOutStream: OutStream;
MediaInStream: InStream;
MediaID: Guid;
begin
TempBlob.CreateOutStream(MediaOutStream, TextEncoding::UTF8);
EmailAttachments."Email Attachment".ExportStream(MediaOutStream);
TempBlob.CreateInStream(MediaInStream, TextEncoding::UTF8);
EmailMessageAttachment.Data.ImportStream(MediaInStream, EmailAttachments."Attachment Name");
MediaID := EmailMessageAttachment.Data.MediaId();
TenantMedia.Get(MediaID);
TenantMedia.CalcFields(Content);
EmailMessageAttachment.Length := TenantMedia.Content.Length;
EmailMessageAttachment.Insert();
end;
local procedure ReplaceRgbaColorsWithRgb(var Body: Text)
var
RgbaRegexPattern: DotNet Regex;
begin
Body := RgbaRegexPattern.Replace(Body, RbgaPatternTok, RgbReplacementTok);
end;
local procedure AddAttachment(AttachmentName: Text[250]; ContentType: Text[250]; InLine: Boolean; ContentId: Text[40]; var EmailMessageAttachment: Record "Email Message Attachment")
begin
EmailMessageAttachment."Email Message Id" := GlobalEmailMessage.Id;
EmailMessageAttachment."Attachment Name" := AttachmentName;
EmailMessageAttachment."Content Type" := ContentType;
EmailMessageAttachment.InLine := InLine;
EmailMessageAttachment."Content Id" := ContentId;
end;
procedure GetRecipients(): List of [Text]
var
EmailRecipients: Record "Email Recipient";
begin
EmailRecipients.SetRange("Email Message Id", GlobalEmailMessage.Id);
exit(GetEmailAddressesOfRecipients(EmailRecipients));
end;
procedure GetRecipients(RecipientType: Enum "Email Recipient Type"): List of [Text]
var
EmailRecipients: Record "Email Recipient";
begin
EmailRecipients.SetRange("Email Message Id", GlobalEmailMessage.Id);
EmailRecipients.SetRange("Email Recipient Type", RecipientType);
exit(GetEmailAddressesOfRecipients(EmailRecipients));
end;
local procedure GetEmailAddressesOfRecipients(var EmailRecipients: Record "Email Recipient"): List of [Text]
var
Recipients: List of [Text];
begin
if EmailRecipients.FindSet() then
repeat
Recipients.Add(EmailRecipients."Email Address");
until EmailRecipients.Next() = 0;
exit(Recipients);
end;
procedure GetRecipientsAsText(RecipientType: Enum "Email Recipient Type"): Text
var
RecipientList: List of [Text];
Recipient, Result : Text;
begin
RecipientList := GetRecipients(RecipientType);
foreach Recipient in RecipientList do
Result += ';' + Recipient;
Result := Result.TrimStart(';'); // trim extra semicolon
exit(Result);
end;
procedure SetRecipients(RecipientType: Enum "Email Recipient Type"; RecipientsText: Text)
var
RecipientsList: List of [Text];
begin
RecipientsList := RecipientsText.Split(';');
SetRecipients(RecipientType, RecipientsList);
end;
procedure SetRecipients(RecipientType: Enum "Email Recipient Type"; Recipients: List of [Text])
var
EmailRecipientRecord: Record "Email Recipient";
UniqueRecipients: Dictionary of [Text, Text];
Recipient: Text;
begin
EmailRecipientRecord.SetRange("Email Message Id", GlobalEmailMessage.Id);
EmailRecipientRecord.SetRange("Email Recipient Type", RecipientType);
if not EmailRecipientRecord.IsEmpty() then
EmailRecipientRecord.DeleteAll();
foreach Recipient in Recipients do begin
Recipient := DelChr(Recipient, '<>'); // trim the whitespaces around
if Recipient <> '' then
if UniqueRecipients.Add(Recipient.ToLower(), Recipient) then begin // Set the recipient key to lowercase to prevent duplicates
EmailRecipientRecord.Init();
EmailRecipientRecord."Email Message Id" := GlobalEmailMessage.Id;
EmailRecipientRecord."Email Recipient Type" := RecipientType;
EmailRecipientRecord."Email Address" := CopyStr(Recipient, 1, MaxStrLen(EmailRecipientRecord."Email Address"));
EmailRecipientRecord.Insert();
end;
end;
Modify();
end;
procedure AddRecipient(RecipientType: Enum "Email Recipient Type"; Recipient: Text)
var
Recipients: List of [Text];
begin
Recipient := DelChr(Recipient, '<>'); // trim the whitespaces around
if Recipient = '' then
exit;
Recipient := Recipient.ToLower();
Recipients := GetRecipients(RecipientType);
if Recipients.Contains(Recipient) then
exit;
Recipients.Add(Recipient);
SetRecipients(RecipientType, Recipients);
end;
procedure Attachments_DeleteContent(): Boolean
begin
exit(Attachments_DeleteContent(false));
end;
procedure Attachments_DeleteContent(BypassSentCheck: Boolean): Boolean
var
EmailAttachmentsImpl: Codeunit "Email Attachments Impl";
MediaId: Guid;
CustomDimensions: Dictionary of [Text, Text];
begin
MediaId := GlobalEmailMessageAttachment.Data.MediaId();
TenantMedia.Get(MediaId);
Clear(TenantMedia.Content);
TenantMedia.Modify();
if BypassSentCheck then
BindSubscription(EmailAttachmentsImpl);
Modify();
GlobalEmailMessageAttachment.Length := 0;
GlobalEmailMessageAttachment.Modify();
if BypassSentCheck then begin
CustomDimensions.Add('category', EmailCategoryLbl);
CustomDimensions.Add('MessageId', Format(GlobalEmailMessageAttachment."Email Message Id"));
Telemetry.LogMessage('0000LD1', TelemetryEmailMessageAttachmentContentDeletedMsg, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::All, CustomDimensions);
UnbindSubscription(EmailAttachmentsImpl);
end;
exit(not TenantMedia.Content.HasValue());
end;
procedure Attachments_Delete() Result: Boolean
begin
exit(Attachments_Delete(false));
end;
procedure Attachments_Delete(BypassSentCheck: Boolean) Result: Boolean
var
EmailMessageAttachment: Record "Email Message Attachment";
EmailAttachmentsImpl: Codeunit "Email Attachments Impl";
CustomDimensions: Dictionary of [Text, Text];
begin
if BypassSentCheck then
BindSubscription(EmailAttachmentsImpl);
EmailMessageAttachment.Get(GlobalEmailMessageAttachment.Id);
Result := EmailMessageAttachment.Delete();
if BypassSentCheck then begin
CustomDimensions.Add('category', EmailCategoryLbl);
CustomDimensions.Add('MessageId', Format(EmailMessageAttachment."Email Message Id"));
Telemetry.LogMessage('0000LD2', TelemetryEmailMessageAttachmentDeletedMsg, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::All, CustomDimensions);
UnbindSubscription(EmailAttachmentsImpl);
end;
end;
procedure Attachments_First(): Boolean
begin
GlobalEmailMessageAttachment.SetRange("Email Message Id", GlobalEmailMessage.Id);
exit(GlobalEmailMessageAttachment.FindFirst());
end;
procedure Attachments_Next(): Integer
begin
GlobalEmailMessageAttachment.SetRange("Email Message Id", GlobalEmailMessage.Id);
exit(GlobalEmailMessageAttachment.Next());
end;
procedure Attachments_GetId(): BigInteger
begin
exit(GlobalEmailMessageAttachment.Id);
end;
procedure Attachments_GetName(): Text[250]
begin
exit(GlobalEmailMessageAttachment."Attachment Name");
end;
procedure Attachments_GetContent(var InStream: InStream)
var
EmailMessage: Codeunit "Email Message";
MediaID: Guid;
Handled: Boolean;
begin
MediaID := GlobalEmailMessageAttachment.Data.MediaId();
TenantMedia.Get(MediaID);
TenantMedia.CalcFields(Content);
if TenantMedia.Content.HasValue() then
TenantMedia.Content.CreateInStream(InStream)
else begin
EmailMessage.OnGetAttachmentContent(MediaID, InStream, Handled);
if not Handled then
Error(EmailMessageGetAttachmentContentErr);
end;
end;
procedure Attachments_GetContentBase64(): Text
var
Base64Convert: Codeunit "Base64 Convert";
InStream: InStream;
begin
Attachments_GetContent(InStream);
exit(Base64Convert.ToBase64(InStream));
end;
procedure Attachments_GetContentType(): Text[250]
begin
exit(GlobalEmailMessageAttachment."Content Type");
end;
procedure Attachments_GetContentId(): Text[40]
begin
exit(GlobalEmailMessageAttachment."Content Id");
end;
procedure Attachments_IsInline(): Boolean
begin
exit(GlobalEmailMessageAttachment.InLine);
end;
procedure Attachments_GetLength(): Integer
begin
exit(GlobalEmailMessageAttachment.Length);
end;
procedure GetRelatedAttachments(var EmailRelatedAttachments: Record "Email Related Attachment"): Boolean
begin
exit(GetRelatedAttachments(GlobalEmailMessage.Id, EmailRelatedAttachments));
end;
procedure GetRelatedAttachments(EmailMessageId: Guid; var EmailRelatedAttachmentOut: Record "Email Related Attachment"): Boolean
var
TempEmailRelatedAttachment: Record "Email Related Attachment";
EmailRelatedRecord: Record "Email Related Record";
Email: Codeunit Email;
EmailImpl: Codeunit "Email Impl";
begin
EmailRelatedRecord.SetRange("Email Message Id", EmailMessageId);
EmailImpl.FilterRemovedSourceRecords(EmailRelatedRecord);
if not EmailRelatedRecord.FindSet() then
exit(false);
repeat
Email.OnFindRelatedAttachments(EmailRelatedRecord."Table Id", EmailRelatedRecord."System Id", TempEmailRelatedAttachment);
if TempEmailRelatedAttachment.FindSet() then
InsertRelatedAttachments(EmailRelatedRecord."Table Id", EmailRelatedRecord."System Id", TempEmailRelatedAttachment, EmailRelatedAttachmentOut);
TempEmailRelatedAttachment.DeleteAll();
until EmailRelatedRecord.Next() = 0;
exit(true);
end;
procedure GetId(): Guid
begin
exit(GlobalEmailMessage.Id);
end;
procedure Get(MessageId: Guid): Boolean
begin
Clear(GlobalEmailMessageAttachment);
InvalidateHeadersCache();
exit(GlobalEmailMessage.Get(MessageId));
end;
procedure DeleteOrphanedMessages(StartMessageId: Guid; MessagesToIterate: Integer) NextMessageId: Guid
var
EmailMessage: Record "Email Message";
EmptyGuid: Guid;
MessageNo: Integer;
begin
EmailMessage.SetLoadFields(Id);
EmailMessage.ReadIsolation(IsolationLevel::ReadCommitted);
EmailMessage.SetFilter(Id, '>=%1', StartMessageId);
if not EmailMessage.FindSet() then
exit(EmptyGuid);
for MessageNo := 1 to MessagesToIterate do begin
DeleteEmailMessageIfOrphaned(EmailMessage);
if EmailMessage.Next() = 0 then
exit(EmptyGuid);
end;
exit(EmailMessage.Id);
end;
local procedure DeleteEmailMessageIfOrphaned(var EmailMessage: Record "Email Message")
var
EmailOutbox: Record "Email Outbox";
SentEmail: Record "Sent Email";
begin
EmailOutbox.SetRange("Message Id", EmailMessage.Id);
if not EmailOutbox.IsEmpty() then
exit;
SentEmail.SetRange("Message Id", EmailMessage.Id);
if not SentEmail.IsEmpty() then
exit;
EmailMessage.Delete();
end;
procedure DeleteEmailRecipientsIfOrphaned(StartMessageId: Guid; RecipientsToIterate: Integer) NextMessageId: Guid
var
EmailRecipients: Record "Email Recipient";
OrphanedDict: Dictionary of [Guid, Boolean];
EmptyGuid: Guid;
RecipientIdx: Integer;
begin
EmailRecipients.SetLoadFields("Email Message Id");
EmailRecipients.ReadIsolation(IsolationLevel::ReadCommitted);
EmailRecipients.SetFilter("Email Message Id", '>=%1', StartMessageId);
if not EmailRecipients.FindSet() then
exit;
for RecipientIdx := 1 to RecipientsToIterate do begin
if IsEmailRecipientOrphaned(OrphanedDict, EmailRecipients) then
EmailRecipients.Delete();
if EmailRecipients.Next() = 0 then
exit(EmptyGuid);
end;
exit(EmailRecipients."Email Message Id");
end;
local procedure IsEmailRecipientOrphaned(var OrphanedDict: Dictionary of [Guid, Boolean]; var EmailRecipient: Record "Email Recipient"): Boolean
var
EmailMessage: Record "Email Message";
begin
if OrphanedDict.ContainsKey(EmailRecipient."Email Message Id") then
exit(OrphanedDict.Get(EmailRecipient."Email Message Id"));
EmailMessage.SetRange(Id, EmailRecipient."Email Message Id");
if EmailMessage.IsEmpty() then begin
OrphanedDict.Add(EmailRecipient."Email Message Id", true);
exit(true);
end;
OrphanedDict.Add(EmailRecipient."Email Message Id", false);
exit(false);
end;
procedure ValidateRecipients()
var
EmailAccount: Codeunit "Email Account";
Recipients: List of [Text];
Recipient: Text;
begin
Recipients := GetRecipients();
if Recipients.Count() = 0 then
Error(NoAccountErr);
foreach Recipient in Recipients do
EmailAccount.ValidateEmailAddress(Recipient, false);
end;
procedure GetNoOfModifies(): Integer
begin
exit(GlobalEmailMessage."No. of Modifies");
end;
local procedure InsertRelatedAttachments(TableID: Integer; SystemID: Guid; var EmailRelatedAttachment2: Record "Email Related Attachment"; var EmailRelatedAttachment: Record "Email Related Attachment")
var
RecordRef: RecordRef;
begin
RecordRef.Open(TableID);
if not RecordRef.GetBySystemId(SystemID) then begin
Session.LogMessage('0000CTZ', StrSubstNo(RecordNotFoundMsg, TableID), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', EmailCategoryLbl);
exit;
end;
repeat
EmailRelatedAttachment.Copy(EmailRelatedAttachment2);
EmailRelatedAttachment."Attachment Source" := CopyStr(Format(RecordRef.RecordId(), 0, 1), 1, MaxStrLen(EmailRelatedAttachment."Attachment Source"));
EmailRelatedAttachment.Insert();
until EmailRelatedAttachment2.Next() = 0;
end;
// Used for formatting a filesize in KB or MB (only)
internal procedure FormatFileSize(SizeInBytes: Integer): Text
var
FileSizeConverted: Decimal;
FileSizeUnit: Text;
begin
FileSizeConverted := SizeInBytes / 1024; // The smallest size we show is KB
if FileSizeConverted < 1024 then
FileSizeUnit := 'KB'
else begin
FileSizeConverted := FileSizeConverted / 1024; // The largest size we show is MB
FileSizeUnit := 'MB'
end;
exit(StrSubstNo(FileSizeTxt, Round(FileSizeConverted, 1, '>'), FileSizeUnit));
end;
[InternalEvent(false)]
local procedure OnBeforeDeleteSentEmailAttachment(var BypassSentCheck: Boolean)
begin
end;
[EventSubscriber(ObjectType::Table, Database::"Email Inbox", OnBeforeInsertEvent, '', false, false)]
local procedure OnBeforeInsertEventEmailInbox(var Rec: Record "Email Inbox"; RunTrigger: Boolean)
begin
if Rec.IsTemporary() then
exit;
Rec."User Security Id" := UserSecurityId();
end;
[EventSubscriber(ObjectType::Table, Database::"Email Inbox", OnAfterDeleteEvent, '', false, false)]
local procedure OnAfterDeleteEmailInbox(var Rec: Record "Email Inbox"; RunTrigger: Boolean)
var
EmailMessage: Record "Email Message";
begin
if Rec.IsTemporary() then
exit;
if EmailMessage.Get(Rec."Message Id") then
EmailMessage.Delete();
end;
[EventSubscriber(ObjectType::Table, Database::"Sent Email", OnAfterDeleteEvent, '', false, false)]
local procedure OnAfterDeleteSentEmail(var Rec: Record "Sent Email"; RunTrigger: Boolean)
var
EmailMessage: Record "Email Message";
EmailOutbox: Record "Email Outbox";
SentEmail: Record "Sent Email";
begin
if Rec.IsTemporary() then
exit;
EmailOutbox.SetRange("Message Id", Rec."Message Id");
if not EmailOutbox.IsEmpty() then
exit;
SentEmail.SetRange("Message Id", Rec."Message Id");
if not SentEmail.IsEmpty() then
exit;
if EmailMessage.Get(Rec."Message Id") then
EmailMessage.Delete();
end;
[EventSubscriber(ObjectType::Table, Database::"Email Outbox", OnAfterDeleteEvent, '', false, false)]
local procedure OnAfterDeleteEmailOutbox(var Rec: Record "Email Outbox"; RunTrigger: Boolean)
var
SentEmail: Record "Sent Email";
EmailOutbox: Record "Email Outbox";
EmailMessage: Record "Email Message";
EmailError: Record "Email Error";
begin
if Rec.IsTemporary() then
exit;
EmailError.SetRange("Outbox Id", Rec.Id);
EmailError.DeleteAll(true);
SentEmail.SetRange("Message Id", Rec."Message Id");
if not SentEmail.IsEmpty() then
exit;
EmailOutbox.SetRange("Message Id", Rec."Message Id");
if not EmailOutbox.IsEmpty() then
exit;
if EmailMessage.Get(Rec."Message Id") then
EmailMessage.Delete();
end;
[EventSubscriber(ObjectType::Table, Database::"Email Message", OnBeforeDeleteEvent, '', false, false)]
local procedure OnBeforeDeleteEmailMessage(var Rec: Record "Email Message"; RunTrigger: Boolean)
var
EmailMessageAttachment: Record "Email Message Attachment";
EmailRecipient: Record "Email Recipient";
EmailRelatedRecord: Record "Email Related Record";
begin
if Rec.IsTemporary() then
exit;
EmailMessageAttachment.SetRange("Email Message Id", Rec.Id);
EmailMessageAttachment.DeleteAll();
EmailRecipient.SetRange("Email Message Id", Rec.Id);
EmailRecipient.DeleteAll();
EmailRelatedRecord.SetRange("Email Message Id", Rec.Id);
EmailRelatedRecord.DeleteAll();
end;
[EventSubscriber(ObjectType::Table, Database::"Email Message", OnBeforeModifyEvent, '', false, false)]
local procedure OnBeforeModifyEmailMessage(var Rec: Record "Email Message"; var xRec: Record "Email Message"; RunTrigger: Boolean)
var
EmailInbox: Record "Email Inbox";
EmailOutbox: Record "Email Outbox";
EmailMessageOld: Record "Email Message";
BypassSentCheck: Boolean;
begin
if Rec.IsTemporary() then
exit;
EmailOutbox.SetRange("Message Id", Rec.Id);
EmailOutbox.SetFilter(Status, '%1|%2', EmailOutbox.Status::Queued, EmailOutbox.Status::Processing);
if not EmailOutbox.IsEmpty() then
Error(EmailMessageQueuedCannotModifyErr);
EmailInbox.SetRange("Message Id", Rec.Id);
if not EmailInbox.IsEmpty() then
Error(EmailMessageRetrievedCannotModifyErr);
OnBeforeDeleteSentEmailAttachment(BypassSentCheck);
if EmailMessageOld.Get(Rec.Id) and (not EmailMessageOld.Editable) and (not BypassSentCheck) then
Error(EmailMessageSentCannotModifyErr);
Rec."No. of Modifies" += 1;
end;
[EventSubscriber(ObjectType::Table, Database::"Email Message Attachment", OnBeforeDeleteEvent, '', false, false)]
local procedure OnBeforeDeleteAttachment(var Rec: Record "Email Message Attachment")
var
EmailOutbox: Record "Email Outbox";
SentEmail: Record "Sent Email";
BypassSentCheck: Boolean;
begin
if Rec.IsTemporary() then
exit;
EmailOutbox.SetRange("Message Id", Rec."Email Message Id");
EmailOutbox.SetFilter(Status, '%1|%2', EmailOutbox.Status::Queued, EmailOutbox.Status::Processing);
if not EmailOutbox.IsEmpty() then
Error(EmailMessageQueuedCannotDeleteAttachmentErr);
OnBeforeDeleteSentEmailAttachment(BypassSentCheck);
SentEmail.SetRange("Message Id", Rec."Email Message Id");
if not BypassSentCheck then
if not SentEmail.IsEmpty() then
Error(EmailMessageSentCannotDeleteAttachmentErr);
end;
[EventSubscriber(ObjectType::Table, Database::"Email Recipient", OnBeforeDeleteEvent, '', false, false)]
local procedure OnBeforeDeleteRecipient(var Rec: Record "Email Recipient")
var
EmailOutbox: Record "Email Outbox";
SentEmail: Record "Sent Email";
begin
if Rec.IsTemporary() then
exit;
EmailOutbox.SetRange("Message Id", Rec."Email Message Id");
EmailOutbox.SetFilter(Status, '%1|%2', EmailOutbox.Status::Queued, EmailOutbox.Status::Processing);
if not EmailOutbox.IsEmpty() then
Error(EmailMessageQueuedCannotDeleteRecipientErr);
SentEmail.SetRange("Message Id", Rec."Email Message Id");
if not SentEmail.IsEmpty() then
Error(EmailMessageSentCannotDeleteRecipientErr);
end;
[EventSubscriber(ObjectType::Table, Database::"Email Message Attachment", OnBeforeInsertEvent, '', false, false)]
local procedure OnBeforeInsertAttachment(var Rec: Record "Email Message Attachment")
var
EmailOutbox: Record "Email Outbox";
SentEmail: Record "Sent Email";
begin
if Rec.IsTemporary then
exit;
EmailOutbox.SetRange("Message Id", Rec."Email Message Id");
EmailOutbox.SetFilter(Status, '%1|%2', EmailOutbox.Status::Queued, EmailOutbox.Status::Processing);
if not EmailOutbox.IsEmpty() then
Error(EmailMessageQueuedCannotInsertAttachmentErr);
SentEmail.SetRange("Message Id", Rec."Email Message Id");
if not SentEmail.IsEmpty() then
Error(EmailMessageSentCannotInsertAttachmentErr);
end;
[EventSubscriber(ObjectType::Table, Database::"Email Recipient", OnBeforeInsertEvent, '', false, false)]
local procedure OnBeforeInsertRecipient(var Rec: Record "Email Recipient")
var
EmailOutbox: Record "Email Outbox";
SentEmail: Record "Sent Email";
begin
if Rec.IsTemporary() then
exit;
EmailOutbox.SetRange("Message Id", Rec."Email Message Id");
EmailOutbox.SetFilter(Status, '%1|%2', EmailOutbox.Status::Queued, EmailOutbox.Status::Processing);
if not EmailOutbox.IsEmpty() then
Error(EmailMessageQueuedCannotInsertRecipientErr);
SentEmail.SetRange("Message Id", Rec."Email Message Id");
if not SentEmail.IsEmpty() then
Error(EmailMessageSentCannotInsertRecipientErr);
end;
procedure MarkAsRead()
begin
if GlobalEmailMessage.Editable then begin
GlobalEmailMessage.Editable := false;
GlobalEmailMessage.Modify();
end;
end;
procedure GetEmailMessage(var EmailMessage: Record "Email Message")
begin
EmailMessage := GlobalEmailMessage;
end;
var
GlobalEmailMessage: Record "Email Message";
GlobalEmailMessageAttachment: Record "Email Message Attachment";
TenantMedia: Record "Tenant Media";
Telemetry: Codeunit Telemetry;
GlobalHeadersJson: JsonObject;
GlobalHeadersLoaded: Boolean;
GlobalHeadersDirty: Boolean;
EmailCategoryLbl: Label 'Email', Locked = true;
EmailMessageQueuedCannotModifyErr: Label 'Cannot edit the email because it has been queued to be sent.';
EmailMessageSentCannotModifyErr: Label 'Cannot edit the message because it has already been sent.';
EmailMessageRetrievedCannotModifyErr: Label 'Cannot edit the message because it is from an external source.';
EmailMessageQueuedCannotDeleteAttachmentErr: Label 'Cannot delete the attachment because the email has been queued to be sent.';
EmailMessageSentCannotDeleteAttachmentErr: Label 'Cannot delete the attachment because the email has already been sent.';
EmailMessageQueuedCannotInsertAttachmentErr: Label 'Cannot add the attachment because the email is queued to be sent.';
EmailMessageSentCannotInsertAttachmentErr: Label 'Cannot add the attachment because the email has already been sent.';
EmailMessageQueuedCannotDeleteRecipientErr: Label 'Cannot delete the recipient because the email is queued to be sent.';
EmailMessageSentCannotDeleteRecipientErr: Label 'Cannot delete the recipient because the email has already been sent.';
EmailMessageQueuedCannotInsertRecipientErr: Label 'Cannot add a recipient because the email is queued to be sent.';
EmailMessageSentCannotInsertRecipientErr: Label 'Cannot add the recipient because the email has already been sent.';
EmailMessageGetAttachmentContentErr: Label 'The attachment content was not found.';
TelemetryEmailMessageAttachmentDeletedMsg: Label 'Email message attachment has been deleted.', Locked = true;
TelemetryEmailMessageAttachmentContentDeletedMsg: Label 'Email message attachment content has been deleted.', Locked = true;
NoAccountErr: Label 'You must specify a valid email account to send the message to.';
RecordNotFoundMsg: Label 'Record not found in table: %1', Comment = '%1 - File size', Locked = true;
RgbReplacementTok: Label 'rgb($1, $2, $3)', Locked = true;
RbgaPatternTok: Label 'rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*1(\.0{0,2})?\)', Locked = true;
FileSizeTxt: Label '%1 %2', Comment = '%1 = File Size, %2 = Unit of measurement', Locked = true;
}