Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

IT Support & Help Desk Fundamentals  Module 14 – Scripting, cloud and email support

Email, calendars and meeting apps

Match mail ports to protocols, fix apps that keep asking for a password, read SPF, DKIM and DMARC results, and sort calendar, camera and microphone faults.

  • Intermediate
  • 20 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Explain which protocol and port a mail app uses to send mail and to read it
  • Fix a mail app that keeps asking for a password now that Basic authentication is off
  • Read SPF, DKIM and DMARC results to explain why a message went to junk
  • Diagnose meetings that move after a clock change, and mail sent from shared mailboxes
  • Troubleshoot camera, microphone and screen-sharing faults in meeting apps

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain which protocol and port a mail app uses to send mail and to read it
  • Fix a mail app that keeps asking for a password now that Basic authentication is off
  • Read SPF, DKIM and DMARC results to explain why a message went to junk
  • Diagnose meetings that move after a clock change, and mail sent from shared mailboxes
  • Troubleshoot camera, microphone and screen-sharing faults in meeting apps

Before you start

On this page

A quote sent from a new billing service lands in every customer’s junk folder, while a reply the sales team sent the same morning reaches the inbox. Both messages carry the answer in their headers: one server waited 11 minutes 42 seconds, and one check said fail.

Mail and meeting tickets look personal (“my email is broken”, “they cannot see me”) and are mostly read off a few fixed places. You will name the ports a mail app uses, fix an app that keeps asking for a password, read the results a receiving server writes into a message, explain why DMARC failed for the quote and what fixes it, and work out why a weekly meeting moved by an hour and why a camera tile stays black.

Mail goes out on one protocol and is read with another

A user setting up mail in a desktop app meets two servers: one to read mail from, one to send it through, each with a port number. They are different jobs. Sending from an app to its own mail service is message submission, which RFC 6409 puts on port 587; servers relaying mail to each other keep using SMTP on port 25. Reading uses IMAP or POP3. The IANA registry lists the ports a support desk meets:

Port IANA service name Used for
25 smtp servers relaying mail to each other
587 submission an app sending, then switching to TLS
465 submissions an app sending, TLS from the first byte
993 imaps an app reading a mailbox over IMAP
995 pop3s an app downloading mail over POP3

On 587 the connection opens in clear text and the app asks the server to start encryption with A command that upgrades a connection which began in clear text to an encrypted TLS one, before anything private is sent. Mail apps use it on the submission port 587.. On 465, 993 and 995 TLS starts as soon as the connection opens, which RFC 8314 calls TLS that starts the moment a connection opens, on a port used only for encrypted connections, such as 465 for sending mail and 993 and 995 for reading it., and the RFC recommends it in preference to STARTTLS for sending and reading mail. Not every app uses these ports: Microsoft’s Basic authentication page says phones’ built-in mail apps generally reach Exchange Online with Exchange ActiveSync, and lists Outlook for Windows and for Mac as connections of their own. Follow one message on its way:

Interactive · message by message One message, from a mail app to the recipient Step through the trip and watch where each port is used and where the receiving server decides inbox or junk.
One message, from a mail app to the recipient, step 4 of 4. Messages so far: Mail app to Sender: port 465 or 587; Sender to Receiver: port 25.Mail applaptopSenderyour mailReceivertheir mailport 465 or 587port 25inbox or junk

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. Submit, signed in. The app connects to its own mail service on port 465, where TLS starts with the first byte, or on 587, where it asks for STARTTLS before anything else. It signs in, then hands the message over.
  2. Sign it and relay it. The sending service may add a DKIM signature for its domain, then relays the message over SMTP on port 25 to the service that receives mail for the recipient's domain.
  3. Three checks. The receiver checks with SPF whether the server that connected may send for the envelope sender's domain, verifies any DKIM signature, then asks whether a domain that passed matches the From address. That last question is DMARC.
  4. The verdict, written down. It writes the three results into an Authentication-Results header at the top of the message, then files it in the inbox, or in junk when DMARC fails and the From domain's policy asks for that.

The last two steps are the ones a junk-mail ticket turns on, and the next three sections read them in detail. IANA’s registry, in the references, names every other port a settings screen may show.

Checkpoint

Check: Mail protocols and their ports

  1. Question 1 of 2 Which port does a mail app use to send mail with TLS from the very first byte?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 465

    Sending from an app is message submission: 465 with implicit TLS, or 587 with STARTTLS. Reading uses IMAP on 993 or POP3 on 995, and 25 is left to servers relaying mail between themselves.

  2. Question 2 of 2 A user typed port 25 as the outgoing server in a new laptop's mail app, and sending fails. What do you set instead?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 465 with TLS, or 587 with STARTTLS, matching what the mail service supports

    RFC 6409 splits submission from relay: apps submit on 587, and relay between servers keeps port 25. RFC 8314 adds 465 with implicit TLS for submission.

A password prompt that keeps coming back means Basic authentication

A user’s desktop mail app, set up years ago with IMAP and a password, asks for the password every few minutes, and typing the right one changes nothing. The app is using Signing in by sending the user name and password with every request, which apps often store on the device. Exchange Online no longer accepts it for mail protocols., and Microsoft’s deprecation page is plain about where that stands: it is now disabled in every Exchange Online tenant, and neither the tenant’s admins nor Microsoft support can switch it back on. It was removed for Exchange ActiveSync, POP, IMAP, Remote PowerShell, EWS, the Offline Address Book, Autodiscover and Outlook for Windows and Mac. SMTP AUTH: Sending mail through a server after signing in with a mailbox's credentials. Microsoft calls its form in Exchange Online client SMTP submission. More in “Add, share and secure printers and scanners” was switched off in tenants that were not using it, and Microsoft has announced plans to retire Basic authentication for it too, pointing to its latest announcement for the timeline.

What replaces the password is modern authentication: Signing in to a service with short-lived tokens from an identity provider, using OAuth 2.0, instead of sending the user name and password to the service itself. It also lets an organisation ask for a second factor. More in “Support phones and tablets from setup to sync”: the app opens a sign-in page, the user signs in there, with multifactor authentication where the organisation asks for it, and the app keeps a token. Microsoft added OAuth 2.0 to POP, IMAP and SMTP AUTH and names Thunderbird among the apps updated to use it, so the same desktop app often needs only a new version and a changed setting. The same page notes that app passwords stopped working too, for apps that do not support two-step verification. Work the fix from the cheapest step:

Interactive · stage by stage A password prompt that will not go away Step through the fix in the order a desk tries it, from the cheapest step to replacing the app.
A password prompt that will not go away, step 6 of 6. Showing Which app asks?, Update the app, Remove and add back, Modern sign-in, Read the sign-in log, Replace the app. In focus: Read the sign-in log.No OAuth: replaceWhich app asks?Name and versionUpdate the appOften enoughRemove and add backThe account, freshModern sign-inOAuth 2.0 pageRead the sign-in logWhat was refusedReplace the appNo OAuth in it

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Name the app first. Write down the app, its version and how the account was set up: a phone's built-in mail, a desktop app with IMAP, an old program that sends with SMTP. The same symptom has a different fix in each.
  2. Known to lack OAuth. An app whose maker says it cannot sign in with OAuth 2.0 will never stop asking, because the password route is gone. Skip straight to a replacement.
  3. Update it. Microsoft notes that the built-in mail apps of popular platforms typically support modern authentication, so the first fix is often the latest version of the app.
  4. Remove and add back. An app that is current but still set up the old way keeps sending the password. Removing the account and adding it back lets it discover the modern sign-in.
  5. Modern sign-in. Choose the account type or the OAuth 2.0 option the app offers. The sign-in then happens on a Microsoft page, with multifactor authentication, and the app keeps a token instead of the password.
  6. Still asking? Read the log. If the prompt comes back, the sign-in log of the account shows each attempt, the app that made it and any Conditional Access policy that refused it.

Organisations also refuse the old route on purpose. Microsoft recommends blocking sign-ins over legacy protocols that cannot do multifactor authentication with a Conditional Access: Microsoft Entra ID's policy engine that decides each sign-in from signals (user, device compliance, location, risk, application) after the password step. Each policy is an if-then rule with an allow, require MFA or block grant. More in “Cloud identity: Entra ID, SSO and device join” policy, and says more than 97 percent of credential stuffing attacks use legacy authentication. So a refused sign-in in the sign-in log: The record Microsoft Entra ID writes for every sign-in attempt, with the user, device, application and every Conditional Access policy that fired. A help-desk ticket about a cloud sign-in is often closed by reading one row of it. More in “Cloud identity: Entra ID, SSO and device join” is the organisation’s choice working. Phones have their own walk-through in the mobile devices lesson, and scanners that send mail in adding and sharing printers.

Asking for Basic authentication to be switched back on

Wrong: “Ask Microsoft to re-enable Basic for our tenant until the app is fixed.” Right: nobody can re-enable it any more, so update the app, add the account again with modern sign-in, or replace an app that cannot do OAuth 2.0.

Checkpoint

Check: The end of Basic authentication

  1. Question 1 of 2 An up-to-date phone mail app keeps asking for the password of an Exchange Online mailbox. What is the next step?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Remove the account from the app and add it back, so it can find the modern sign-in

    The prompt comes back because the app uses Basic authentication. Update the app, then remove the account and add it back so that it signs in with modern authentication and keeps a token instead of the password.

  2. Question 2 of 2 The sign-in log shows an old desktop app's attempts refused by a Conditional Access policy that blocks legacy authentication. What does that tell you?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The policy is doing its job: the app signs in by a legacy protocol that cannot do MFA, so update or replace it

    A refusal by a legacy-authentication policy is the organisation's choice working. The fix is an app that signs in with OAuth 2.0, not a hole in the policy.

The receiving server writes its verdict into the message

Back to the quote in junk. A message carries its own history in its headers. Every server that handles it adds a Received line at the top, which RFC 5322 calls a trace field, so the lines read from the bottom up give the route with a time at each step. And the receiving server writes what it decided into an A header that a receiving mail server adds to a message, recording the results of its checks, such as spf=pass, dkim=pass and dmarc=fail, under its own name. header, defined in RFC 8601, under its own name.

The program reads both: the sales reply and the billing quote, as samples invented for this lesson, sent to a customer whose server is mx.example.org. It works out each hop’s delay, picks the slowest, and reads the three results from the header mx.example.org added:

Two messages' routes, their slowest hop and the receiver's results Python · headers/headers.py
"""Read the trace of two messages: where each one waited, and what the receiving server decided about it.

Both messages are samples invented for this lesson, written in the header layout RFC 5322 and RFC 8601 define. The
sender's company is example.com, a billing service it has started to use is example.net, and the recipient's mail
server is mx.example.org. Every server that handles a message adds a Received line at the top, so the lines are read
from the bottom up. Only the Authentication-Results header added by the recipient's own server counts: the one that
names mx.example.org.
"""

from email.utils import parsedate_to_datetime

RECEIVER = "mx.example.org"

SALES_REPLY = """\
Authentication-Results: mx.example.org; spf=pass smtp.mailfrom=example.com; dkim=pass header.d=example.com; dmarc=pass header.from=example.com
Received: from out.example.com (out.example.com [192.0.2.25]) by mx.example.org with ESMTPS; Mon, 05 Oct 2099 10:00:07 +0530
Received: from laptop-07.example.com ([192.0.2.70]) by out.example.com with ESMTPSA; Mon, 05 Oct 2099 10:00:04 +0530
From: Sales <sales@example.com>
Subject: Your quote request"""

BILLING_QUOTE = """\
Authentication-Results: mx.example.org; spf=pass smtp.mailfrom=bounces.example.net; dkim=pass header.d=example.net; dmarc=fail header.from=example.com
Received: from mailer.example.net (mailer.example.net [198.51.100.40]) by mx.example.org with ESMTPS; Mon, 05 Oct 2099 10:14:52 +0530
Authentication-Results: mailer.example.net; dkim=pass header.d=example.net
Received: from queue.example.net ([198.51.100.41]) by mailer.example.net with ESMTP; Mon, 05 Oct 2099 10:03:10 +0530
Received: from app.example.net ([198.51.100.42]) by queue.example.net with ESMTP; Mon, 05 Oct 2099 10:03:08 +0530
From: Quotes <quotes@example.com>
Subject: Quote Q-1042"""


def header_lines(text, name):
    """The values of every header line called `name`, top first."""
    prefix = name.lower() + ":"
    return [line.split(":", 1)[1].strip() for line in text.splitlines() if line.lower().startswith(prefix)]


def hops(text):
    """(from host, by host, seconds since the previous hop) for each Received line, oldest first."""
    found = []
    for value in reversed(header_lines(text, "Received")):
        route, when = value.rsplit(";", 1)
        source = route.split("from ", 1)[1].split()[0]
        target = route.split(" by ", 1)[1].split()[0]
        found.append((source, target, parsedate_to_datetime(when.strip())))
    out = []
    for i, (source, target, when) in enumerate(found):
        waited = 0 if i == 0 else int((when - found[i - 1][2]).total_seconds())
        out.append((source, target, waited))
    return out


def results(text, server):
    """method -> result, from the Authentication-Results header that `server` added."""
    for value in header_lines(text, "Authentication-Results"):
        authserv, _, rest = value.partition(";")
        if authserv.strip() != server:
            continue
        found = {}
        for part in rest.split(";"):
            method, _, outcome = part.strip().partition("=")
            found[method] = outcome.split()[0]
        return found
    return {}


for name, text in (("Sales reply", SALES_REPLY), ("Billing quote", BILLING_QUOTE)):
    print(name)
    trace = hops(text)
    for source, target, waited in trace:
        print(f"  {source} -> {target}: {waited} s")
    slow = max(trace, key=lambda hop: hop[2])
    print(f"  slowest hop: {slow[0]} -> {slow[1]}, {slow[2] // 60} min {slow[2] % 60} s")
    found = results(text, RECEIVER)
    print(f"  {RECEIVER} says: spf={found.get('spf', 'none')} dkim={found.get('dkim', 'none')} dmarc={found.get('dmarc', 'none')}")

Output

Sales reply
  laptop-07.example.com -> out.example.com: 0 s
  out.example.com -> mx.example.org: 3 s
  slowest hop: out.example.com -> mx.example.org, 0 min 3 s
  mx.example.org says: spf=pass dkim=pass dmarc=pass
Billing quote
  app.example.net -> queue.example.net: 0 s
  queue.example.net -> mailer.example.net: 2 s
  mailer.example.net -> mx.example.org: 702 s
  slowest hop: mailer.example.net -> mx.example.org, 11 min 42 s
  mx.example.org says: spf=pass dkim=pass dmarc=fail

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 headers.py

The sales reply went through in 3 seconds and passed all three checks. The quote sat at mailer.example.net, the billing service’s last server, for 11 minutes 42 seconds before the customer’s server accepted it. Headers show where a message waited, not why, so the billing service’s own delivery log is the next place to ask. Then the verdict: Sender Policy Framework (RFC 7208): a domain's DNS record listing the servers allowed to send mail with that domain in the envelope sender, the bounce address a receiver checks. and DomainKeys Identified Mail (RFC 6376): a signature a sending service adds to a message for one domain, which receivers verify with a public key that domain publishes in DNS. passed, and Domain-based Message Authentication, Reporting and Conformance (RFC 9989): a check that passes only when the From address's domain matches a domain that SPF or DKIM confirmed, plus the policy its owner asks for when it fails. failed. Note the second Authentication-Results header in the quote, added by the billing service’s own server: the program ignores it, because only the receiving server’s word counts. RFC 8601 even tells a receiving server to delete any such header that carries its own name but arrived from outside, since anyone can write one.

Email Header Analyzer Paste a message's headers to see its route with delays and its SPF, DKIM and DMARC results; nothing is uploaded.

Checkpoint

Check: Reading a message's route and results

  1. Question 1 of 2 A message carries two Authentication-Results headers: one from mailer.example.net saying dmarc=pass, and one from mx.example.org, your own server, saying dmarc=fail. Which do you go by?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The one from mx.example.org, because only your receiving server's checks count

    The lesson's program reads only the header that names mx.example.org. RFC 8601 goes further and tells a receiving server to delete headers that carry its own name but arrived from outside.

  2. Question 2 of 2 Read from the bottom up, a message's hops took 0 s, 2 s and then 11 min 42 s from mailer.example.net to mx.example.org. Where did it wait?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: At the sender's server, mailer.example.net, before your service received it

    Each Received line carries the time a server took the message. The 702 seconds between the sender's last server and yours passed before your service had it; the headers show where it waited, and the sender's delivery log shows why.

DMARC passes only when a checked domain matches the From address

Two passes and a fail look contradictory until you see what each check covers. SPF and DKIM each confirm a domain, but not necessarily the domain in the From address the reader sees. DMARC joins them to the From address:

Check What it confirms In the quote
SPF the server may send for the envelope sender’s domain pass, for bounces.example.net
DKIM the signature is valid for the domain after d= pass, for example.net
DMARC a confirmed domain aligns with the From domain fail: the From is example.com

RFC 9989 calls the match alignment. Under relaxed mode, the default, the confirmed domain and the From domain must share the same organisational domain, so bounces.example.com would align with example.com; strict mode needs them identical. Neither bounces.example.net nor example.net aligns with example.com, so DMARC failed although nothing was forged. The fix happens at the billing service: have it sign with a DKIM key for example.com, whose public key the company publishes in its own DNS, or send with an envelope sender under example.com, which that domain’s SPF record must then allow.

Key idea

SPF and DKIM can both pass for a sending service’s own domain while DMARC fails: DMARC passes only when a domain they confirmed aligns with the From address the reader sees.

Before the second fix, check what an include costs. The program reads sample TXT records of example.com as dig would print them: the SPF record before and after the service’s include is added, as it would be for quotes sent with an envelope sender at example.com itself, and the DMARC record:

An SPF record's lookup count and a DMARC record's policy Python · txt-records/txt_records.py
"""Read a domain's SPF and DMARC records the way a technician checks them before changing anything.

The records are samples invented for this lesson, as `dig TXT example.com` and `dig TXT _dmarc.example.com` would print
them. For SPF, RFC 7208 lets the terms include, a, mx, ptr, exists and redirect cause at most 10 DNS lookups between
them; each include also brings the terms of the record it names, so the count of one record is a lower bound. For
DMARC, RFC 9989 requires v=DMARC1 first, treats a missing p as p=none, and ignores tags it does not define.
"""

LOOKUP_MECHANISMS = {"include", "a", "mx", "ptr", "exists"}
DMARC_TAGS = {"v", "p", "sp", "np", "psd", "t", "rua", "ruf", "adkim", "aspf", "fo"}

SPF_BEFORE = "v=spf1 include:spf.mail.example.org a mx ip4:192.0.2.25 -all"
SPF_AFTER = SPF_BEFORE.replace(" -all", " include:bounces.example.net -all")
DMARC = "v=DMARC1; p=quarantine; pct=50; rua=mailto:dmarc-reports@example.com"


def spf_lookups(record):
    """The terms of one SPF record that cost a DNS lookup."""
    found = []
    for term in record.split()[1:]:
        name = term.lstrip("+-~?")
        mechanism = name.split(":", 1)[0].split("/", 1)[0]
        if mechanism in LOOKUP_MECHANISMS or name.startswith("redirect="):
            found.append(term)
    return found


def spf_all(record):
    """What the record says about every other sender: the qualifier of its all term."""
    last = record.split()[-1]
    return {"-all": "fail", "~all": "softfail", "?all": "neutral", "+all": "pass", "all": "pass"}.get(last, "no all term")


def dmarc_tags(record):
    """(tags RFC 9989 defines, tags it ignores) of one DMARC record."""
    pairs = [part.strip().split("=", 1) for part in record.split(";") if part.strip()]
    known = {k: v for k, v in pairs if k in DMARC_TAGS}
    ignored = [k for k, _ in pairs if k not in DMARC_TAGS]
    return known, ignored


for label, record in (("SPF now", SPF_BEFORE), ("SPF with the service added", SPF_AFTER)):
    lookups = spf_lookups(record)
    print(f"{label}: {record}")
    print(f"  lookup terms: {len(lookups)} of the 10 allowed ({', '.join(lookups)}); everyone else: {spf_all(record)}")

known, ignored = dmarc_tags(DMARC)
print(f"DMARC: {DMARC}")
print(f"  policy for mail that fails: {known.get('p', 'none')}; test mode: {known.get('t', 'n')}")
print(f"  aggregate reports to: {known.get('rua', 'nobody')}")
print(f"  ignored, not defined by RFC 9989: {', '.join(ignored) or 'nothing'}")

Output

SPF now: v=spf1 include:spf.mail.example.org a mx ip4:192.0.2.25 -all
  lookup terms: 3 of the 10 allowed (include:spf.mail.example.org, a, mx); everyone else: fail
SPF with the service added: v=spf1 include:spf.mail.example.org a mx ip4:192.0.2.25 include:bounces.example.net -all
  lookup terms: 4 of the 10 allowed (include:spf.mail.example.org, a, mx, include:bounces.example.net); everyone else: fail
DMARC: v=DMARC1; p=quarantine; pct=50; rua=mailto:dmarc-reports@example.com
  policy for mail that fails: quarantine; test mode: n
  aggregate reports to: mailto:dmarc-reports@example.com
  ignored, not defined by RFC 9989: pct

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 txt_records.py

RFC 7208 lets the terms that cost a DNS lookup (include, a, mx, ptr, exists and redirect) add up to at most 10, and each include brings the lookups of the record it names, so the program’s counts, 3 and then 4, are floors. The DMARC record asks receivers to quarantine failing mail, which is why the customers’ servers filed the quote in junk rather than refusing it. Under RFC 9989 its pct=50 means nothing: the tag was removed, unknown tags are ignored, and a t=y test mode took over part of its job.

Adding the service to the SPF record of the From domain

Wrong: “Add include:bounces.example.net to example.com’s SPF and the quotes will pass.” SPF is checked for the envelope sender’s domain, which is still the service’s own, so nothing aligns. Right: an aligned DKIM signature for example.com, or an envelope sender under example.com that its SPF allows.

Checkpoint

Check: SPF, DKIM and DMARC alignment

  1. Question 1 of 2 SPF passed for bounces.example.net and DKIM passed for example.net, yet DMARC failed for From: quotes@example.com. Why?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Neither confirmed domain aligns with example.com, the From domain

    SPF and DKIM each confirm a domain, and DMARC asks whether that domain matches the From address. The billing service confirmed only its own domains, so nothing aligned with example.com.

  2. Question 2 of 2 Which change makes the billing service's quotes pass DMARC for example.com?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Have the service sign with a DKIM key for example.com, whose public key example.com publishes in DNS

    Alignment is fixed at the sender: an aligned DKIM signature, or an envelope sender under example.com that its SPF record allows. Relaxing the policy only hides the failure.

A recurring meeting keeps the clock of the zone it was made in

A London manager runs a weekly call at 09:00 London time with a team on UTC+05:30. One Monday in late October, the team say the call has moved an hour later. Nothing was edited. RFC 5545, the iCalendar standard calendars exchange, advises writing a recurring event’s start as a local time with its time zone, so that every instance starts at the same local time even when that zone’s clocks change. GOV.UK says the UK’s clocks go back an hour at 2am on the last Sunday in October, from British Summer Time (UTC+01:00) to Greenwich Mean Time (UTC+00:00), and forward again on the last Sunday in March. The program shows the same meeting from both sides, and the reverse case:

One weekly meeting across the week the UK clocks go back Python · meeting-times/meeting_times.py
"""A weekly meeting across the week the UK's clocks go back, seen from London and from a UTC+05:30 office.

The offsets are written out instead of read from a time-zone database: London is UTC+01:00 on British Summer Time and
UTC+00:00 on Greenwich Mean Time (GOV.UK), and the other office stays on UTC+05:30. A recurring meeting keeps its
clock time in the time zone it was created in, so whoever is in the other zone sees it move.
"""

from datetime import datetime, timedelta, timezone

LONDON = {"week before": timezone(timedelta(hours=1)), "week after": timezone(timedelta(hours=0))}
OFFICE = timezone(timedelta(hours=5, minutes=30))


def shown(hour, minute, made_in, seen_in):
    """The meeting's local time in `seen_in`, when it is held at hour:minute in `made_in`."""
    held = datetime(2000, 1, 3, hour, minute, tzinfo=made_in)  # any day: only the clock time matters here
    return held.astimezone(seen_in).strftime("%H:%M")


print("Organised in London for 09:00 London time:")
for week, london in LONDON.items():
    print(f"  {week}: London 09:00 (UTC{london.utcoffset(None).total_seconds() / 3600:+.0f}), office {shown(9, 0, london, OFFICE)}")

print("Organised in the office for 15:00 office time:")
for week, london in LONDON.items():
    print(f"  {week}: office 15:00, London {shown(15, 0, OFFICE, london)}")

Output

Organised in London for 09:00 London time:
  week before: London 09:00 (UTC+1), office 13:30
  week after: London 09:00 (UTC+0), office 14:30
Organised in the office for 15:00 office time:
  week before: office 15:00, London 10:30
  week after: office 15:00, London 09:30

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 meeting_times.py

The London meeting stays at 09:00 for its organiser and moves from 13:30 to 14:30 for the team; a meeting created in the team’s office for 15:00 moves the other way for London, from 10:30 to 09:30. Neither is a fault. The fix, when it matters, is a decision about whose clock the meeting follows. One person seeing every meeting shifted, by contrast, usually has a device set to the wrong time zone, so compare the device’s zone and clock with the real local time before touching the calendar.

A delegate’s permission decides how mail from a shared mailbox looks

Mail sent for someone else has its own three permissions in Exchange Online, and Microsoft’s permissions page draws the lines a desk is asked about:

Permission What the delegate can do
Full Access open the mailbox and work in it, but not send from it
Send As send mail that appears to come from the mailbox itself
Send on Behalf send as “delegate on behalf of mailbox”; replies go to the mailbox

A delegate who holds both sending permissions always sends as Send As, Microsoft adds, which explains why “on behalf of” vanished after a second permission was granted.

Checkpoint

Check: Meeting times and mailbox permissions

  1. Question 1 of 2 A weekly call made in London for 09:00 London time shows at 13:30 in a UTC+05:30 office. The UK clocks then go back. When does it show in the office?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 14:30

    A recurring meeting keeps the local time of the zone it was created in. meeting_times.py printed 13:30 before the change and 14:30 after it for the office.

  2. Question 2 of 2 A delegate's mail from the shared Accounts mailbox used to show "Asha on behalf of Accounts". After a second permission was granted it shows only "Accounts". Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: She now has Send As as well, and Send As is always used when a delegate has both

    Send on Behalf shows "delegate on behalf of mailbox" and sends replies to the mailbox; Send As looks as if the mailbox itself sent it. With both, Send As wins.

A meeting app needs an app setting and an operating system permission

A user joins a call and their camera tile stays black, though the camera worked in the morning. Meeting faults sit in two layers: what the app has chosen, and what the operating system allows it. Check them in this order:

Symptom First checks
Black or missing video the camera chosen in the app; the system’s camera permission
Others cannot hear you mute, the microphone chosen in the app; the system’s microphone permission
Shared screen is blank, on a Mac the app’s Screen & System Audio Recording permission
Headset sounds worse once the mic is on the headset’s hands-free mode, peripherals lesson

On Windows the camera switches sit under Settings, Privacy & security, Camera: Camera access for the device, then separate switches for Microsoft Store apps, one by one, and for desktop apps, all together, because Microsoft says desktop apps cannot be allowed one at a time. The microphone page has the same switches. When the device’s switch will not change, Microsoft says an administrator on the device has to change it. On a Mac, System Settings, Privacy & Security lists Camera, Microphone and Screen & System Audio Recording, each with a switch per app; the macOS permissions lesson explains the TCC: The name technicians use for the macOS privacy database, after the com.apple.TCC payload that manages it: the store of each user's decisions about which apps may reach the camera, the microphone, the screen and personal files. More in “macOS security: FileVault, SIP, permissions” database behind them. A call in a browser needs one more permission: Chrome asks each site for the camera and microphone, and at work an administrator may set those choices.

When the settings look right, test the hardware away from the meeting app, so you know which layer is left:

Webcam Test Check in the browser that the camera works and what resolution and frame rate it really sends; the picture stays on your device. Microphone Test See and hear whether the microphone works and how loud it is, with a short recording to play back; the sound is not uploaded.

Checkpoint

Check: Camera, microphone and screen sharing

  1. Question 1 of 2 On Windows, a user's camera works in the Camera app but stays black in a meeting app installed from the internet. Where do you look?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Settings, Privacy & security, Camera: the switch that lets desktop apps use the camera

    Check the app's own camera choice first, then the system's permission. On Windows the camera page has a switch for the device, one per Store app, and one for all desktop apps.

  2. Question 2 of 2 On a Mac, others in a call see a blank window when the user shares the screen, though camera and microphone work. What is missing?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The meeting app's switch under Privacy & Security, Screen & System Audio Recording

    macOS lists Camera, Microphone and Screen & System Audio Recording as separate permissions, each with a switch per app, so one can be on while another is off.

Practise: read a message’s results and its slowest hop

Most mail tickets come down to the two readings the headers program made. Write them as functions that take any message’s headers, ignore results written by the wrong server and read the route from the bottom up:

Exercise · Medium · Python

Read a message's results and its slowest hop

Write two functions that read the headers of one message, given as text with one header per line, the way the lesson's headers.py reads them.

auth_results(headers, server) returns a dict with exactly the keys "spf", "dkim" and "dmarc":

  • read only the topmost Authentication-Results header whose first part, before the first ;, is server (compare the server names without regard to case), and ignore every header that another server added;
  • each value is the word after method=, such as pass, fail or softfail;
  • a method that header does not mention, and every method when no header from server exists, is "none".

slowest_hop(headers) returns (by_host, seconds) for the hop that took longest, or None when the message has fewer than two Received lines:

  • every server adds its Received line at the top, so read them from the bottom up;
  • the host after by names the server that added the line, and the date-time after the last ; is when it did (email.utils.parsedate_to_datetime reads it);
  • a hop's time is the difference, in whole seconds, between a line and the one below it, and the hop belongs to the by host of the upper line;
  • when two hops take the same time, keep the older one, the one nearer the bottom.

The sample tests cover the lesson's billing quote, a header added by the sending service, a missing DKIM result and a server name in other case, a message with no results from the receiver, a result for a method other than the three, equal hops and a single Received line.

Starter code · read_headers.py

from email.utils import parsedate_to_datetime


def header_values(headers, name):
    """The values of every header line called `name`, top first."""
    prefix = name.lower() + ":"
    return [line.split(":", 1)[1].strip() for line in headers.splitlines() if line.lower().startswith(prefix)]


def auth_results(headers, server):
    """spf, dkim and dmarc results from the topmost Authentication-Results header that `server` added."""
    # Trusts the first Authentication-Results header, whoever added it, and leaves missing methods out.
    values = header_values(headers, "Authentication-Results")
    found = {}
    if values:
        for part in values[0].split(";")[1:]:
            method, _, outcome = part.strip().partition("=")
            found[method] = outcome.split()[0]
    return found


def slowest_hop(headers):
    """(by host, seconds) of the hop that took longest, or None with fewer than two Received lines."""
    # Reads the Received lines top-down, so every gap comes out negative.
    stamps = []
    for value in header_values(headers, "Received"):
        route, when = value.rsplit(";", 1)
        stamps.append((route.split(" by ", 1)[1].split()[0], parsedate_to_datetime(when.strip())))
    best = None
    for i in range(1, len(stamps)):
        seconds = int((stamps[i][1] - stamps[i - 1][1]).total_seconds())
        if best is None or seconds > best[1]:
            best = (stamps[i][0], seconds)
    return best
The sample tests · test_read_headers.py
from read_headers import auth_results, slowest_hop

QUOTE = """\
Authentication-Results: mx.example.org; spf=pass smtp.mailfrom=bounces.example.net; dkim=pass header.d=example.net; dmarc=fail header.from=example.com
Received: from mailer.example.net (mailer.example.net [198.51.100.40]) by mx.example.org with ESMTPS; Mon, 05 Oct 2099 10:14:52 +0530
Authentication-Results: mailer.example.net; dkim=pass header.d=example.net; dmarc=pass
Received: from queue.example.net ([198.51.100.41]) by mailer.example.net with ESMTP; Mon, 05 Oct 2099 10:03:10 +0530
Received: from app.example.net ([198.51.100.42]) by queue.example.net with ESMTP; Mon, 05 Oct 2099 10:03:08 +0530
From: Quotes <quotes@example.com>"""

NO_DKIM = """\
Authentication-Results: MX.example.org; spf=softfail smtp.mailfrom=example.com; dmarc=fail header.from=example.com
Received: from out.example.com ([192.0.2.25]) by mx.example.org with ESMTPS; Mon, 05 Oct 2099 09:00:30 +0530
Received: from pc-12.example.com ([192.0.2.71]) by out.example.com with ESMTPSA; Mon, 05 Oct 2099 09:00:00 +0530
From: Accounts <accounts@example.com>"""

EVEN = """\
Received: from b.example.net ([198.51.100.2]) by c.example.org with ESMTPS; Mon, 05 Oct 2099 11:00:20 +0530
Received: from a.example.net ([198.51.100.1]) by b.example.net with ESMTP; Mon, 05 Oct 2099 11:00:10 +0530
Received: from app.example.net ([198.51.100.3]) by a.example.net with ESMTP; Mon, 05 Oct 2099 11:00:00 +0530"""


def test_only_the_receiving_servers_header_counts():
    assert auth_results(QUOTE, "mx.example.org") == {"spf": "pass", "dkim": "pass", "dmarc": "fail"}


def test_another_servers_header_is_read_only_when_asked_for():
    assert auth_results(QUOTE, "mailer.example.net") == {"spf": "none", "dkim": "pass", "dmarc": "pass"}


def test_a_missing_method_reads_none_and_the_server_name_ignores_case():
    assert auth_results(NO_DKIM, "mx.example.org") == {"spf": "softfail", "dkim": "none", "dmarc": "fail"}


def test_no_header_from_that_server():
    assert auth_results(EVEN, "mx.example.org") == {"spf": "none", "dkim": "none", "dmarc": "none"}


def test_a_method_other_than_the_three_is_left_out():
    extra = "Authentication-Results: mx.example.org; arc=pass; spf=pass smtp.mailfrom=example.com; dkim=pass header.d=example.com; dmarc=pass header.from=example.com"
    assert auth_results(extra, "mx.example.org") == {"spf": "pass", "dkim": "pass", "dmarc": "pass"}


def test_slowest_hop_of_the_quote():
    assert slowest_hop(QUOTE) == ("mx.example.org", 702)


def test_equal_hops_keep_the_older_one():
    assert slowest_hop(EVEN) == ("b.example.net", 10)


def test_one_received_line_has_no_hop():
    one = "Received: from a.example.net ([198.51.100.1]) by b.example.net with ESMTP; Mon, 05 Oct 2099 11:00:10 +0530"
    assert slowest_hop(one) is None
A hint

Start auth_results with every method set to "none", then look for the first header whose part before ; matches the server; split the rest on ; and each piece on the first =. For slowest_hop, reverse the list of Received values before you pair each line with the one before it, and replace the best hop only when a gap is strictly longer.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then check what you know, starting with the idea you are least sure of:

Practice that adapts to you

Practise this lesson

18 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 18 Which port does IMAP over TLS use?

    Choose one answer.

    Show the answer: Question 1 of 18

    Answer: 993

    Reading and sending are separate settings: IMAP on 993 or POP3 on 995 to read, and 465 or 587 to send.

  2. Question 2 of 18 On a hotel network a guest's mail app cannot send on 587, while web pages and reading mail on 993 work. Which outgoing setting is worth trying?

    Choose one answer.

    Show the answer: Question 2 of 18

    Answer: Port 465 with TLS, a different submission port that the network may allow

    Reading working while sending fails points at the outgoing port, not the account. 465 with implicit TLS is the other submission port; if neither works, the guest can send from the web mail page until they leave that network.

  3. Question 3 of 18 Who can switch Basic authentication back on for an Exchange Online tenant now?

    Choose one answer.

    Show the answer: Question 3 of 18

    Answer: Nobody, neither the tenant's admins nor Microsoft support

    Basic authentication is off for Exchange ActiveSync, POP, IMAP, EWS, Autodiscover, Outlook and more, and cannot be switched back on, so every fix is on the app's side.

  4. Question 4 of 18 A desktop IMAP app set up with a password keeps prompting. The maker says its latest version supports OAuth 2.0. What do you do?

    Choose one answer.

    Show the answer: Question 4 of 18

    Answer: Update the app and change the account to its OAuth 2.0 sign-in

    The fix order is name the app, update it, add the account again with modern sign-in, then read the sign-in log if the prompt still returns.

  5. Question 5 of 18 A colleague suggests an app password for an old app that cannot do two-step verification. Will it work with Exchange Online?

    Choose one answer.

    Show the answer: Question 5 of 18

    Answer: No: the end of Basic authentication also stopped app passwords for such apps

    An app password was a Basic authentication password by another name. Apps that cannot sign in with OAuth 2.0 are replaced.

  6. Question 6 of 18 In which order do you read a message's Received lines to follow its route?

    Choose one answer.

    Show the answer: Question 6 of 18

    Answer: From the bottom up, because each server adds its line at the top

    Received lines stack up at the top, so the route runs from the bottom line to the top one, and the gaps between their times are the hops' delays.

  7. Question 7 of 18 A message's Received lines, top first, carry the times 10:14:52, 10:03:10 and 10:03:08. How many seconds did its slowest hop take?

    Type a number.

    A hint

    Read from the bottom up and subtract each time from the one above it.

    Show the answer: Question 7 of 18

    Answer: 702 seconds

    From the bottom: 10:03:08 to 10:03:10 is 2 s, and 10:03:10 to 10:14:52 is 11 min 42 s, which is 702 s, the hop headers.py found for the billing quote.

  8. Question 8 of 18 Why does headers.py read only the Authentication-Results header that names mx.example.org?

    Choose one answer.

    Show the answer: Question 8 of 18

    Answer: Because only the receiving server's own header records checks you can trust

    The receiving server writes its verdict under its own name. A header that another server wrote is a claim, not a check, which is why RFC 8601 has servers delete copies of their own name that arrive from outside.

  9. Question 9 of 18 At most how many DNS-querying terms (include, a, mx, ptr, exists, redirect) may one SPF check use before it returns permerror?

    Type a number.

    Show the answer: Question 9 of 18

    Answer: 10 terms

    RFC 7208 caps them at 10 for the whole check, including the terms of every included record, so a record that counts 4 on its own can still pass the limit through its includes.

  10. Question 10 of 18 Under relaxed alignment, which DKIM signing domain aligns with From: billing@example.com?

    Choose one answer.

    Show the answer: Question 10 of 18

    Answer: mail.example.com

    Relaxed alignment, the default, needs the same organisational domain; strict alignment needs the exact domain, so mail.example.com would fail under adkim=s.

  11. Question 11 of 18 A domain's DMARC record reads p=reject; pct=10. Its owner believes only 10 % of failing mail is refused. What does RFC 9989 say?

    Choose one answer.

    Show the answer: Question 11 of 18

    Answer: pct was removed and is ignored, so the record asks receivers that follow RFC 9989 to reject all failing mail; t=y is the test mode now

    txt_records.py listed pct as ignored. The policy stays a request, and each receiver decides what it finally does with failing mail. An owner who wants a gentler start uses t=y, which RFC 9989 says applies one level less than the stated policy: reject becomes quarantine.

  12. Question 12 of 18 When do the UK's clocks go back, according to GOV.UK?

    Choose one answer.

    Show the answer: Question 12 of 18

    Answer: At 2am on the last Sunday in October

    The UK runs on UTC+01:00 in summer and UTC+00:00 in winter, so meetings made in London move for anyone on a fixed offset twice a year.

  13. Question 13 of 18 A meeting made in a UTC+05:30 office for 15:00 office time shows at 10:30 in London. After the UK clocks go back, when does London see it?

    Choose one answer.

    Show the answer: Question 13 of 18

    Answer: 09:30

    meeting_times.py printed 10:30 before the change and 09:30 after it. The meeting did not move; London's clock did.

  14. Question 14 of 18 Which permission lets a delegate send mail that appears to come from the shared mailbox itself?

    Choose one answer.

    Show the answer: Question 14 of 18

    Answer: Send As

    Full Access is for working in a mailbox, Send As and Send on Behalf for sending from it, and they are granted separately.

  15. Question 15 of 18 A delegate can open the shared mailbox and read every message, but sending from it fails. Which permission do they have?

    Choose one answer.

    Show the answer: Question 15 of 18

    Answer: Full Access only

    Reading and sending are separate grants. Ask whether the team wants replies to look like the mailbox (Send As) or to show the person (Send on Behalf) before adding one.

  16. Question 16 of 18 In what order does the lesson check a camera or microphone fault in a meeting?

    Choose one answer.

    Show the answer: Question 16 of 18

    Answer: The app's own device choice, then the system's and browser's permissions, then the hardware on its own

    Two layers decide what a meeting app gets: what it has chosen and what the system lets it use. Check both before testing the camera or microphone alone.

  17. Question 17 of 18 A call in Chrome shows no camera, while the same camera works in another app. What do you check in the browser?

    Choose one answer.

    Show the answer: Question 17 of 18

    Answer: The site's camera permission, which Chrome asks for site by site

    A browser adds one more permission layer per site. At work an administrator may set those choices, and then the user cannot change them.

  18. Question 18 of 18 On Windows, a user wants only one meeting app installed from the internet to use the camera, and no other desktop app. What do you tell them?

    Choose one answer.

    Show the answer: Question 18 of 18

    Answer: Windows cannot do that: desktop apps share one switch, all of them or none

    Store apps get a switch each; desktop apps, including those installed from the internet or by IT, get one switch together. A user who needs finer control asks whether a Store version of the app exists.

Interview questions

Warm-up (fresher): a mail app’s settings offer port 465 or port 587 for sending. What is the difference?

Both are for message submission: an app handing outgoing mail to its own mail service after signing in. On 587, the submission port of RFC 6409, the connection starts in clear text and the app asks for STARTTLS to encrypt it before it signs in or sends anything. On 465, which IANA lists as submissions, TLS starts from the first byte, which RFC 8314 calls implicit TLS and recommends in preference to STARTTLS. Either is fine when the service supports it and the app is set to match: 465 with TLS or SSL, 587 with STARTTLS. Port 25 is the one not to use in an app, because it is for servers relaying mail to each other. Reading mail is a separate setting: IMAP on 993 or POP3 on 995.

The next two tickets need more than this lesson’s examples: a forwarding rule to a personal address that stopped overnight, and an old in-house tool built on Exchange Web Services. Note your first move on each before its page shows the opening step:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A sales manager's inbox rule forwarded every message to a personal address outside the company. Overnight it stopped, and each forward now comes back as a bounce. The manager wants it working again today. What do you check, and what do you say?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    The finance team's old in-house tool reads invoices from a shared mailbox through Exchange Web Services. Its author has left, and a vendor tells you EWS is going away. How do you find everything that still uses EWS, and what does a lasting fix look like?

    Model answer, Pro. First step and follow-ups

Flashcards · 7 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Which ports does a mail app use to send and to read mail?

    Submission on 587 with STARTTLS or on 465 with implicit TLS; reading with IMAP on 993 or POP3 on 995. Port 25 is for servers relaying mail between themselves.

  2. What is the difference between ports 465 and 587 for sending mail?

    On 587 the connection starts in clear text and the app asks for STARTTLS before it signs in. On 465 TLS starts from the first byte, which RFC 8314 calls implicit TLS.

  3. A mail app keeps asking for an Exchange Online mailbox's password, and the right one changes nothing. What is going on?

    It is using Basic authentication, which Exchange Online no longer accepts and nobody can switch back on. Use a current app, add the account again with modern sign-in, or replace the app.

  4. How do you read a message's route and find its slowest hop?

    Read the Received lines from the bottom up: each server adds one with a time, so the biggest gap between two neighbouring lines is the slowest hop. Only the Authentication-Results header written by the receiving server counts.

  5. SPF and DKIM both pass for a sending service, yet DMARC fails. Why?

    DMARC passes only when a domain that SPF or DKIM confirmed aligns with the From domain. The service needs an aligned DKIM signature or envelope sender, not an extra include in the From domain's SPF record.

  6. Why does a recurring meeting shift for people in another country?

    It keeps the local time of the zone it was created in, so when that zone's clocks change, it moves by an hour for people whose own clocks stay put.

  7. In what order are camera, microphone and screen-sharing faults checked?

    The app's settings first, then the operating system's privacy permissions and the browser's site permissions, and the hardware on its own last.

Key takeaways

  • Apps send mail by message submission on 587 with STARTTLS or 465 with implicit TLS, read it with IMAP on 993 or POP3 on 995, and leave port 25 to servers relaying mail between themselves.
  • A mail app that keeps asking for a password is using Basic authentication, which Exchange Online no longer accepts and nobody can switch back on, so the fix is a current app, the account added again with modern sign-in, or a replacement.
  • Received lines read from the bottom up give a message’s route and its slowest hop, and only the Authentication-Results header written by the receiving server counts.
  • DMARC passes only when a domain that SPF or DKIM confirmed aligns with the From domain, so a sending service needs an aligned DKIM signature or envelope sender, not an extra include in the From domain’s SPF record.
  • A recurring meeting keeps the local time of the zone it was created in, so it moves for people elsewhere when that zone’s clocks change, while Send As and Send on Behalf decide how mail from a shared mailbox looks.
  • Camera, microphone and screen-sharing faults are checked in the app first, then in the operating system’s privacy permissions and the browser’s site permissions, and the hardware is tested on its own last.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.