Senest opdateret: 2. marts 2026

Send e-mails fra Opter med Microsoft 365

Hvis du bruger Microsoft 365 som din e-mailtjeneste, skal du godkende Opter ved at registrere programmet som en app i Azure.

Appregistreringen i Azure kan udføres med et PowerShell-script, som findes som kodeeksempel nedenfor. Grunden til, at scriptet ikke kan køres i forbindelse med en opdatering, er, at den person, der kører det, skal have tilladelse til at ændre Microsoft Entra ID (tidligere Azure Active Directory) og være Microsoft 365-administrator for din organisation, hvilket Opter ikke er. Scriptet foretager de nødvendige indstillinger i Entra ID og Exchange. Når scriptet er kørt, skal nogle oplysninger indtastes under fanen Servere i kontorindstillingerne.

Nedenstående script skal muligvis tilpasses for at fungere i dit specifikke miljø.

Trin 1: Find afsenderadresser

Find ud af, hvilke e-mailadresser du bruger til at sende e-mails fra Opter. Du kan angive, hvilke e-mailadresser du bruger, når du kører scriptet. Ellers skal du gøre dette manuelt i Exchange bagefter (se nedenfor).

  • Primær adresse: Findes i feltet Email under fanen Generelt i kontorindstillingerne.

  • Adresse for regioner (hvis relevant): Tilgængelig i felt Email på fanen Generelt for regionerne.

  • Adresse til fakturaer og anden økonomisk korrespondance: Findes i feltet Email under fanen Fakturadetaljer i kontorindstillingerne eller i regionerne (hvis du bruger det).

Systemejeren af Opter ved, hvilke afsenderadresser der bruges af Opter. Du kan finde yderligere oplysninger i Alternative e-mailadresser.

Trin 2: Åbn Powershell

Scriptet skal køres i PowerShell 7 eller nyere, ikke Windows PowerShell ISE. Klik på Start-menuen i Windows og søg efter "powershell".

Et kommandovindue åbnes. Indstil rettigheden RemoteSigned ved at indsætte nedenstående kommando i kommandolinjen og trykke på Enter. Du kan finde flere oplysninger om rettighederne på Microsofts hjemmeside.

PowerShell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Når du har kørt scriptet, kan du gendanne tilladelserne med følgende kommando

PowerShell
Set-ExecutionPolicy Restricted -Scope CurrentUser

Trin 3: Gem scriptet på din computer

Kopier og gem følgende script som regmsgraph.ps1 på din computer. Afhængigt af hvordan dit miljø er konfigureret, kan det være nødvendigt at justere nogle detaljer i scriptet.

PowerShell
#requires -Version 7.0
<#

End-to-end: Create Entra ID App Registration for app-only Microsoft Graph sendMail
AND scope it with an Exchange Online Application Access Policy using a mail-enabled security group.

Derived naming:
- SecretDisplayName is derived from AppDisplayName (unless overridden)
- GroupName and GroupAlias are derived from AppDisplayName (unless overridden)

Creates:
- App registration (Application)
- Enterprise app (Service Principal)
- Client secret (default 10 years)
- Grants Microsoft Graph application permission: Mail.Send (app role assignment)
- Mail-enabled security group (Distribution Group of Type Security)
- Application Access Policy restricting the app to members of that group

Change log:
2026-02-16 Initial version 

#>

param(
  # App registration name (source for derived names)
  [Parameter(Mandatory = $false)]
  [string]$AppDisplayName = "OpterMessageProxy",

  # Secret settings
  [Parameter(Mandatory = $false)]
  [int]$SecretValidYears = 10,

  # Optional overrides (if not provided, derived from AppDisplayName)
  [Parameter(Mandatory = $false)]
  [string]$SecretDisplayName,

  [Parameter(Mandatory = $false)]
  [string]$GroupName,

  [Parameter(Mandatory = $false)]
  [string]$GroupAlias,

  # Optional: add initial members (UPN/email of mailboxes)
  [Parameter(Mandatory = $false)]
  [string[]]$InitialGroupMembers = @()
)

$ErrorActionPreference = "Stop"

function Ensure-Module {
  param([Parameter(Mandatory = $true)][string]$Name)
  if (-not (Get-Module -ListAvailable -Name $Name)) {
    Install-Module $Name -Scope CurrentUser -Force
  }
  Import-Module $Name -ErrorAction Stop
}

function Normalize-Name {
  <#
    Creates a friendly base string from an app name for use in:
    - distribution group display name
    - distribution group alias (mailNickname)
    Rules:
    - keep letters/digits/spaces/hyphens
    - collapse whitespace
    - trim
  #>
  param([Parameter(Mandatory=$true)][string]$Name)

  $n = $Name -replace '[^\p{L}\p{Nd}\s-]', ' '      # replace other chars with space
  $n = $n -replace '\s+', ' '                      # collapse spaces
  $n = $n.Trim()
  return $n
}

function To-Alias {
  <#
    Creates an Exchange-safe alias from a base name:
    - lowercase
    - letters/digits/hyphen only
    - spaces -> hyphen
    - collapse multiple hyphens
    - trim hyphens
    - max length 64 (Exchange alias limit is typically 64)
  #>
  param([Parameter(Mandatory=$true)][string]$Name)

  $a = $Name.ToLowerInvariant()
  $a = $a -replace '\s+', '-'                      # spaces to hyphen
  $a = $a -replace '[^a-z0-9-]', ''                # remove non-safe characters
  $a = $a -replace '-{2,}', '-'                    # collapse hyphens
  $a = $a.Trim('-')
  if ($a.Length -gt 64) { $a = $a.Substring(0,64).Trim('-') }
  if ([string]::IsNullOrWhiteSpace($a)) { throw "Could not derive a valid alias from AppDisplayName '$Name'." }
  return $a
}

Write-Host "=== Installing/importing modules (may take several minutes, be patient) ==="
Ensure-Module -Name "Microsoft.Graph"
Ensure-Module -Name "ExchangeOnlineManagement"

Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Applications

# ---- Derive names from AppDisplayName (unless overridden) ----
$baseName = Normalize-Name -Name $AppDisplayName
$derivedAliasBase = To-Alias -Name $baseName

if (-not $SecretDisplayName) {
  $SecretDisplayName = "$derivedAliasBase-secret"
}
if (-not $GroupName) {
  $GroupName = "$baseName - MailSend Allowed"
}
if (-not $GroupAlias) {
  $GroupAlias = "$derivedAliasBase-mailsend-allowed"
  if ($GroupAlias.Length -gt 64) { $GroupAlias = $GroupAlias.Substring(0,64).Trim('-') }
}

Write-Host ""
Write-Host "=== Derived/selected Names ==="
Write-Host "AppDisplayName     : $AppDisplayName"
Write-Host "SecretDisplayName  : $SecretDisplayName"
Write-Host "GroupName          : $GroupName"
Write-Host "GroupAlias         : $GroupAlias"
Write-Host ""

Write-Host "=== Connecting to Microsoft Graph ==="
$scopes = @(
  "Application.ReadWrite.All",
  "Application.Read.All",
  "Directory.Read.All",
  "AppRoleAssignment.ReadWrite.All"
)
Connect-MgGraph -Scopes $scopes | Out-Null
$ctx = Get-MgContext
Write-Host "Connected to Graph. TenantId: $($ctx.TenantId)"

Write-Host "`n=== Creating App Registration + Service Principal ==="
$app = New-MgApplication -DisplayName $AppDisplayName -SignInAudience "AzureADMyOrg"
$sp  = New-MgServicePrincipal -AppId $app.AppId

Write-Host "App created:"
Write-Host "  App (objectId)   : $($app.Id)"
Write-Host "  ClientId (appId) : $($app.AppId)"
Write-Host "  SP (objectId)    : $($sp.Id)"

Write-Host "`n=== Creating Client Secret (default $SecretValidYears years) ==="
$end = (Get-Date).ToUniversalTime().AddYears($SecretValidYears).ToString("o")
$pwdParams = @{
  PasswordCredential = @{
    DisplayName = $SecretDisplayName
    EndDateTime = $end
  }
}
$secret = Add-MgApplicationPassword -ApplicationId $app.Id -BodyParameter $pwdParams

Write-Host "Secret created (store the value now, it is shown only once)."
Write-Host "  Secret expires (UTC): $end"

Write-Host "`n=== Granting Microsoft Graph Application Permission: Mail.Send ==="
$graphAppId = "00000003-0000-0000-c000-000000000000"  # Microsoft Graph
$graphSp = Get-MgServicePrincipal -Filter "appId eq '$graphAppId'"
if (-not $graphSp) { throw "Microsoft Graph service principal not found in tenant." }

$mailSendRole = $graphSp.AppRoles | Where-Object {
  $_.Value -eq "Mail.Send" -and $_.AllowedMemberTypes -contains "Application"
} | Select-Object -First 1

if (-not $mailSendRole) { throw "Could not find Microsoft Graph app role 'Mail.Send' (Application)." }

$assignBody = @{
  principalId = $sp.Id
  resourceId  = $graphSp.Id
  appRoleId   = $mailSendRole.Id
}
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -BodyParameter $assignBody | Out-Null
Write-Host "Granted: Mail.Send (Application)"

Write-Host "`n=== Connecting to Exchange Online ==="
try {
  Connect-ExchangeOnline -ErrorAction Stop | Out-Null
} catch {
  Write-Warning "Connect-ExchangeOnline failed. Retrying with device code..."
  Connect-ExchangeOnline -Device -ErrorAction Stop | Out-Null
}

Write-Host "`n=== Creating Mail-Enabled Security Group ==="
# Mail-enabled security group is created as a Distribution Group with -Type Security
$dg = New-DistributionGroup -Name $GroupName -Alias $GroupAlias -Type Security

Write-Host "Group created:"
Write-Host "  Name   : $($dg.DisplayName)"
Write-Host "  Alias  : $($dg.Alias)"
Write-Host "  SMTP   : $($dg.PrimarySmtpAddress)"

if ($InitialGroupMembers.Count -gt 0) {
  Write-Host "`nAdding initial group members..."
  foreach ($m in $InitialGroupMembers) {
    Write-Host "  + $m"
    Add-DistributionGroupMember -Identity $dg.Identity -Member $m
  }
} else {
  Write-Host "`nNo initial members provided. Add allowed mailboxes to: $($dg.PrimarySmtpAddress)"
}

Write-Host "`n=== Creating Application Access Policy (RestrictAccess) ==="
New-ApplicationAccessPolicy `
  -AppId $app.AppId `
  -PolicyScopeGroupId $dg.PrimarySmtpAddress `
  -AccessRight RestrictAccess `
  -Description "Restrict app-only Graph mail access to members of $($dg.PrimarySmtpAddress)" | Out-Null

Write-Host "Application Access Policy created."

Write-Host "`n=== OUTPUT (save securely) ==="
Write-Host "Ms Graph Tenant Id       : $($ctx.TenantId)"
Write-Host "Ms Graph Client Id       : $($app.AppId)"
Write-Host "Ms Graph Client Secret   : $($secret.SecretText)"
Write-Host "Secret Expires (UTC)     : $end"
Write-Host "Scoped Group SMTP        : $($dg.PrimarySmtpAddress)"
Write-Host ""
Write-Host "Test examples (EXO):"
Write-Host "  Test-ApplicationAccessPolicy -AppId $($app.AppId) -Identity allowed.user@yourdomain.com"
Write-Host "  Test-ApplicationAccessPolicy -AppId $($app.AppId) -Identity notallowed.user@yourdomain.com"
Write-Host ""
Write-Host "Reminder: app-only sending uses POST /users/{id|UPN}/sendMail (not /me/sendMail)."

Disconnect-ExchangeOnline -Confirm:$false | Out-Null
Disconnect-MgGraph | Out-Null

Hvad gør scriptet?

Vi deler det op i blokke.

  • Linjerne 24–96 indstiller variabler, der bruges senere i scriptet, og sikrer, at værdierne for variablerne fungerer.

  • Linjerne 98–126 installerer de Azure-moduler, der er nødvendige for, at resten af scriptet kan fungere. Modulerne installeres i C:\Program Files\WindowsPowerShell\Modules\.

  • Linjer 128–137 logger ind på Microsoft Entra ID (tidligere Azure Active Directory) med de tilladelser, der kræves for at gennemføre app-registreringen.

  • Linjerne 139–178 registrerer Opter som en app i Azure med navnet "OpterMessageProxy" og giver appen tilladelse til at sende e-mails. Du kan ændre navnet "OpterMessageProxy" til noget andet på linje 27, men det er nemmere at beholde standardnavnet, hvis du af en eller anden grund har brug for at kontakte support.

  • Linjerne 180–214 opretter en tilladelsesgruppe i Exchange med begrænsede rettigheder og tilføjer OpterMessageProxy til den.

  • Linjerne 216–230 viser de oplysninger, der skal indtastes i kontorindstillingerne i Opter (fanen Servere).

Trin 4: Kør scriptet

Kør scriptet fra PowerShell 7.0 eller nyere ved at indsætte stien til filen regmsgraph.ps1 i kommandolinjen.

  • Hvis du senere vil tilføje afsenderadresserne manuelt i Exchange, skal du køre scriptet i henhold til linje 1 nedenfor. Erstat "C:\Temp" med den sti, hvor du har gemt scriptet.

  • Hvis du vil tilføje afsenderadresserne nu, skal du bruge linje 2, når du kører scriptet, og erstatte stien til scriptet ("C:\Temp") og e-mailadresserne med de korrekte.

PowerShell
PS C:\Temp\regmsgraph.ps1
PS C:\Temp\regmsgraph.ps1 -InitialGroupMembers @("info@exempel.se","faktura@exempel.se")

Når scriptet kører, bruger det de godkendelsesoplysninger, du er logget ind med på Azure. Sørg for, at du er logget ind med en konto, der har administratorrettigheder til Microsoft Entra ID og Microsoft 365 i din organisation. Hvis du ikke er logget ind, skal du logge ind, når scriptet kører.

Du vil blive spurgt, om du vil installere modulerne – her skal du svare ja.

Når scriptet er klar, vises de oplysninger, der skal indtastes i kontorindstillingerne. Luk ikke PowerShell-konsollen, før du har indtastet oplysningerne eller indsat dem i en tekstfil eller et andet sted, hvorfra du kan kopiere dem.

Klienthemmeligheden kan ikke hentes eller gendannes. Hvis du ikke har gemt den et sted, skal du oprette en ny i Azure-portalen. De øvrige oplysninger kan hentes fra Azure-portalen bagefter, hvis det bliver nødvendigt.

Trin 5: Forbind Opter med Microsoft 365/MS Graph

Klik på Indstillinger > Kontor > fanen Servere og udfyld følgende felter med de oplysninger, der vises i PowerShell-konsollen:

  • Ms Graph Tenant Id

  • Ms Graph Client Id

  • Ms Graph Client Secret

Gem indstillingerne, og klik derefter på Test email. Indtast dig selv som modtager og kontroller, at du modtager en besked.

Hvis der findes en værdi i feltet Ms Graph Tenant Id, er det ligegyldigt, om felterne ovenfor under denne fane er udfyldt (SMTP-server, Port, Brugernavn, Adgangskode og Sikker forbindelse (SSL)). Disse oplysninger bruges ikke, da autentificeringen sker via appregistreringen i Azure. Det kan være en god idé at fjerne oplysningerne i dem for at undgå misforståelser.

Appregistrering i Azure

Du kan finde appregistreringen på portal.azure.com ved at søge på "optermessageproxy" og vælge det søgeresultat, der hedder Application. Du kan også gå til appregistreringer og lede i listen over registrerede programmer.

Under OpterMessageProxy kan du se og kopiere Ms Graph Tenant Id = Directory (tenant) ID (2) og Ms Graph Client Id = Application (client) ID (1).

Ms Graph Client Secret kan ikke vises eller gendannes i Azure-portalen. Hvis du glemte at indsætte den, da scriptet kørte, skal du oprette en ny hemmelighed. Gå til Manage (1) > Certificates & secrets (2) i menuen til venstre, og klik på New client secret. Der kan du også tjekke, hvornår klienthemmeligheden udløber.

Flere domæner

Hvis du har flere domæner, f.eks. "transportfirman.centraleurope.se" og "transportfirman.nordics.se", skal du køre scriptet én gang for hvert domæne og ændre navnet på app-registreringen, hver gang du kører det. Medmindre navnet ændres, overskrives den tidligere app-registrering, når scriptet køres igen. Sådan gør du:

  1. Ændr linje 27 i scriptet, så navnet identificerer det første domæne. I tilfælde af transportvirksomheden ændres linje 27 fra

    [string]$AppDisplayName = "OpterMessageProxy",

    til

    [string]$AppDisplayName = "OpterMessageProxy-centraleurope",

  2. Gem og kør scriptet.

  3. Ændr linje 27 til navnet på det næste domæne. I tilfælde af transportvirksomhed fra

    [string]$AppDisplayName = "OpterMessageProxy-centraleurope",

    til

    [string]$AppDisplayName = "OpterMessageProxy-nordics",

  4. Gem og kør scriptet igen.

Nu er der to app-registreringer i Azure, som kan administreres hver for sig.

Alternative afsenderadresser

Hvis du ikke tilføjede de alternative adresser, da du startede scriptet, kan du gøre det i sikkerhedsgruppen OpterMessageProxy_group i Exchange. Hvis du ændrede navnet under registreringen af Opter i Azure, vil gruppen få det navn, du angav, efterfulgt af "_group". Alle de afsenderadresser, du har tilføjet til Opter, skal være inkluderet som medlemmer af sikkerhedsgruppen.

Flere oplysninger og eksempler på, hvordan man tilføjer forskellige afsenderadresser, findes i Alternative e-mailadresser.

Sådan tilføjer du en afsenderadresse som medlem af OpterMessageProxy_group i Exchange admin center. Du kan finde flere oplysninger på Microsofts hjemmeside.

Se også