Help! My email code is obsolete!

In version 17, the old and much beloved SMTP Mail codeunit is being marked obsolete. In this video, I’ll show how to replace the obsoleted code with fresh new functionality.

https://youtu.be/7oXVwPiU1mg

In this video, Erik walks through how to update your AL email code in Business Central when the familiar SMTP Mail codeunit becomes obsolete. Microsoft is retiring the old SMTP-based email functionality and replacing it with a new, more capable email framework. Erik demonstrates the migration step by step, showing how to replace the old code with the new Email Message and Email codeunits.

The Problem: Obsolete SMTP Mail Code

If you’ve been working with AL extensions in Business Central, you may have noticed Visual Studio Code suddenly showing warnings about obsolete code. Specifically, starting with platform version 17.0, Microsoft has marked the SMTP mail functionality for removal. When you compile your project, you’ll see warnings like:

  • Table “SMTP Mail Setup” is marked for removal — “Move to Email SMTP app”
  • Codeunit “SMTP Mail” is marked for removal — “Replaced with Email Message codeunit from System Application”

The obsolete messages themselves have some quirks (including what Erik noted as a typo or two), but the intent is clear: stop using the old SMTP codeunit and switch to the new email framework.

The Old Way: Using SMTP Mail

Here’s the legacy approach that many extensions use. It relies on the SMTP Mail Setup record and the SMTP Mail codeunit to compose and send emails:

procedure sendemail()
var
    SMTPSetup: Record "SMTP Mail Setup";
    mail: Codeunit "SMTP Mail";
    TempBlob: Codeunit "Temp Blob";
    attach1: InStream;
    addressTo: List of [Text];
    subjectTxt: Text;
begin
    SMTPSetup.get();
    SMTPSetup.TestField("User ID");

    addressTo.Add('homey@home.com');
    addressTo.Add('demo@example.com');

    subjectTxt := 'Important message inside, please open!';
    mail.CreateMessage('CEO of Bank of Spain', SMTPSetup."User ID", addressTo, subjectTxt, '');
    mailBody(mail);

    Clear(TempBlob);
    SaveDocumentAsPDFToStream(TempBlob, 50140);
    TempBlob.CreateInStream(attach1);
    mail.AddAttachmentStream(attach1, 'a cheque for you.pdf');

    mail.Send();
end;

This pattern is straightforward: create a message with recipients, subject, and body; optionally add attachments from streams; then call Send(). The SMTP Mail Setup record provides the sender’s user ID and SMTP server configuration.

The New Way: Email Message + Email Codeunits

The key insight is that the old single SMTP Mail codeunit has been split into two separate codeunits:

  1. Email Message — responsible for composing the email (recipients, subject, body, attachments)
  2. Email — responsible for sending the email (and other actions like saving as draft, opening in editor, enqueueing)

Here’s the updated code:

procedure NewSendMail()
var
    mail: Codeunit "Email Message";
    email: Codeunit Email;
    InS: InStream;
    base64: Codeunit "Base64 Convert";
begin
    mail.Create('hello@example.com', 'The is the subject', 'here''s the body');
    mail.AddAttachment('open this.pdf', 'application/pdf', base64.ToBase64(InS));
    email.Send(mail);
end;

Step-by-Step Breakdown

1. Create the Message

The Email Message codeunit has a Create method that takes three parameters: recipients, subject, and body. This replaces the old CreateMessage call:

mail.Create('hello@example.com', 'The is the subject', 'here''s the body');

2. Add Attachments

The AddAttachment method on the new codeunit takes a filename, a content type, and a Base64-encoded string (not a stream directly). If you have your report saved to an InStream, you need to convert it using the Base64 Convert codeunit:

mail.AddAttachment('open this.pdf', 'application/pdf', base64.ToBase64(InS));

This is a notable difference from the old approach where you could pass a stream directly via AddAttachmentStream. Now you need the extra Base64 encoding step.

3. Send the Email

The Email codeunit handles the actual sending. You simply pass your composed Email Message to the Send method:

email.Send(mail);

The Email codeunit also exposes other useful methods that go beyond what SMTP could offer:

  • Enqueue — queue the email for later sending
  • SaveAsDraft — save the email as a draft
  • OpenInEditor / OpenInEditorModally — open the email in an editor for the user to review before sending

Why the Change? SMTP vs. Higher-Level Email

Erik explains the architectural reasoning behind this change well: SMTP is purely a transport protocol. It moves the body of an email from point A to point B — that’s it. SMTP has no concept of sent folders, drafts, or any of the features we associate with modern email clients.

With Business Central’s deeper integration into Office 365, Microsoft wants to work at a higher abstraction level. The new email framework is aware of concepts like drafts, sent folders, and email accounts configured through different connectors (SMTP being just one option). This means you can configure email accounts from the “Email Accounts” page in Business Central, and your code doesn’t need to care whether the underlying transport is SMTP, Microsoft Graph API, or something else entirely.

Codeunits as Objects

Erik also points out an interesting pattern that’s becoming more common in Business Central AL development: using codeunits as variables that carry state. In the old NAV world, codeunits were rarely used this way. But in the new email framework, the Email Message codeunit is essentially used as an object — you create it, populate it with data (recipients, body, attachments), and then pass it to the Email codeunit for sending. This is the closest thing AL has to object-oriented patterns, and it’s a paradigm worth getting comfortable with.

The Complete Source File

Here’s the full source code showing both the old (obsolete) approach and the new approach side by side:

pageextension 50100 CustomerListExt extends "Customer List"
{
    procedure CalcAvgUnitCost(Item: Record Item; Location: Record Location): decimal
    var
        ILE: Record "Item Ledger Entry";
        Q: Decimal;
        Total: Decimal;
    begin
        ILE.setrange("Item No.", Item."No.");
        ILE.setrange("Location Code", Location.Code);
        ILE.setrange(Open, true);
        ILE.SetCurrentKey("Item No.", "Location Code", Open);
        if ILE.findset() then
            repeat
                ILE.CalcFields("Cost Amount (Actual)", "Cost Amount (Expected)");
                if ILE."Cost Amount (Actual)" <> 0 then begin
                    Total += ILE."Cost Amount (Actual)" / ILE.Quantity * ILE."Remaining Quantity";
                    Q += ILE."Remaining Quantity";
                end else
                    if ILE."Cost Amount (Expected)" <> 0 then begin
                        Total += ILE."Cost Amount (Expected)" / ILE.Quantity * ILE."Remaining Quantity";
                        Q += ILE."Remaining Quantity";
                    end;
            until ILE.Next() = 0;
        if Q <> 0 then
            exit(Total / Q);
    end;

    // NEW approach - no obsolete warnings
    procedure NewSendMail()
    var
        mail: Codeunit "Email Message";
        email: Codeunit Email;
        InS: InStream;
        base64: Codeunit "Base64 Convert";
    begin
        mail.Create('hello@example.com', 'The is the subject', 'here''s the body');
        mail.AddAttachment('open this.pdf', 'application/pdf', base64.ToBase64(InS));
        email.Send(mail);
    end;

    // OLD approach - generates obsolete warnings
    procedure sendemail()
    var
        SMTPSetup: Record "SMTP Mail Setup";
        mail: Codeunit "SMTP Mail";
        TempBlob: Codeunit "Temp Blob";
        attach1: InStream;
        addressTo: List of [Text];
        subjectTxt: Text;
    begin
        SMTPSetup.get();
        SMTPSetup.TestField("User ID");

        addressTo.Add('homey@home.com');
        addressTo.Add('demo@example.com');

        subjectTxt := 'Important message inside, please open!';
        mail.CreateMessage('CEO of Bank of Spain', SMTPSetup."User ID", addressTo, subjectTxt, '');
        mailBody(mail);

        Clear(TempBlob);
        SaveDocumentAsPDFToStream(TempBlob, 50140);
        TempBlob.CreateInStream(attach1);
        mail.AddAttachmentStream(attach1, 'a cheque for you.pdf');

        mail.Send();
    end;

    procedure mailBody(var mail: Codeunit "SMTP Mail"): Text
    begin
        mail.AppendBody('I am Santiago Sebastian, a Spanish');
        mail.AppendBody('politician/investor and former Senior');
        mail.AppendBody('Director with Bank of Spain, National Central');
        mail.AppendBody('Bank of Spain. I am interested in investing the');
        mail.AppendBody('sum of US$12.7Million in your Country or any');
        mail.AppendBody('other place in the world under your custody.');
        mail.AppendBody('Please if you are interested in this project,');
        mail.AppendBody('kindly reply me and lets move forward.');
    end;

    procedure SaveDocumentAsPDFToStream(var TempBlob: Codeunit "Temp Blob"; ReportID: integer): Boolean;
    var
        DocumentRef: RecordRef;
        VarOutStream: OutStream;
    begin
        DocumentRef.Open(18);
        TempBlob.CreateOutStream(VarOutStream);
        if Report.SaveAs(ReportID, '', ReportFormat::Pdf, VarOutStream, DocumentRef) then
            exit(true)
        else
            Error('Could not save Report: %1 as PDF attachment', ReportID);
    end;
}

Summary

Migrating from the obsolete SMTP Mail codeunit to the new email framework in Business Central is straightforward once you understand the pattern:

  • Replace Codeunit "SMTP Mail" with two codeunits: Codeunit "Email Message" for composing and Codeunit Email for sending.
  • Use Email Message.Create() instead of SMTP Mail.CreateMessage().
  • Use Email Message.AddAttachment() with Base64-encoded content instead of SMTP Mail.AddAttachmentStream().
  • Use Email.Send() and pass the message codeunit as a parameter.
  • You no longer need the SMTP Mail Setup record — email accounts are now configured through the Email Accounts page in Business Central.

The SMTP functionality was obsoleted in version 17, so it will be removed in a future release. The sooner you migrate your email code, the better prepared you’ll be. The new framework also opens up capabilities beyond basic SMTP — including draft management, editor integration, and support for multiple email connectors.