Первый пост был написан скорей на эмоциях. Метод Николь мне показался местами несостоятельным и громоздким, но стоит признать, что он во многом превосходит мои наработки и отныне я с удовольствием перенимаю ее приемы. Тем не менее Николь сделала легкие огрехи, которые я исправил. Также я убираю лишний мусор и экспериментирую с универсальными решениями для типичных задач при верстке.

К чему мы приходим:
— Использование внешних стилей и последующий прогон кода через инлайнер. Из-за усложнения кода это стало целесообразным
— Улучшение семантики через именование классов и сокращение участков кода
— Частичная поддержка the Bat! Хотя Николь на него забила.
— Полная поддержка всех мобильных почтовых клиентов
— Использование ранее опасных конструкций. Благодаря усердному тестированию проблемы решены.

Обертка


Давайте обрисуем новую обертку письма(Github):
Скрытый текст
<meta charset="utf-8">
<style>
	body {
		margin:0;
	}
	/* здесь будут общие стили нашего письма */
    .content {
        background-color:#ffffff;
        padding:30px;
    }
	.newsletter {
		margin: 0 auto;
		max-width: 600px;
	}
	.wrapper {
		width: 100%;
		background-color:#f4f4f4;
		table-layout: fixed;
		-webkit-text-size-adjust: 100%;
		-ms-text-size-adjust: 100%;
	}
	@media only screen and (max-width: 400px) {
        /* причесываем верстку на малых мобильных экранах */
	}
	@media screen and (min-width: 401px) and (max-width: 600px) {
		/* причесываем верстку на средних мобильных экранах */
	}
	@media screen and (min-width: 600px) {
		.newsletter { width:600px !important; }
	}
</style>
<!--[if (gte mso 9)|(IE)]>
    <style>
        /* здесь будут располагаться хаки для аутлука */
    </style>
<![endif]-->
<div align="center" class="wrapper">
    <!--[if (gte mso 9)|(IE)]>
    <table align="center" width="600" cellpadding="0" cellspacing="0" style="border-collapse:collapse;"><tr><td>
    <![endif]-->
	<table class="newsletter" align="center" width="100%" cellpadding="30" cellspacing="0">
    <tr>
		<td class="content" style="width:600px !important;">
            контент письма
        </td>
	</tr>
    </table>
    <!--[if (gte mso 9)|(IE)]></td></tr></table><![endif]-->
</div>

А теперь я расскажу что в ней и как:

— В первую очередь стоит отметить, что нам не нужны doctype, head и body. Без доктайпа поведение писем аналогично везде и не приносит сюрпризов. Head просто бесполезен. Ну а body все равно будет вырезан большинством почтовых вебморд. Именно по этой причине мы не задаем общий фон письма через body.

— Блок .wrapper является общей оберткой с указанием цвета фона, посему задаем ему фон и несколько css свойств для избежания непредсказуемого поведения.

— Таблица .newsletter — непосредственно отцентрированное письмо. Для его родителя .wrapper мы указали значение center для атрибута align, чтобы в yahoo mail наша таблица была отцентрирована. Стоит понимать, что в будушем для выравнивания любых таблиц необходимо оборачивать их дивами с указанным выравниванием. При этом не забываем указывать выравнивание для самой таблицы. Для .newsletter мы указали ширину в 100% через инлайн для верности, при этом указали max-width:600px для получения адаптивности. Но это свойство не работает в аутлуке и в the bat! Для решения проблемы с аутлуком мы нарисовали дополнительную таблицу, которая является родителем нашего .newsletter.

— Ячейка .content уже является частью письма. Для нее мы указали background-color:#ffffff в сss для задания белого фона письма в отличие от серого фона подложки. Мы явно указали width:600px !important; для решения проблемы the bat!, который не поддерживает max-width, получив 600px ширину письма в бате.



Результат мы видим на скриншоте выше. При этом мы можем спокойно дергать края браузера для тестирования адаптивности нашей верстки.

Контент


А теперь давайте добавим немного контента нашему письму(Github)
Скрытый текст
<meta charset="utf-8">
<style>
    body {
        margin:0;
    }
    a {
        color:#0077cc !important;
    }
        a img {
            border:0;
        }
        a span {
            color:#0077cc;
        }
    table {
        border-collapse:collapse;
    }
    p {
        margin:1em 0;
		font-family: arial;
        font-size:14px;
        color:#666666;
        line-height:1.4em;
        text-align: left;
    }
	    .paragraph {
	        font-family:arial;
	        font-size:14px;
	        color:#666666;
	        line-height:1.4em;
            text-align: left;
	    }
    .button {
        display:inline-block;
        vertical-align:top;
        width:150px;
    }
		.button table {
			border-collapse: separate !important;
			border:#ffffff 5px solid;
		}
        .button div {
            font-family:arial;
            font-size:14px;
        }
        .button a, .button span {
            color:#ffffff !important;
            text-decoration:none;
        }
    .content {
        background-color:#ffffff;
        padding:30px;
    }
    .h1 {
        font-family: arial;
        font-size: 24px;
        color:#666666;
        line-height:1.2em;
    }
    .newsletter {
        margin: 0 auto;
        max-width: 600px;
    }
    .wrapper {
        width: 100%;
        background-color:#f4f4f4;
        table-layout: fixed;
        -webkit-text-size-adjust: 100%;
        -ms-text-size-adjust: 100%;
    }
    @media only screen and (max-width: 400px) {
        /* причесываем верстку на малых мобильных экранах */
    }
    @media screen and (min-width: 401px) and (max-width: 600px) {
        /* причесываем верстку на средних мобильных экранах */
    }
    @media screen and (min-width: 600px) {
        .newsletter { width:600px !important; }
    }
</style>
<!--[if (gte mso 9)|(IE)]>
    <style>
        /* здесь будут располагаться хаки для аутлука */
    </style>
<![endif]-->
<div align="center" class="wrapper">
    <!--[if (gte mso 9)|(IE)]>
    <table align="center" width="600" cellpadding="0" cellspacing="0" style="border-collapse:collapse;"><tr><td>
    <![endif]-->
    <table class="newsletter" align="center" width="100%" cellpadding="30" cellspacing="0">
    <tr>
        <td class="content" style="width:600px !important;">
            <div class="h1">Paragraph Heading</div>
            <p><a href="#" target="_blank"><span>Lorem ipsum dolor</span></a> sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
            <div align="center">
                <div class="button">
                    <table cellpadding="10" cellspacing="0">
                        <tr>
                            <td bgcolor="#666666" style="padding:10px 20px; border-radius:50px;">
                                <div><b><a href="#" target="_blank"><span>Button Example</span></a></b></div>
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="button">
                    <table cellpadding="10" cellspacing="0">
                        <tr>
                            <td bgcolor="#666666" style="padding:10px 20px;">
                                <div><b><a href="#" target="_blank"><span>Button Example</span></a></b></div>
                            </td>
                        </tr>
                    </table>
                </div>
				<div class="button">
                    <table cellpadding="10" cellspacing="0">
                        <tr>
                            <td bgcolor="#666666" style="padding:10px 20px; border-radius:50px;">
                                <div><b><a href="#" target="_blank"><span>Button Example</span></a></b></div>
                            </td>
                        </tr>
                    </table>
                </div>
            </div>
            <br>
            <div class="h1">Markered List</div>
            <p>–  Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
            <p>–  Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
            <p>–  Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
            <p>–  Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
        </td>
    </tr>
    </table>
    <!--[if (gte mso 9)|(IE)]></td></tr></table><![endif]-->
</div>



И разберем все по порядку.

Заголовок


.h1 {
        font-family: arial;
        font-size: 24px;
        color:#666666;
        line-height:1.2em;
    }

<div class="h1">Paragraph Heading</div>


Используется именно div без задания margin для избежания появления лишних отступов для блоков, у которых указан верхний padding.

Абзац


p {
        margin:1em 0;
		font-family: arial;
        font-size:14px;
        color:#666666;
        line-height:1.4em;
        text-align: left;
    }
	    .paragraph {
	        font-family:arial;
	        font-size:14px;
	        color:#666666;
	        line-height:1.4em;
            text-align: left;
	    }

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>

<div class="paragraph">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>

Обращаю внимание на то, что для тега абзаца мы явно указываем margin. Это необходимо для пересчета отступов в outlook.com. Там они кардинально отличаются от дефолтных. Тег параграфа идеально использовать вместе с предыдушим вариантом заголовка.

Второй вариант абзаца, который выполнен при помощи div используется куда реже, но тем не менее иногда необходим. Это все тот же абзац, который просто не имеет вертикальных отступов.

Ссылка


    a {
        color:#0077cc !important;
        text-decoration:underline;
    }
        a img {
            border:0;
        }
        a span {
            color:#0077cc;
        }

<a href="#" target="_blank"><span>Lorem ipsum dolor</span></a>

Конструкция ссылки всегда должна содержать внутри span, если ссылкой является текст. Это костыль для аутлука. Мы задаем цвет ссылки и такой же цвет для внутреннего span. Явное задание декорирования цвета необходимо для мобильного gmail, который снимает подчеркивания у ссылок. Для изображений ссылок мы сразу устанавливаем нулевой border.

Кнопка


.button {
        display:inline-block;
        vertical-align:top;
        width:150px;
    }
		.button table {
			border-collapse: separate !important;
			border:#ffffff 5px solid;
		}
        .button div {
            font-family:arial;
            font-size:14px;
        }
        .button a, .button span {
            color:#ffffff !important;
            text-decoration:none;
        }

<div class="button">
    <table cellpadding="10" cellspacing="0">
        <tr>
            <td bgcolor="#666666" style="padding:10px 20px; border-radius:50px;">
                <div><b><a href="#" target="_blank"><span>Button Example</span></a></b></div>
            </td>
        </tr>
    </table>
</div>

Конструкция кнопки немного громоздка, но тем не менее железобетонна. В первую очередь наша кнопка — это инлайновый, а не блочный элемент, что позволяет нам размещать несколько кнопок рядом друг с другом. Для того, чтобы иметь возможность использовать адекватный border-radius для кнопки таблицу внутри кнопки надо обязательно сепарировать. Остальное по примеру ссылки. Немного офрормления и кнопка готова. Важное замечание. Наша кнопка имеет свойство схлопываться в две строчки, если ее название состоит из двух слов, поэтому я использую неразрывный пробел для лечения этой проблемы. В качестве альтернативы можно использовать фиксированную ширину ячейки таблицы. Для выравнивания кнопок используется родительский div с явно заданным выравниванием.

И наконец, список


О нем я уже писал в предыдущем топике И не буду повторять подробности. Я лишь просто покажу как его оформлять лучшим образом. Никакого лишнего CSS. Все предельно просто.

Так как хабрапарсер жрет юникод символы, я вставлю пример кода скриншотом.



На этой ноте я закончу «снова первую часть», а в следующей расскажу как оформить другие части письма, а также приведу примеры мультиколоночности. Для тех, кто скажет «Какого хрена? Ведь в предыдущей статье было тоже самое!» отвечу, что разница огромна. Чтобы прийти к этому подходу мне понадобилась неделя множественных тестов, проб и ошибок.

Стэй тюнд.

UPD: на гитхабе вы, конечно, можете найти более полный файл верстки, но я специально не давал на него ссылку здесь, потому что многие его нюансы могут быть не сразу понятны. Это пока что экспериментальная версия окончательной методики. Как говорится, если интересно, курите сами.

UPD2: Всплыли баги в мобильном yahoo клиенте. О решении обязательно напишу апдейтом к этому топику или в следующих частях.

UPD3: Баги Yahoo решены. Мобильное приложение yahoo не понимает свойства cellpadding у таблиц, следовательно мы должны указывать отступы ячеек через стили. Также в yahoo скукоживались инлайн кнопки. В качестве панацеи используется фиксированная ширина для кнопки. Не так круто, как хотелось бы, но что поделать.

В рамках нашего сегодняшнего материала необходимо добавить следующий CSS

.content { padding:30px; }
.button { width:150px; }

Комментарии (25)


  1. M1nstrel
    14.07.2015 09:33
    +3

    Спасибо за ваш труд по тестированию.


    1. dudeonthehorse Автор
      14.07.2015 12:39

      На самом деле это главная причина, по которой я этим занимаюсь. Верстать под 5 браузеров не так интересно, как под over40 почтовиков.


  1. antirek
    14.07.2015 11:13
    +1

    Это реально круто, что есть верстальщики, которые заморачиваются как будет выглядеть письмо в веб-клиентах. Письмо, которое откроют, посмотрят и перейдут к следующему. Но для меня, как программиста, хочется получить какой-нибудь API, например под node.js, чтобы как-то абстрагироваться от всех этих нюансов верстки и просто знать, что мои письма (а я отправляю разные отчеты, таблицы, графики) будут и красиво, и корректно отображаться на различных устройствах и в разных почтовых клиентах.

    API, который бы позволил быстро сделать емейл как одностраничный сайт, как лендинг, сформированный из разных блоков: заголовок, красочный блок с картинкой, три колонки с основными вещами, блок с таблицей, блок с графиком, блок с формой сбора данных, блок со ссылками и трекингом, блок с логотипом сервиса и слоганом (что-то еще может быть?)

    Может уже такое есть? Планируете ли вы такое сделать на основе своих изысканий?


    1. dudeonthehorse Автор
      14.07.2015 11:20
      +1

      Тут скорей не API, а некий сверстанный UI KIT(фреймворк если угодно), на основе которого можно будет собирать необходимые письма. Сейчас есть в таком виде моего исполнения. Если нужно нечто более заточенное под ваши нужды, велкам в личку.

      Я неспроста начал эту серию статей. Ибо то, что вы ищите в принципе не существует толком. Есть Zurb Ink framework, но он ущербен и поддерживает малое количество почтовых клиентов.


      1. antirek
        14.07.2015 12:16

        Может быть и UI kit.

        Я смотрел Zurb Ink и еще MUI. Была мысль написать небольшую обертку для MUI для красоты емейлов. Вроде попроще. После отпуска надо заняться.

        >>Я неспроста начал эту серию статей.
        А какова конечная цель? Серия статей это хорошо, но это не «взял и стал использовать». Есть ли предложения, патчи к zurb ink для устранения ущербности, внесения ваших предложений в их фреймворк?


        1. dudeonthehorse Автор
          14.07.2015 12:34

          Я в принципе на руках уже имею более чем достойный конкурент Зурбу. Ссылку на него и дал в комментарии выше. Но использовать его в лоб было бы глупым, ибо у стороннего пользователя просто нет понимания принципов верстки писем. А писать документацию к готовому решению я не хочу. Цель моих статей — донести то самое понимание для обычного верстальщика.


          1. antirek
            14.07.2015 13:34

            Вспомнил, что еще недавно видел такую штуку (собственно где и есть немного зачатков «лендинг, сформированный из разных блоков»): github.com/bevacqua/campaign


            1. dudeonthehorse Автор
              14.07.2015 13:47

              Неплохое решение, так как там уже прикручено Mandrill API.
              Но шаблон ни разу не гибкий, и сверстан отвратительно, увы.


    1. dudeonthehorse Автор
      14.07.2015 11:22
      +1

      Я как раз занимаюсь не только маркетинговым шлаком, но и триггерными рассылками, о которых вы, скорее всего говорите. Будь то служебные письма или отчеты какого нибудь дэшборда с графиками и статистикой. Мне это в любом случае интересно.


      1. antirek
        14.07.2015 12:19

        Да, всякие ежедневные отчеты о количестве действий, емейлы при совершении различных действий на сайте, заполнении форм и т.п. В любое такое письмо полезно вставлять фирменную шапку, логотип и контакты техслужбы в футер.


        1. dudeonthehorse Автор
          14.07.2015 12:41
          +1

          Вы можете свободно использовать мой шаблон на гитхабе для этих целей.


  1. Varhal
    14.07.2015 12:10

    А тестировали на Outlook десктоп версии?


    1. dudeonthehorse Автор
      14.07.2015 12:13

      Обижаете. Само собой. Если возникли проблемы, то напоминаю, что перед отправкой код надо прогонять через инлайнер


      1. Varhal
        14.07.2015 12:19

        Дело в том, что медиа квери создавало много проблем, поэтому мы от него отказались в пользу более универсального метода. Не все маил клиенты, особенно десктопные и мобильные, понимают media


        1. dudeonthehorse Автор
          14.07.2015 12:22

          Дело в том, что описанная в этой статье методика полностью адаптивна без единого медиазапроса.


          1. Varhal
            14.07.2015 12:23

            я почему-то увидел медиа запросы у вас в коде, вот и пишу)) раз так — то ок


            1. dudeonthehorse Автор
              14.07.2015 12:27

              Конкретно в этой статье присутствует всего один медиазапрос, который является перестраховкой для Outlook 2011 и Apple Mail версий ранее шестой. Там медиазапросы работают.

              Будучи автором статьи по верстке писем стыдно невнимательно относится к подобному материалу.


              1. eosunknown
                14.07.2015 21:26

                А ка же

                media only screen and (max-width: 400px) {
                /* причесываем верстку на малых мобильных экранах */
                }
                media screen and (min-width: 401px) and (max-width: 600px) {
                /* причесываем верстку на средних мобильных экранах */
                }
                media screen and (min-width: 600px) {
                .newsletter { width:600px !important; }
                }


                С этой конструкцией как раз инлайнер и не справляется нормально...?


                1. dudeonthehorse Автор
                  14.07.2015 23:11

                  А это инлайнеру и не нужно. Эти записи для тех почтовых клиентов, которые медиазапросы поддерживают. Мы делаем адаптивный шаблон без медиазапросов, но можем их использовать для финальной «прически» шаблона. Пример использования я приведу в следующих частях.


    1. dudeonthehorse Автор
      14.07.2015 12:19
      +2

      Тестировалось во всех версиях Аутлука, Бате, Яндексе, Мейле, Яху, Рамблере, Мейлбоксе, Гмейле, Спэрроу, ЭйрМейле, Яблочном Мейле, Андроид Мейле, АОЛ, МайКом, Тандерберде. Как на десктопах и вебмордах, так и на всех мобильных приложениях.


      1. Varhal
        14.07.2015 12:22
        -1

        del


  1. VitaLik_is_goodman
    22.07.2015 23:40

    C кнопкой поскромничали, мне кажется, она не нажимается по всей площади, а для триггерных писем это важно, так как там обычно одна кнопка большая на все письмо

    Как вариант можно на td указать vertical-align: middle, ну и кнпоку сделать display: inline-block c paddingами. Где padding не работают, там она будет маленькая, а где работают будет все пучком


    1. dudeonthehorse Автор
      23.07.2015 07:00

      Качество кликабельности текущей кнопки не раз тестировалось. Надо быть суперкриворуким, чтобы по ней не попасть. Если критично, можно сделать кнопку картинкой. Тогда уж точно будут все счастливы. Тем не менее замечание уместно и кнопкой я займусь, но у этого момента меньший приоритет, чем у других.


  1. antirek
    28.07.2015 08:23

    А стоит ли embed'ить картинки в html в base64? Или оставлять лучше просто ссылки на внешние ресурсы, полагась на их загрузку почтовиками? Или еще есть возможность через content id ссылаться на картинки в письме?


    1. dudeonthehorse Автор
      28.07.2015 09:09

      За content id не скажу, ибо не приходилось сталкиваться. А base64 избыточен как по мне. Бытует легенда, что за злоупотребление base64 можно улететь в спам. Да и незачем засорять письмо картинками. Это назойливо и реакция получателя может быть отрицательной. Я все картинки размещаю на внешнем ресурсе, просто прописывая к ним url. Хотя кодировать в base64, например, логотип компании или крайне смысловую картинку — нормальная практика.