PPowerApps UI
ComponentsLayoutsTemplatesAbout
P
PowerApps UI
Open Source

Production-ready UI components and patterns for Power Apps developers.

MIT License

Product

  • Components
  • Layouts
  • Templates
  • Starter Kits
  • About
  • Cheat Sheet
  • Documentation

Developers

  • Getting Started
  • Suggest Idea
  • Submit Component
  • Report Issues

Resources

  • Changelog
  • Newsletter
  • Best Practices
  • Design System
© 2026 PowerApps UI. Open source under MIT License.
Built byRodas Yonass
Privacy Policy•Terms of Service•MIT License
ComponentsSend Email

Send Email

A self-contained compose dialog. Recipients come from a directory you supply, addresses are de-duplicated and cleaned, the note is capped and escaped, and every field is handed back to you in OnSend. It owns no connector, so you choose Outlook, a flow, or SMTP.

cmpSendEmail.yaml
  1. 1. Go to the Components tab (right side panel, next to Screens)
  2. 2. Click New component — this creates a blank component
  3. 3. Click outside the new component to deselect it
  4. 4. Paste the YAML with Ctrl+V / ⌘V

This component requires Modern controls to be enabled. Settings → Updates → Preview → Modern controls and themes

Preview

Theme:
Review requested
REQ-2026-000123
TO   (REQUIRED)
Ava Chen, Marcus Hale
2 recipients
SUBJECT
MESSAGE
106 / 2000

Play

Scenario
Accent
Scenario maps to Title / ContextLine / DefaultSubject; the toggles map to Config.

A compose-email dialog for Canvas apps: directory-backed To and CC pickers, priority tags, a lockable subject, a note with a character counter, and a finished HTML email body ready to send.

Owns no connector. The component queries nothing and sends nothing. It builds every output and raises OnSend, and the host decides what sending means. That is what lets it drop into any app and work immediately against the sample directory.


What it does not do

  • It does not send. OnSend hands you ToAddresses, Subject and Body.
  • It does not search. Directory is an input. The component exposes what the sender typed as SearchText and leaves the query, the connector and the minimum-character rule to you.
  • It does not own the busy state. You set Busy while your send runs, because only you know when it finished.

Install

  1. Components → New component → Import from code
  2. Paste cmpSendEmail.yaml
  3. Add it to a screen and set Visible to your open flag

Three things a paste does not carry, all Power Apps behaviour rather than anything about this component:

  • Set the instance Width by hand. A pasted instance arrives at 600 regardless of the property. Height survives; Width does not.
  • Pass Config explicitly if you are customising it. A Record input's default does not reach a pasted instance.
  • Modern controls must be enabled. The two pickers are ModernCombobox.

Populating the directory

Directory is an ordinary table input, so both patterns work with no change to the component. Pick based on how big your tenant is.

Preloaded, for a directory you can hold in memory

// App.OnStart
ClearCollect(
    colDirectory,
    Office365Users.SearchUser({ searchTerm: "", top: 500 })
);

// Instance
Directory: =colDirectory

One call at startup, instant filtering after that, and the picker works offline once loaded. Right for a few hundred people.

Live search, for a directory you cannot

Directory: |-
  =Office365Users.SearchUser(
      { searchTerm: Trim(cmpSendEmail_1.SearchText), top: 50 }
  )

SearchUser returns DisplayName, Mail and JobTitle among others, so it is a superset of what Directory expects and drops straight in with no projection.

A blank searchTerm returns a default set rather than erroring, so the picker opens populated and narrows as the sender types. If you would rather not spend a call on an empty box:

Directory: |-
  =If(
      Len(Trim(cmpSendEmail_1.SearchText)) >= 3,
      Office365Users.SearchUser(
          { searchTerm: Trim(cmpSendEmail_1.SearchText), top: 50 }
      )
  )

Suggestions when idle, directory when typing

The version worth building for a real app:

Directory: |-
  =If(
      IsBlank(Trim(cmpSendEmail_1.SearchText)),
      colRecentRecipients,
      Office365Users.SearchUser(
          { searchTerm: Trim(cmpSendEmail_1.SearchText), top: 50 }
      )
  )

Build colRecentRecipients from your own audit rows. The common case becomes one tap with no connector call at all.

What to know about SearchUser

  • DelayOutput is already set on both pickers, so SearchText updates when the sender pauses rather than on every keystroke. Without it you would fire one call per character against a connector that throttles per user per connection.
  • It is prefix-oriented, not full text. Typing a surname often returns nothing, which senders read as a broken picker rather than as no match.
  • It returns unlicensed accounts and mail contacts with a blank Mail. The dialog counts these and warns before the send, and drops them from ToAddresses. You will see them far more against a real tenant than against the sample rows.
  • top is the only cap that matters. There is nothing to delegate here: SearchUser is an action, not a tabular source, so the server-side filtering is the searchTerm parameter and no delegation warning will ever appear.

Wiring OnSend

=Set(gblSendBusy, true);
Set(gblSendFailed, false);
IfError(
    Office365Outlook.SendEmailV2(
        cmpSendEmail_1.ToAddresses,
        cmpSendEmail_1.Subject,
        cmpSendEmail_1.Body,
        {
            Cc: cmpSendEmail_1.CcAddresses,
            Importance: If(cmpSendEmail_1.Priority = "Urgent", "High", "Normal")
        }
    ),
    Set(gblSendFailed, true);
    Notify("Could not send. " & FirstError.Message, NotificationType.Error)
);
Set(gblSendBusy, false);
If(
    !gblSendFailed,
    Notify(
        "Sent to " & cmpSendEmail_1.RecipientCount & " recipient(s).",
        NotificationType.Success
    );
    Set(gblSendOpen, false);
    Reset(cmpSendEmail_1)
)
// OnCancel
=Set(gblSendOpen, false);
Reset(cmpSendEmail_1)

Reset() only on the success path. A failed send that also clears the dialog throws away recipients and a note the sender just typed, with no way back. On failure the dialog stays open, still populated, and Busy is cleared so they can try again.

Audit off Note, not Body. Body is escaped markup and will read as < soup in an audit trail.


Properties

Inputs

PropertyTypeDefaultNotes
BusyBooleanfalseSet this true from the host while your send is running and false when it finishes. The dialog disables every control and shows a sending state.
ConfigRecordsee belowLook and behaviour. Theme is light or dark.
ContextHtmlText""HTML placed above the note in the body. Use it for a record summary.
ContextLineText""One short line shown under the dialog title, so the sender can see what the mail is about. Not sent.
DefaultSubjectText""Prefills the subject box. With Config.LockSubject true it becomes read only, which is what you want when a reply has to be traceable back to a record.
DirectoryTablesee belowThe people the pickers offer. Needs DisplayName and Mail columns; JobTitle is shown when present.
SignatureText""Plain text appended to the bottom of the body. Blank omits the line entirely.
TitleText"New message"Dialog heading.

Config defaults:

{
    Theme:        "light",
    Accent:       "",
    Radius:       16,
    Width:        480,
    MaxNote:      2000,
    ShowPriority: true,
    ShowCc:       true,
    LockSubject:  false
}

Accent is a hex string; blank falls back to blue. Every key is Coalesced against these values, so a partial record is safe.

Outputs

OutputTypeNotes
BodyTextThe finished HTML body. Read this in OnSend and hand it to your mail action.
CcAddressesTextSemicolon separated. Blank addresses dropped, duplicates collapsed, and anyone already on the To line removed.
NoteTextThe plain text the sender typed, trimmed and unescaped. Log this rather than Body, which is markup.
PriorityTextUrgent, FYI, Reminder, or blank. Only a label; deciding what Urgent means to your mail action is the host's job.
RecipientCountNumberHow many usable addresses ended up on the To line, after blanks and duplicates were removed.
SearchTextTextWhat the sender has typed into whichever picker they are using, Cc taking precedence when it is not blank. Bind Directory to a formula that reads this to get a live directory search.
SubjectTextThe subject as sent, priority prefix included.
ToAddressesTextSemicolon separated. Blank addresses dropped and duplicates collapsed.

Events

EventFires whenRead in handler
OnSendSend is pressed and every output is populatedToAddresses, CcAddresses, Subject, Body, Note, Priority, RecipientCount
OnCancelDismissed by the scrim, the close button or Cancel—

The email body

Body is a complete table-based HTML email, not a string of <b> tags. Accent rule across the top, a priority pill when one is set, the subject as a heading, your ContextHtml in a tinted box, the note, then a rule and the signature.

Tables and inline styles only, because Outlook on Windows renders mail through Word. No flexbox, no grid, no <style> block, no web fonts. border-radius degrades to square corners there, which is the intended floor.

Three slots, three different contracts

SlotTreatmentWhy
The noteescapedtyped by a sender
Signatureescapeddocumented as plain text
ContextHtmlrawyour own markup

That asymmetry is what makes a record summary possible. It also means any user-typed text you interpolate into ContextHtml is yours to escape. A request title pulled from a list is the usual candidate.


ContextHtml examples

Record summary

The one to copy first. A label/value table survives every mail client without a single div.

<table role='presentation' cellpadding='0' cellspacing='0' border='0' width='100%'>
  <tr><td style='padding:2px 0;color:#64748B;width:90px;'>Request</td><td style='padding:2px 0;color:#0F172A;font-weight:600;'>REQ-2026-000123</td></tr>
  <tr><td style='padding:2px 0;color:#64748B;'>Status</td><td style='padding:2px 0;color:#0F172A;'>In review</td></tr>
  <tr><td style='padding:2px 0;color:#64748B;'>Owner</td><td style='padding:2px 0;color:#0F172A;'>Ava Chen</td></tr>
</table>

Call to action

The link is wrapped in a one-cell table rather than styled directly, because Outlook will not paint a background or a radius on a bare <a>. It degrades to a plain blue rectangle with square corners, which is still a button.

<table role='presentation' cellpadding='0' cellspacing='0' border='0'><tr>
  <td bgcolor='#3B82F6' style='background-color:#3B82F6;border-radius:6px;'>
    <a href='https://example.com/item/123' style='display:inline-block;padding:10px 18px;color:#FFFFFF;font-size:13px;font-weight:600;text-decoration:none;'>Open the request</a>
  </td>
</tr></table>

Status change

<div style='margin-bottom:10px;'>
  <span style='display:inline-block;padding:3px 10px;border-radius:999px;background-color:#FEF3C7;color:#92400E;font-size:11px;font-weight:600;'>ON HOLD</span>
  &nbsp;&nbsp;<span style='color:#64748B;font-size:12px;'>was In review</span>
</div>
<div style='color:#334155;'>Paused pending budget confirmation.</div>

Attributes are single-quoted throughout. Double quotes each need doubling inside a Power Fx string and a template this size becomes unreadable; every mail client accepts single quotes.


Accessibility

  • Every interactive control is named, by AccessibleLabel on modern controls and by Tooltip on classic ones, which have no AccessibleLabel property.
  • Priority tags carry their selected state in the accessible name, since fill colour alone is not a state a screen reader can read.
  • The click-outside scrim is out of the tab order, so keyboard focus never lands on an unnamed full-screen control. Close and Cancel are the keyboard paths.
  • Send is disabled with no recipients, and its tooltip says which requirement is still unmet rather than greying out silently.

Limits

RecipientsNo cap in the component. Your mail action will have one.
Note lengthConfig.MaxNote, default 2000, with a counter that warms amber at 90%.
Instances per screenOne. Dialog state lives in component-scoped globals.
Directory sizeWhatever your Directory formula returns. Live search is capped by top.

Get new components and templates when they drop

One email when something new ships. Unsubscribe anytime.

Community

Use the toolbar to format · or type markdown directly