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:
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 FQDN of your Zimbra server, and (optionally) the password for the user account. It will then test if the Zimbra services are reachable by following the autodiscover records.
#!/bin/bash # 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. # Color codes for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Arrays to track test results declare -a PASSED_TESTS declare -a FAILED_TESTS declare -a NEEDS_INVESTIGATION # Function to add test result add_test_result() { local test_name="$1" local status="$2" case $status in "PASS") PASSED_TESTS+=("$test_name") ;; "FAIL") FAILED_TESTS+=("$test_name") ;; "INVESTIGATE") NEEDS_INVESTIGATION+=("$test_name") ;; esac } # Prompt user for email address echo -e "${BLUE}Zimbra Autodiscover DNS Configuration Validator${NC}" echo "=============================================" echo -n "Enter email address (name@domain): " read EMAIL # Validate email format if [[ ! "$EMAIL" =~ ^[^@]+@[^@]+\.[^@]+$ ]]; then echo -e "${RED}Error: Invalid email address format. Please use format: name@domain${NC}" exit 1 fi # Extract username and domain USERNAME=$(echo "$EMAIL" | cut -d'@' -f1) DOMAIN=$(echo "$EMAIL" | cut -d'@' -f2) # Ask for mail server hostname echo -n "Enter mail server hostname (e.g. mail.domain.com, imap.domain.com): " read MAIL_SERVER # Validate mail server format if [[ ! "$MAIL_SERVER" =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then echo -e "${RED}Error: Invalid mail server hostname format${NC}" exit 1 fi # Ask for password (optional) echo -n "Enter password for $EMAIL (press Enter to skip authenticated validation): " read -s PASSWORD echo echo "" echo -e "${BLUE}Validating autodiscover DNS configuration for $DOMAIN${NC}" echo "Username: $USERNAME" echo "Domain: $DOMAIN" echo "Mail Server: $MAIL_SERVER" echo "=============================================" echo -e "${BLUE}1. DNS SRV Records:${NC}" echo "IMAPS:" IMAPS_RESULT=$(dig SRV _imaps._tcp.$DOMAIN +short) echo "$IMAPS_RESULT" if [ ! -z "$IMAPS_RESULT" ]; then add_test_result "DNS SRV _imaps._tcp" "PASS" else add_test_result "DNS SRV _imaps._tcp" "FAIL" fi echo "SUBMISSION:" SUBMISSION_RESULT=$(dig SRV _submission._tcp.$DOMAIN +short) echo "$SUBMISSION_RESULT" if [ ! -z "$SUBMISSION_RESULT" ]; then add_test_result "DNS SRV _submission._tcp" "PASS" else add_test_result "DNS SRV _submission._tcp" "FAIL" fi echo "AUTODISCOVER:" AUTODISCOVER_RESULT=$(dig SRV _autodiscover._tcp.$DOMAIN +short) echo "$AUTODISCOVER_RESULT" if [ ! -z "$AUTODISCOVER_RESULT" ]; then add_test_result "DNS SRV _autodiscover._tcp" "PASS" else add_test_result "DNS SRV _autodiscover._tcp" "FAIL" fi echo "CalDAV:" CALDAV_RESULT=$(dig SRV _caldavs._tcp.$DOMAIN +short) echo "$CALDAV_RESULT" if [ ! -z "$CALDAV_RESULT" ]; then add_test_result "DNS SRV _caldavs._tcp" "PASS" else add_test_result "DNS SRV _caldavs._tcp" "FAIL" fi echo "CardDAV:" CARDDAV_RESULT=$(dig SRV _carddavs._tcp.$DOMAIN +short) echo "$CARDDAV_RESULT" if [ ! -z "$CARDDAV_RESULT" ]; then add_test_result "DNS SRV _carddavs._tcp" "PASS" else add_test_result "DNS SRV _carddavs._tcp" "FAIL" fi echo -e "\n${BLUE}2. DNS TXT Records (DAV paths):${NC}" echo "CalDAV path:" CALDAV_TXT_RESULT=$(dig TXT _caldavs._tcp.$DOMAIN +short) echo "$CALDAV_TXT_RESULT" if [ ! -z "$CALDAV_TXT_RESULT" ]; then add_test_result "DNS TXT _caldavs._tcp" "PASS" else add_test_result "DNS TXT _caldavs._tcp" "FAIL" fi echo "CardDAV path:" CARDDAV_TXT_RESULT=$(dig TXT _carddavs._tcp.$DOMAIN +short) echo "$CARDDAV_TXT_RESULT" if [ ! -z "$CARDDAV_TXT_RESULT" ]; then add_test_result "DNS TXT _carddavs._tcp" "PASS" else add_test_result "DNS TXT _carddavs._tcp" "FAIL" fi echo -e "\n${BLUE}3. DNS CNAME Records:${NC}" echo "Autodiscover CNAME:" CNAME_RESULT=$(dig CNAME autodiscover.$DOMAIN +short) echo "$CNAME_RESULT" if [ ! -z "$CNAME_RESULT" ]; then add_test_result "DNS CNAME autodiscover" "PASS" else add_test_result "DNS CNAME autodiscover" "FAIL" fi echo -e "\n${BLUE}4. Autodiscover Service Availability:${NC}" echo "ActiveSync/Exchange autodiscover endpoint:" ACTIVESYNC_ENDPOINT=$(curl -I -s "https://autodiscover.$DOMAIN/autodiscover/autodiscover.xml" | head -1) echo "$ACTIVESYNC_ENDPOINT" if [[ "$ACTIVESYNC_ENDPOINT" =~ "200"|"401"|"405" ]]; then add_test_result "ActiveSync Autodiscover Service" "PASS" else add_test_result "ActiveSync Autodiscover Service" "FAIL" fi echo "Thunderbird autoconfig endpoint:" THUNDERBIRD_ENDPOINT=$(curl -I -s "https://autoconfig.$DOMAIN/mail/config-v1.1.xml" | head -1) echo "$THUNDERBIRD_ENDPOINT" if [[ "$THUNDERBIRD_ENDPOINT" =~ "200" ]]; then add_test_result "Thunderbird Autoconfig Service" "PASS" elif [[ "$THUNDERBIRD_ENDPOINT" =~ "404" ]]; then add_test_result "Thunderbird Autoconfig Service" "FAIL" else add_test_result "Thunderbird Autoconfig Service" "INVESTIGATE" fi echo -e "\n${BLUE}5. Well-known URI Discovery:${NC}" echo "CardDAV well-known URI:" CARDDAV_WELLKNOWN=$(curl -I -s "https://$MAIL_SERVER/.well-known/carddav" | head -1) echo "$CARDDAV_WELLKNOWN" if [[ "$CARDDAV_WELLKNOWN" =~ "301"|"302"|"200" ]]; then add_test_result "CardDAV Well-known URI" "PASS" else add_test_result "CardDAV Well-known URI" "FAIL" fi echo "CalDAV well-known URI:" CALDAV_WELLKNOWN=$(curl -I -s "https://$MAIL_SERVER/.well-known/caldav" | head -1) echo "$CALDAV_WELLKNOWN" if [[ "$CALDAV_WELLKNOWN" =~ "301"|"302"|"200" ]]; then add_test_result "CalDAV Well-known URI" "PASS" else add_test_result "CalDAV Well-known URI" "FAIL" fi echo -e "\n${BLUE}6. DAV Service Discovery:${NC}" echo "DAV service endpoint availability:" DAV_ENDPOINT=$(curl -I -s "https://$MAIL_SERVER/service/dav/home/" | head -1) echo "$DAV_ENDPOINT" if [[ "$DAV_ENDPOINT" =~ "200"|"401"|"405" ]]; then add_test_result "DAV Service Discovery" "PASS" else add_test_result "DAV Service Discovery" "FAIL" fi # Optional authenticated validation if [ ! -z "$PASSWORD" ]; then echo -e "\n${BLUE}7. Authenticated Service Validation:${NC}" echo "Testing ActiveSync autodiscover response format..." ACTIVESYNC_AUTH_RESPONSE=$(curl -s -u "$EMAIL:$PASSWORD" -X POST "https://autodiscover.$DOMAIN/autodiscover/autodiscover.xml" \ -H "Content-Type: text/xml; charset=utf-8" \ -d "<?xml version=\"1.0\" encoding=\"utf-8\"?> <Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006\"> <Request> <EMailAddress>$EMAIL</EMailAddress> <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006</AcceptableResponseSchema> </Request> </Autodiscover>") if [[ "$ACTIVESYNC_AUTH_RESPONSE" =~ "Autodiscover" ]] && [[ ! "$ACTIVESYNC_AUTH_RESPONSE" =~ "401" ]]; then echo "ActiveSync response contains proper XML structure" add_test_result "ActiveSync Response Format" "PASS" elif [[ "$ACTIVESYNC_AUTH_RESPONSE" =~ "401" ]]; then echo "ActiveSync authentication failed - check credentials" add_test_result "ActiveSync Response Format" "FAIL" else echo "ActiveSync response format unexpected" add_test_result "ActiveSync Response Format" "INVESTIGATE" fi echo "Testing CardDAV service response..." CARDDAV_AUTH_RESPONSE=$(curl -s -u "$EMAIL:$PASSWORD" -X PROPFIND "https://$MAIL_SERVER/service/dav/home/" \ -H "Depth: 0" \ -H "Content-Type: text/xml" \ -d "<?xml version=\"1.0\" encoding=\"utf-8\"?> <propfind xmlns=\"DAV:\"> <prop> <resourcetype/> </prop> </propfind>") if [[ "$CARDDAV_AUTH_RESPONSE" =~ "multistatus" ]]; then echo "CardDAV service responding with proper DAV XML" add_test_result "CardDAV Service Response" "PASS" elif [[ "$CARDDAV_AUTH_RESPONSE" =~ "401" ]]; then echo "CardDAV authentication failed - check credentials" add_test_result "CardDAV Service Response" "FAIL" else echo "CardDAV service response unexpected" add_test_result "CardDAV Service Response" "INVESTIGATE" fi echo "Testing CalDAV service response..." CALDAV_AUTH_RESPONSE=$(curl -s -u "$EMAIL:$PASSWORD" -X PROPFIND "https://$MAIL_SERVER/service/dav/home/" \ -H "Depth: 0" \ -H "Content-Type: text/xml" \ -d "<?xml version=\"1.0\" encoding=\"utf-8\"?> <propfind xmlns=\"DAV:\"> <prop> <resourcetype/> </prop> </propfind>") if [[ "$CALDAV_AUTH_RESPONSE" =~ "multistatus" ]]; then echo "CalDAV service responding with proper DAV XML" add_test_result "CalDAV Service Response" "PASS" elif [[ "$CALDAV_AUTH_RESPONSE" =~ "401" ]]; then echo "CalDAV authentication failed - check credentials" add_test_result "CalDAV Service Response" "FAIL" else echo "CalDAV service response unexpected" add_test_result "CalDAV Service Response" "INVESTIGATE" fi fi # Generate summary report echo -e "\n${BLUE}=============================================${NC}" echo -e "${BLUE}ZIMBRA AUTODISCOVER DNS VALIDATION REPORT${NC}" echo -e "${BLUE}=============================================${NC}" if [ ${#PASSED_TESTS[@]} -gt 0 ]; then echo -e "\n${GREEN}✓ DNS CONFIGURATION VALIDATED (${#PASSED_TESTS[@]}):${NC}" for test in "${PASSED_TESTS[@]}"; do echo -e " ${GREEN}✓${NC} $test" done fi if [ ${#NEEDS_INVESTIGATION[@]} -gt 0 ]; then echo -e "\n${YELLOW}⚠ NEEDS REVIEW (${#NEEDS_INVESTIGATION[@]}):${NC}" for test in "${NEEDS_INVESTIGATION[@]}"; do echo -e " ${YELLOW}⚠${NC} $test" done echo -e " ${YELLOW}Note: These may be working but require manual verification${NC}" fi if [ ${#FAILED_TESTS[@]} -gt 0 ]; then echo -e "\n${RED}✗ DNS CONFIGURATION MISSING (${#FAILED_TESTS[@]}):${NC}" for test in "${FAILED_TESTS[@]}"; do echo -e " ${RED}✗${NC} $test" done fi # Overall assessment TOTAL_TESTS=$((${#PASSED_TESTS[@]} + ${#FAILED_TESTS[@]} + ${#NEEDS_INVESTIGATION[@]})) CONFIGURED_ITEMS=$((${#PASSED_TESTS[@]} + ${#NEEDS_INVESTIGATION[@]})) echo -e "\n${BLUE}DNS CONFIGURATION SUMMARY:${NC}" echo "Total autodiscover components tested: $TOTAL_TESTS" echo "Properly configured: ${#PASSED_TESTS[@]}" echo "Need review: ${#NEEDS_INVESTIGATION[@]}" echo "Missing configuration: ${#FAILED_TESTS[@]}" if [ ${#FAILED_TESTS[@]} -eq 0 ]; then echo -e "\n${GREEN}✓ AUTODISCOVER DNS CONFIGURATION COMPLETE${NC}" echo -e "All required DNS records are properly configured for Zimbra autodiscovery." elif [ ${#FAILED_TESTS[@]} -le 2 ]; then echo -e "\n${YELLOW}⚠ AUTODISCOVER MOSTLY CONFIGURED${NC}" echo -e "Core autodiscover functionality available, minor items need attention." else echo -e "\n${RED}✗ AUTODISCOVER CONFIGURATION INCOMPLETE${NC}" echo -e "Multiple DNS records missing - clients may have difficulty with automatic setup." fi echo -e "\n${BLUE}Zimbra autodiscover DNS validation complete.${NC}"
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 VALIDATION REPORT ============================================= ✓ DNS CONFIGURATION VALIDATED (16): ✓ DNS SRV _imaps._tcp ✓ DNS SRV _submission._tcp ✓ DNS SRV _autodiscover._tcp ✓ DNS SRV _caldavs._tcp ✓ DNS SRV _carddavs._tcp ✓ DNS TXT _caldavs._tcp ✓ DNS TXT _carddavs._tcp ✓ DNS CNAME autodiscover ✓ ActiveSync Autodiscover Service ✓ Thunderbird Autoconfig Service ✓ CardDAV Well-known URI ✓ CalDAV Well-known URI ✓ DAV Service Discovery ✓ ActiveSync Response Format ✓ CardDAV Service Response ✓ CalDAV Service Response DNS CONFIGURATION SUMMARY: Total autodiscover components tested: 16 Properly configured: 16 Need review: 0 Missing configuration: 0 ✓ AUTODISCOVER DNS CONFIGURATION COMPLETE All required DNS records are properly configured for Zimbra autodiscovery. Zimbra autodiscover DNS validation 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.