Deploying autodiscover records in public DNS simplifies adding a Zimbra account to any device. This blog post will show you how.
Introduction
Yes, we like the Zimbra web client very much (and are even starting to like the Modern UI too — though that’s another blog post…). But, sometimes you need to configure an IMAP, ActiveSync, CardDAV or CalDAV email client. Without autodiscover records, it’s a pain to add such accounts. It forces users to enter a lot of information, and presents opportunities for users to make mistakes, causing frustration for the users and more tickets for the Help Desk.
With proper autodiscover records in public DNS, configuring those new IMAP, ActiveSync, CardDAV or CalDAV email clients is seamless. You’ll need to enter your username and password, and on iOS for example, choose “Manual Configuration”. After doing so, you’ll be taken immediately to the Verify screen, which will immediately display successful verification checkmarks.
You are done! Easy-peasy!
Technical Background
Autodiscover records tell an email, calendar or address book client where to go to get its data.
Autodiscover records are deployed in the same email domain where the user’s email address lives. So if the user’s email address is like john.doe@mycompany.com, you’d need to deploy the autodiscover records in the public DNS zone file for the mycompany.com domain.
The FQDN in each autodiscover record doesn’t have to be on the same domain; that FQDN is typically the Zimbra Proxy server (or F5, AWS NLB or other Layer 4 load balancer) that john.doe@mycompany.com would normally connect to if using the Zimbra web client.
For our hosting environment, our Amazon Web Services Layer 4 Network Load Balancer has the FQDN “mail.missioncriticalemail.com” (and is also the PublicServiceHostname for all of the domains we host).
The DNS record examples below are all in BIND9 format. Do note the period at the end of the FQDN at the end of most of the records. In BIND9, this is required to prevent BIND9 from appending the domain to the entry. In other words, without the period at the end, BIND9 thinks the entry is a hostname, and appends the domain name of the domain where you are adding these records. To be clear, if we were deploying the below autodiscover records on the mycompany.com domain, and didn’t include the period, the FQDN presented to the client would be “mail.missioncriticalemail.com.mycompany.com“. No server lives there, so autodiscover would fail.
Implementation – IMAP
We need two autodiscover records for IMAP: one for the IMAP client to fetch email, and one for the IMAP client to send email via SMTP-Auth, using the Submission port TCP/587. Both are SRV records, and the records should look like those below. Note that it’s a good idea to start with a short TTL like we have done here (300 seconds), and then when you are comfortable that you got it right and that things are working OK, you should increase the TTL to like a day or so (86400 seconds for example).
_imaps._tcp 300 IN SRV 0 0 993 mail.missioncriticalemail.com. _submission._tcp 300 IN SRV 0 0 587 mail.missioncriticalemail.com.
Implementation – ActiveSync
Here we also need two records, but one is a CNAME and one is an SRV. Same deal… start with a short TTL and then increase once you’ve tested and satisfied yourself that all is good.
autodiscover 300 IN CNAME mail.missioncriticalemail.com. _autodiscover._tcp 300 IN SRV 0 0 443 mail.missioncriticalemail.com.
Implementation – DAV
The DAV protocol is well-supported by Zimbra, and also by Apple products and almost all third-party email/calender/contacts clients with which I am familiar. If you are running Open Source Zimbra and don’t have ActiveSync, deploying IMAP, CardDAV and CalDAV on your workstation or mobile is a reliable way to go — especially if you have a large mailbox. (In our experience, ActiveSync is a bit fragile and doesn’t handle large mailboxes all that well all the time, especially on high-latency cellular connections).
For each of CardDAV and CalDav, you need two autodiscover records. The first as above tells the client where to find the server holding the user’s account. The second is to tell the client where on the server the user’s data lives within the user’s account:
_caldavs._tcp 300 IN SRV 0 0 443 mail.missioncriticalemail.com. _caldavs._tcp 300 IN TXT "path=/dav/%u" _carddavs._tcp 300 IN SRV 0 0 443 mail.missioncriticalemail.com. _carddavs._tcp 300 IN TXT "path=/dav/%u"
Implementation – Thunderbird
Thunderbird is a special case, and it’s a bit more involved to configure autodiscovery for Thunderbird. You’ll need one additional DNS record and an https web site to host an XML file. We use an AWS S3 bucket with an AWS Cloudfront Distribution as it costs about 10 cents per month, but if you have a web server someplace that will work just fine too.
The Thunderbird autodiscover DNS record should be something like some of the previous records:
autoconfig 300 IN CNAME drb4hreabfxdi.cloudfront.net.
In the case above, the CNAME points to the Cloudfront distribution, which is the actual webserver hosting the needed xml file (in two locations, for maximum compatibility). Configuring a Cloudfront distribution is beyond the scope of this blog post, but we are happy to help customers do this if they wish.
The web site should have two directories: .well-known and mail. The .well-known directory should have a subdirectory: mail. In both the /mail and /.well-known/mail directories, you’ll need to deposit the same xml file, named config-v1.1.xml. The file should be available, to use our case as an example, at like https://autoconfig.missioncriticalemail.com/mail/config-v1.1.xml. The xml file contents look like what’s below — using our Zimbra hosting environment as an example — be sure to change the attributes to match your environment!
<?xml version="1.0" encoding="UTF-8"?> <clientConfig version="1.1"> <emailProvider id="missioncriticalemail.com"> <domain>missioncriticalemail.com</domain> <displayName>Mission Critical Email</displayName> <displayShortName>MCE</displayShortName> <!-- IMAP Configuration --> <incomingServer type="imap"> <hostname>mail.missioncriticalemail.com</hostname> <port>993</port> <socketType>SSL</socketType> <authentication>password-cleartext</authentication> <username>%EMAILADDRESS%</username> </incomingServer> <!-- SMTP Configuration --> <outgoingServer type="smtp"> <hostname>mail.missioncriticalemail.com</hostname> <port>587</port> <socketType>STARTTLS</socketType> <authentication>password-cleartext</authentication> <username>%EMAILADDRESS%</username> </outgoingServer> <!-- Additional SMTP option (if you support port 465) --> <outgoingServer type="smtp"> <hostname>mail.missioncriticalemail.com</hostname> <port>465</port> <socketType>SSL</socketType> <authentication>password-cleartext</authentication> <username>%EMAILADDRESS%</username> </outgoingServer> </emailProvider> </clientConfig>
In our environment, we don’t allow port 465, so we actually didn’t include the last stanza.
It’s always good to “trust but verify”, so we downloaded the latest 142 version of Thunderbird for Mac (Arm). We added our username and password, and then Thunderbird let us select which address books and calendars to connect to:
Implementation – Email Domain Differs From the Hosting Domain
If you are a Zimbra Hosting Provider (BSP), or your company hosts multiple domains, it may be that your email is on the “mycompany.com” domain, but the Zimbra web client (and IMAP, DAV, etc.) is located at like “securemail.othercompany.com”.
When that’s the case, the recommendations above will generate a certificate error, so we need to make an adjustment. When the email domain is the same as the hosting domain, then what we suggested above for the autodiscover SRV record is correct. But when the email domain is different from the hosting domain, we need to do a little trick to have the email domain’s _autodiscover._tcp SRV record reference the hosting domain’s autodiscover CNAME record.
Here’s what we suggested when the email domain and the hosting domain are the same:
_autodiscover._tcp 300 IN SRV 0 0 443 mail.missioncriticalemail.com.
But, when we are hosting, say, the domain “mycompany.com”, if the above record was added to mycompany.com’s zone file, that won’t work. Instead, this should be used in the mycompany.com zone file instead:
_autodiscover._tcp 300 IN SRV 0 0 443 autodiscover.missioncriticalemail.com.
What that does is redirect first to “autodiscover.missioncriticalemail.com”, which then points to the Zimbra endpoint, as you can see here:
mstone@MacBook-Pro-2 ~ % host autodiscover.missioncriticalemail.com autodiscover.missioncriticalemail.com is an alias for mail.missioncriticalemail.com. mail.missioncriticalemail.com has address 54.164.37.80 mail.missioncriticalemail.com has address 18.211.117.59
DNS Validation Testing Script
OK, so autodiscover is really awesome — when things are working well — but how do you know if you are not a DNS expert that you got everything right before you let it all loose on your own customers or end users?
We wouldn’t leave you in the lurch! Here’s a bash script you can use to test all of your autodiscover records against your own Zimbra system! The script will ask for a username (the email domain) and the hosting domain. It will then test if the Zimbra services are reachable by following the autodiscover records.
#!/bin/bash # Zimbra Autodiscover DNS Configuration Validator (V17) # Validates autodiscover-related DNS and outputs everything discovered in BIND9 format. # No password / authentication required. # Works on macOS (Bash 3.2) and Linux. # Copyright 2025 Mission Critical Email, LLC - All rights reserved. # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # === Colors === RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' # === Arrays === declare -a PASSED_TESTS FAILED_TESTS NEEDS_INVESTIGATION BIND9_RECORDS add_test_result() { case $2 in PASS) PASSED_TESTS+=("$1");; FAIL) FAILED_TESTS+=("$1");; INVESTIGATE) NEEDS_INVESTIGATION+=("$1");; esac; } add_bind9_record() { BIND9_RECORDS+=("$1"); } extract_srv_hostname() { [ -n "$1" ] && echo "$1" | awk '{print $4}' | sed 's/\.$//'; } # --- Ensure trailing dot for BIND9 --- fqdn_dot() { local s="$1"; [ -n "$s" ] || { echo ""; return; }; [[ "$s" == *"." ]] && echo "$s" || echo "${s}."; } # === DNS utilities === resolve_cname_chain() { local host="$1" seen=" $1 " local current="$1" while :; do local cname cname=$(dig CNAME "$current" +short +time=5 +tries=1 | head -1) if [ -n "$cname" ]; then cname=$(echo "$cname" | sed 's/\.$//') add_bind9_record "$(fqdn_dot "$current") IN CNAME $(fqdn_dot "$cname")" [[ "$seen" == *" $cname "* ]] && { current="$cname"; break; } seen="$seen$cname " current="$cname" else break fi done echo "$current" } resolve_srv_records() { dig SRV _${1}._tcp."$2" +short +time=5 +tries=1; } resolve_txt_records() { dig TXT _${1}._tcp."$2" +short +time=5 +tries=1; } resolve_mx_records() { dig MX "$1" +short +time=5 +tries=1; } resolve_spf_records() { dig TXT "$1" +short +time=5 +tries=1 | grep -i 'v=spf1'; } resolve_host_records() { local host="$1" local final final=$(resolve_cname_chain "$host") [ -z "$final" ] && final="$host" local arec arec=$(dig A "$final" +short +time=5 +tries=1) if [ -n "$arec" ]; then while IFS= read -r ip; do [ -n "$ip" ] && add_bind9_record "$(fqdn_dot "$final") IN A$ip"; done <<< "$arec" fi local aaaa aaaa=$(dig AAAA "$final" +short +time=5 +tries=1) if [ -n "$aaaa" ]; then while IFS= read -r ip6; do [ -n "$ip6" ] && add_bind9_record "$(fqdn_dot "$final") IN AAAA $ip6"; done <<< "$aaaa" fi echo "$final" } # === Prompt === echo -e "${BLUE}Zimbra Autodiscover DNS Configuration Validator${NC}" echo "=============================================" read -p "Enter email address (name@domain): " EMAIL [[ ! "$EMAIL" =~ ^[^@]+@[^@]+\.[^@]+$ ]] && { echo -e "${RED}Invalid email${NC}"; exit 1; } USERNAME=${EMAIL%@*}; DOMAIN=${EMAIL#*@} echo -e "\nEnter the domain on which your provider's services are based." echo "For example, if webmail is available at 'securemail.provider.com', you would enter 'provider.com'." read -p "Provider base domain: " PROVIDER_DOMAIN [[ ! "$PROVIDER_DOMAIN" =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]] && { echo -e "${RED}Invalid domain${NC}"; exit 1; } echo -e "\n${BLUE}Validating autodiscover DNS configuration for $DOMAIN${NC}" echo "Expected provider domain: $PROVIDER_DOMAIN" echo "=============================================" # === Discovery + Validation === MX_RESULT=$(resolve_mx_records "$DOMAIN") [ -n "$MX_RESULT" ] && while IFS= read -r line; do add_bind9_record "$(fqdn_dot "$DOMAIN") IN MX $line"; done <<< "$MX_RESULT" SPF_RESULT=$(resolve_spf_records "$DOMAIN") [ -n "$SPF_RESULT" ] && while IFS= read -r line; do add_bind9_record "$(fqdn_dot "$DOMAIN") IN TXT $line"; done <<< "$SPF_RESULT" for service in imaps submission caldavs carddavs autodiscover; do result=$(resolve_srv_records "$service" "$DOMAIN") if [ -n "$result" ]; then while IFS= read -r line; do add_bind9_record "$(fqdn_dot "_${service}._tcp.$DOMAIN") IN SRV $line"; done <<< "$result" target=$(extract_srv_hostname "$(echo "$result" | head -1)") if [ -n "$target" ]; then if [[ "$target" == *"$PROVIDER_DOMAIN" ]]; then add_test_result "SRV $service → $target" "PASS" else add_test_result "SRV $service → $target" "FAIL" fi fi else add_test_result "SRV $service" "FAIL" fi done for service in caldavs carddavs; do result=$(resolve_txt_records "$service" "$DOMAIN") if [ -n "$result" ]; then while IFS= read -r line; do add_bind9_record "$(fqdn_dot "_${service}._tcp.$DOMAIN") IN TXT $line"; done <<< "$result" add_test_result "TXT $service" "PASS" else add_test_result "TXT $service" "FAIL" fi done AUTODISCOVER_HOST=$(resolve_host_records "autodiscover.$DOMAIN") AUTOCONFIG_HOST=$(resolve_host_records "autoconfig.$DOMAIN") if [ -n "$AUTODISCOVER_HOST" ]; then if [[ "$AUTODISCOVER_HOST" == *"$PROVIDER_DOMAIN" ]]; then add_test_result "Autodiscover host within provider domain" "PASS" else add_test_result "Autodiscover host outside provider domain" "FAIL" fi fi # Service availability resp=$(curl -I -s --max-time 10 "https://$AUTODISCOVER_HOST/autodiscover/autodiscover.xml" | head -1) [[ "$resp" =~ (200|401|405) ]] && add_test_result "ActiveSync Autodiscover Service" "PASS" || add_test_result "ActiveSync Autodiscover Service" "FAIL" resp=$(curl -I -s --max-time 10 "https://$AUTOCONFIG_HOST/mail/config-v1.1.xml" | head -1) if [[ "$resp" =~ (200) ]]; then add_test_result "Thunderbird Autoconfig Service" "PASS" elif [[ "$resp" =~ (404) ]]; then add_test_result "Thunderbird Autoconfig Service" "FAIL" else add_test_result "Thunderbird Autoconfig Service" "INVESTIGATE"; fi for service in carddav caldav; do resp=$(curl -I -s --max-time 10 "https://$PROVIDER_DOMAIN/.well-known/$service" | head -1) [[ "$resp" =~ (200|301|302|401) ]] && add_test_result "$service well-known URI" "PASS" || add_test_result "$service well-known URI" "FAIL" done resp=$(curl -I -s --max-time 10 "https://$AUTODISCOVER_HOST/service/dav/home/" | head -1) [[ "$resp" =~ (200|301|302|401|405) ]] && add_test_result "DAV Service Discovery" "PASS" || add_test_result "DAV Service Discovery" "FAIL" # === BIND9 Output === echo -e "\n${BLUE}=============================================${NC}" echo -e "${BLUE}BIND9 ZONE FILE FORMAT${NC}" echo -e "${BLUE}=============================================${NC}" echo -e "; Discovered records for $DOMAIN" if [ ${#BIND9_RECORDS[@]} -gt 0 ]; then printf "%s\n" "${BIND9_RECORDS[@]}" | awk '!seen[$0]++' else echo "; (no records discovered)" fi # === Validation Report === echo -e "\n${BLUE}=============================================${NC}" echo -e "${BLUE}VALIDATION REPORT${NC}" echo -e "${BLUE}=============================================${NC}" if [ ${#PASSED_TESTS[@]} -gt 0 ]; then echo -e "\n${GREEN}✓ PASSED:${NC}"; for t in "${PASSED_TESTS[@]}"; do echo -e " ✓ $t"; done; fi if [ ${#FAILED_TESTS[@]} -gt 0 ]; then echo -e "\n${RED}✗ FAILED:${NC}"; for t in "${FAILED_TESTS[@]}"; do echo -e " ✗ $t"; done; fi echo -e " ${YELLOW}Note:${NC} Missing .well-known URIs may not break Outlook/Thunderbird but will break Apple and standards-compliant DAV clients." if [ ${#NEEDS_INVESTIGATION[@]} -gt 0 ]; then echo -e "\n${YELLOW}⚠ INVESTIGATE:${NC}"; for t in "${NEEDS_INVESTIGATION[@]}"; do echo -e " ⚠ $t"; done; fi # === Final line === echo -e "\nTesting Complete.\n" exit 0
The script will output to the console the results of its tests, and add a summary at the end. If all is good, the summary will look like this:
Zimbra Autodiscover DNS Configuration Validator ============================================= Enter email address (name@domain): test_user@missioncriticalemail.com Enter the domain on which your provider's services are based. For example, if webmail is available at 'securemail.provider.com', you would enter 'provider.com'. Provider base domain: missioncriticalemail.com Validating autodiscover DNS configuration for missioncriticalemail.com Expected provider domain: missioncriticalemail.com ============================================= ============================================= BIND9 ZONE FILE FORMAT ============================================= ; Discovered records for missioncriticalemail.com missioncriticalemail.com. IN MX 10 mail.missioncriticalemail.com. missioncriticalemail.com. IN TXT "v=spf1 ip4:35.171.80.173/32 ip4:35.173.158.175/32 ip4:3.209.146.84/32 ~all" _imaps._tcp.missioncriticalemail.com. IN SRV 0 0 993 mail.missioncriticalemail.com. _submission._tcp.missioncriticalemail.com. IN SRV 0 0 587 mail.missioncriticalemail.com. _caldavs._tcp.missioncriticalemail.com. IN SRV 0 0 443 mail.missioncriticalemail.com. _carddavs._tcp.missioncriticalemail.com. IN SRV 0 0 443 mail.missioncriticalemail.com. _autodiscover._tcp.missioncriticalemail.com. IN SRV 0 0 443 mail.missioncriticalemail.com. _caldavs._tcp.missioncriticalemail.com. IN TXT "path=/dav/%u" _carddavs._tcp.missioncriticalemail.com. IN TXT "path=/dav/%u" ============================================= VALIDATION REPORT ============================================= ✓ PASSED: ✓ SRV imaps → mail.missioncriticalemail.com ✓ SRV submission → mail.missioncriticalemail.com ✓ SRV caldavs → mail.missioncriticalemail.com ✓ SRV carddavs → mail.missioncriticalemail.com ✓ SRV autodiscover → mail.missioncriticalemail.com ✓ TXT caldavs ✓ TXT carddavs ✓ Autodiscover host within provider domain ✓ ActiveSync Autodiscover Service ✓ Thunderbird Autoconfig Service ✓ carddav well-known URI ✓ caldav well-known URI ✓ DAV Service Discovery Note: Missing .well-known URIs may not break Outlook/Thunderbird but will break Apple and standards-compliant DAV clients. Testing Complete.
Conclusions
Configuring autodiscover records simplifies account setups for all users, and will reduce your Help Desk tickets for sure (unless of course you make a mistake in one of the records and deploy the record with a long TTL…)!
It’s straightforward in every DNS system we’ve used to configure these records, and the benefits are immediate.
If you’d like help with your autodiscover or other email-related DNS records, fill out the form and we’ll get right back in touch:
Your message has been sent
Hope that helps,
L. Mark Stone
Mission Critical Email LLC
22 July 2025
Updated 28 August 2025 to include a testing script and details on satisfying Thunderbird’s autodiscover needs
The information provided in this blog is intended for informational and educational purposes only. The views expressed herein are those of Mr. Stone personally. The contents of this site are not intended as advice for any purpose and are subject to change without notice. Mission Critical Email makes no warranties of any kind regarding the accuracy or completeness of any information on this site, and we make no representations regarding whether such information is up-to-date or applicable to any particular situation. All copyrights are reserved by Mr. Stone. Any portion of the material on this site may be used for personal or educational purposes provided appropriate attribution is given to Mr. Stone and this blog.